vue-cli3 webpack 打包优化实践总结

年前做过一个公司内部的后管平台,前端使用 vue.js 框架开发,基于 vue-cli3 脚手架构建项目,ui 组件库使用 Element-UI,其他组件还包括 axios,echarts,vue-router,vuex 等,该项目功能简单,涉及页面 40 个,都是简单的表单或者列表页。上线的时候直接 npm run build 命令打包,未在 vue.config.js 中做任何配置,将 dist 打好的包放到 nginx 服务器,做个反向代理就用上了。

未做优化的打包结果如下:


未做路由组件动态引入

可以看出 chunk-vendors.82702712.js 文件大小有 1M 多,对应的 css 文件也有 194 kb,app.653b22c5.js 有 172 kb,项目中所有的三方库都打包到 chunk-vendors.82702712.js 中,而所有的页面,自定义组件等都在 app.653b22c5.js 中。因为没有使用动态加载,所以这些 1M,几百 kb 的 js 都会在刚访问网站的时候一起加载,pc 上还好,一旦放到移动端,弱网情况下只能给用户展示一个大白板。

访问项目主页,通过 Chrome 的 Instrument converge 功能查看 js,css 的资源使用率,发现 chunk-vendors.82702712.js 的未使用率达到 85.4%,对应的 chunk-vendors.ea3fa8e3.css 未使用率 98.3%,app.js 的未使用率是 88.4 %,app.css 未使用率是 85%,说明项目首页访问的资源体积大,是因为包含了一大部分未使用的组件,如果每个页面都只加载自己需要的组件,那网站的访问速度会得到较大的提升。

资源使用状况

基于上面遇到的问题,优化打包可以从以下几个方面入手。

  1. 组件动态引入,按需加载,app.653b22c5.js 可以拆分成若干个 js,使用时才去加载对应的 js。
  2. 三方库组件,按需加载,chunk-vendors.82702712.js 可以做拆分,不必一下全部加载,可在使用时再去加载对应部分的 js。
  3. 对资源文件做压缩,上图中可以看到 1069.96kb 的文件, gzip 可以压缩到 310.03kb。

着手优化项目打包

路由懒加载

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
在 router.js 中,原来导入组件的方式是:

import Home from './views/Home.vue'
import Login from './views/Login.vue'
import Role from './views/system/Role.vue'
import User from './views/system/User.vue'
...

这就导致这些组件最后都打包到一个 app.653b22c5.js 文件中,我们修改导入组件的方式,改为动态导入:

const Home = () => import('./views/Home.vue')
const Login = () => import('./views/Login.vue')
...

修改完所有的组件导入后,在运行 npm run build 打包发现,原来的 app.653b22c5.js 和 css 文件得到了拆分。

路由动态加载-1

可以看到 app.js 文件从原来的 172.77kb 降到了 18.14 kb。

路由动态加载-2

但是拆分过细,每个页面都独自拆分出来,一个 js 大一点的 7kb,小的才 2,3kb,css 拆分后有的 0.1kb,甚至还有 0.03kb的,拆分的粒度过细,也会造成更多的网络资源请求,对网站加载造成影响。

其实我们的一个功能流程通常会涉及多个页面,如果能将多个页面组件分组打包,效果会更好,避免了多次网络资源请求。

路由懒加载分组

分组修改方法如下:

const Role = () => import(/* webpackChunkName: "group-role" */'./views/system/Role.vue')
const AddRole = () => import(/* webpackChunkName: "group-role" */'./views/system/AddRole.vue')
const EditRole = () => import(/* webpackChunkName: "group-role" */'./views/system/EditRole.vue')

const User = () => import(/* webpackChunkName: "group-user" */'./views/system/User.vue')
const AddUser = () => import(/* webpackChunkName: "group-user" */'./views/system/AddUser.vue')
const EditUser = () => import(/* webpackChunkName: "group-user" */'./views/system/EditUser.vue')
const UserAllotRole = () => import(/* webpackChunkName: "group-user" */'./views/system/UserAllotRole.vue')

按照功能将组件分组后,打包结果如下:

路由组件动态引入模块分组

每个功能模块打包后的 js 大概有十几kb,文件数量也大大减少。

node_modules 打包

chunk-vendors.js 是对 node_modules 中三方库的打包,本项目中 chunk-vendors.js 的大小,Element-UI 占了大部分比例。group-monitor.js 为 327.92 kb 是因为其引入了 echart。

我在 main.js 中完整引入了 Element-UI,而不是按需引入:

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import http from './utils/network/http'

Vue.use(ElementUI)

所以会将 Element-UI 的所有组件打包到项目中。

对三方库的引用务必使用按需加载,不需要的组件就不要引入进来。优化时将 Element-UI 常用的组件在 main.js 中按需引入。

import { Container, Menu } from 'element-ui';

Vue.use(Container);
Vue.use(Menu);

页面中有单独用到的 Element-UI 组件,则在该页面中单独引入对应的组件,这样在访问该页面时才去加载这些 ui 组件。

由于 40 多个页面用到了 Element-UI 组件,改造工作量比较大,我用 echart 在其他页面做测试,看下打包情况。

我找到三个页面分别按需引入 echart.js:

// MonitorDetail.vue 按需引入 echart.js
var echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/pie');
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('echarts/lib/component/legendScroll');

// Bulletin.vue 按需引入 echart.js
var echarts = require('echarts/lib/echarts');
require('echarts/lib/chart/bar');
require("echarts/lib/chart/tree");

// About.vue 按需引入 echart.js
var echarts = require('echarts/lib/echarts');
// 与 Bulletin.vue 公共的引入
require('echarts/lib/chart/bar');
require("echarts/lib/chart/tree");
// 与 MonitorDetail.vue 公共的引入
require('echarts/lib/chart/pie');
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('echarts/lib/component/legendScroll');
// 自己独有的引入
require("echarts/lib/chart/treemap");
require("echarts/lib/chart/graph");
require("echarts/lib/chart/gauge");

About.vue 引入的 echart 组件中有一部分与 MonitorDetail.vue 相同,一部分与 Bulletin.vue 相同,还有三个页面公共的部分,我们期望公共的部分打包成一个 js,不会重复打包,About.vue 独有部分还和 Acount.vue 在一起打包。

打包结果如下:

三方库打包优化

about~group-bulletin~group-monitor.js 是三个页面 echart 的公共部分,about~group-monitor.js 是 About.vue 与 MonitorDetail.vue echart 的公共部分,about~group-bulletin.js 是 About.vue 与 Bulletin.vue 的公共部分,about.js 独有的 echart 组件则还在自己的 js 中。

只要我们按需引入三方库组件,vue-cli3 就能智能的帮助我们合理的打包,这主要是依靠插件 SplitChunksPlugin 来完成的。

gzip 压缩

如果 Nginx 服务器开启 gzip,会将静态资源在服务端进行压缩,压缩包传输给浏览器后,浏览器再进行解压使用,这大大提高了网络传输的效率,尤其对 js,css 这类文本的压缩,效果很明显。

以下是 Nginx 开启 gzip 的配置:

# 开启|关闭 gzip。
gzip on|off;

# 文件大于指定 size 才压缩,以 kb 为单位。
gzip_min_length 10;

# 压缩级别,1-9,值越大压缩比越大,但更加占用 CPU,且压缩效率越来越低。
gzip_comp_level 2;

# 压缩的文件类型。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;

# 开启后如果能找到 .gz 文件,直接返回该文件,不会启用服务端压缩。
gzip_static on|off
    
# 是否添加响应头 Vary: Accept-Encoding 建议开启。
gzip_vary on;

# 请求压缩的缓冲区数量和大小,以 4k 为单位,32 为倍数。
gzip_buffers 32 4K;

如果 Nginx 没有开启 gzip,前端在打包的时候可以打包出一份资源的压缩版本,Nginx 也会把压缩文件传输给浏览器。

首先安装一个插件:

npm i -D compression-webpack-plugin

在 vue.config.js 中配置下这个插件:

const CompressionPlugin = require("compression-webpack-plugin")

module.exports = {
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new CompressionPlugin({
            test: /\.js$|\.html$|\.css/,
            threshold: 10240,
            deleteOriginalAssets: false
          })
        ]
      }
    }
  }
}

nginx 服务器还要做一下简单配置:

gzip_static on;

使用 compression-webpack-plugin 插件后的打包结果如下:

使用 gzip 压缩打包

上图中的 .gz 就是对应一个资源文件的压缩版本。

配置成功后,重新将代码部署到 nginx,重新载入 nginx 配置。

我们先看一下没有开启 gzip_static off; gzip 的访问情况:

nginx 未开启 gzip 压缩

我们直接看最大的两个文件 chunk-vendors.js 和 chunk-vendors.css,他们的大小分别是 748kb 和 194kb,加载用时分别是 622ms 和 247ms。

然后我们开启 gzip gzip_static on; 再看下资源的加载情况:

nginx 开启 gzip 压缩

同样是 chunk-vendors.js 和 chunk-vendors.css,他们的大小变成 190kb 和 29.3kb,加载时间变成 245ms 和 46ms。

以上就是使用 gzip 的效果。

Vuex 动态注册模块

vuex 通常使用静态模块,这些模块都会打包到 app.js 中,但是如果有的模块过大而且不是立刻就会用到,我们可以动态的注册模块到 vuex 中。

在使用 vuex 某个模块的时候才注册:

mounted () { 
    this.$store.registerModule('myModule', MyModule)
}

在不使用的时候,注销模块:

beforeDestroy () {
    this.$store.unregisterModule('myModule')
}

这样实现的效果是该页面在加载时才下载模块内容,而不是刚访问网站就去下载。

工具

打包时,我们可以通过一些辅助工具帮助分析打包情况,有重点的去做优化。

打开 Chrome 的调试模式,CMD+SHIFT+P 调出命令面板,输入 Coverage,选择 Show Coverage,然后访问指定网站,点击按钮 Instrument converge 就能查看已加载资源的未使用率。

Instrument converge

使用 webpack-bundle-analyzer 插件可以分析出打包后的文件结构。

安装该插件 npm i –D webpack-bundle-analyzer,vue.config.js 中做如下配置:

  chainWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      if (process.env.npm_config_report) {
        config
          .plugin('webpack-bundle-analyzer')
          .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
          .end()
        config.plugins.delete('prefetch')
      }
    }
  }

然后使用 npm run build --report 命令打包就可以查看到打包后的文件结构。

webpack-bundle-analyzer

这个打包后的文件结构页面很有参考价值,对打包的优化帮助很大。

参考文章

提升90%加载速度——vuecli下的首屏性能优化
Vue 性能优化:如何实现延迟加载和代码拆分?
vue-cli3+nginx配置gzip压缩

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容