公司的一个管理后台项目是基于 vue-cli3 搭建的,这两天我将它升级到了 vue-cli4,顺手也做了一些优化,主要是从 webpack 方面入手,优化一下生产环境的代码。
这里简单提一下怎么升级脚手架版本的,首先将你电脑中的脚手架版本升级到 4,直接重新安装就好,然后在你的项目中执行 vue upgrade 这个命令,根据提示一步一步地去升级即可。这里就不多描述细节了,网上能找到很多文章。
接下来主要聊聊 vue-cli 项目中可以配置的一些优化方案,下面提到的方案也并不是我都用上的,还是需要根据真实项目来决定用或不用。
删除 console.log
在开发时,我还是喜欢用 console.log 来调试,但是部署到线上时,最好还是删掉这部分代码。别看代码不多,当项目到了一定规模时,还是占用不少体积的。脚手架内部使用的是 TerserWebpackPlugin 这个插件来压缩js代码的,它自带一个删除 console.log 的功能,所以直接配置即可,在 configureWebpack 中配置,如下:
{ // ... configureWebpack: config => { // 生产环境下生效 if (process.env.NODE_ENV === 'production') { // 配置删除 console.log config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true } } // ... }
启用 gzip 压缩
使用 gzip 压缩代码,效果显著。gzip 是需要服务端配置的,当开启时会压缩我们的线上代码,但是如果我们前端已经使用 gzip 压缩过,那么服务端就会直接使用已经压缩好的代码,就不需要花时间再去压缩了。前端启用 gzip 压缩,使用到的插件是
// 需要先安装这个插件 // npm install compression-webpack-plugin const CompressionWebpackPlugin = require('compression-webpack-plugin') { // ... configureWebpack: config => { // 生产环境下生效 if (process.env.NODE_ENV === 'production') { // 配置 gzip 压缩 config.plugins.push( new CompressionWebpackPlugin({ test: /.js$|.html$|.css$/, threshold: 4096 // 超过4kb压缩 }) ) } } // ... }
当配置好 gzip 后,再去打包,会发现打包文件中有 .gz 结尾的文件,说明配置 gzip 压缩成功了:
当然你可能还需要跟后端沟通一下,得让服务端开启这个功能。
代码分割
对于 vue 项目,如果你留心过打包文件,会发现一个叫 chunk-vendors 的 js 文件,这个文件包含了业务代码和一些引入的第三方库代码,所以一般体积比较大,浏览器加载时也耗时更多。
其实我们可以将这部分代码做一个分割,分成多个文件,这样浏览器加载时会并行加载,而且对于不变的代码,会直接从缓存中读取,速度得到提升。
webpack 提供了一个配置可以实现这样的功能,从 webpack4 开始,可以在 optimization.splitChunks 中配置代码分割功能,可以参考文档 splitChunksPlugin 。直接看文档可能有些懵,建议多搜些此类文章看看,我这里直接给出在 vue.config.js 中配置 splitChunks 的做法,我是在 chainWebpack 中配置的:
{ // ... chainWebpack: config => { config .when(process.env.NODE_ENV === 'production', // 配置生产环境生效 config => { config.optimization.splitChunks({ chunks: 'all', // 将对什么类型的代码进行分割,三种值:all: 全部 | async: 异步,按需加载的代码 | initial: 入口代码块 cacheGroups: { // 缓存组 // 定义 libs 缓存组,分割从 node_modules 中引入的代码 libs: { name: 'chunk-libs', // 分割成的文件名 test: /[\/]node_modules[\/]/, // 匹配 node_modules 中模块 priority: 10, // 优先级,当模块同时命中多个缓存组的规则时,分配到优先级高的缓存组 chunks: 'initial' // 这里覆盖上面的 chunks: 'all',仅打包最初依赖的第三方库 }, // 项目使用 iview 组件开发的,定义 iviewUI 缓存组,用于分割 iview 代码 iviewUI: { name: 'chunk-iviewUI', priority: 20, // 优先级 20,命中 iview 代码时,优先分割到此组里 test: /[\/]node_modules[\/]_?iview(.*)/ // 匹配 iview 代码 } } }) } ) } // ... }
目前我只做了简单的分割,对我的项目而言已经足够了,splitChunks 的配置项有很多,建议多看看此方面的资料。打包后可以看到代码已被分割:
配置 CDN
老实说,我不用这个功能的,线上使用 cdn 总让我有一种不安全感,除非公司有自己的 cdn 库,不过这确实也是一种优化方案,效果也还不错。它的配置也很简单,在 externals 中配置,例子:
{ // ... configureWebpack: config => { if (process.env.NODE_ENV === 'production') { // 配置 cdn,这里将 vue,vue-router 和 axios 三个包配置成 cdn 引入 // 其中 Vue,VueRouter 等名称是该库暴露在全局中的变量名 config.externals = { vue: 'Vue', 'vue-router': 'VueRouter', axios: 'axios' } } } // ... }
然后在 public/index.html 模板文件中引入 cdn 地址:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title></title> <!-- 引入 cdn 地址 --> <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.5.10/vue.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.18.0/axios.min.js"></script> </head> <body> <div id="app"></div> </body> </html>
我这里使用的是 bootcdn 的地址,需要注意版本问题。也可以借助 HtmlWebpackPlugin 插件来方便插入 cdn 的引入。
使用 cdn 引入的方式虽然能极大改善网页加载速度,但我还是不会用这个功能,项目还不需要非得这样的优化,也怕 cdn 不稳定。觉得需要的还是可以上的。
图片压缩
图片压缩会影响图片质量,这个根据个人需求来选择。管理后台项目一般图片不多,所以我自己没有用上。试验一下也未尝不可,需要安装 image-webpack-loader ,配置如下:
{ // ... chainWebpack:config => { // 配置图片压缩 config.module .rule('images') .use('image-webpack-loader') .loader('image-webpack-loader') .options({ bypassOnDebug: true }) .end() } // ... }
可以看一下效果,配置前的某图片:
配置图片压缩后:
效果还是显著的,对图片质量要求不高的可以配置上。
IgnorePlugin
webpack 中的 IgnorePlugin 插件,可以在打包第三方包时忽略一些文件内容,有效减小打包文件体积。一个典型的案例就是忽略 moment 插件的语言包,moment 自带有很多语言包,即使你不用这些包,也会被一起打包了,这时候就可以使用 ignorePlugin 来忽略打包这部分文件,配置插件:
{ // ... plugins:[ new webpack.IgnorePlugin(/^./locale$/, /moment$/) // 忽略打包语言包 ] // ... }
在使用时,只需要引入自己需要的那个语言包即可:
import moment from 'moment' // 引入中文 import 'moment/locale/zh-cn' // 设置中文 moment.locale('zh-cn'); let momentStr = moment().subtract(10, 'days').calendar();
这里只是例举 moment 这个例子,关于时间格式化,我还是喜欢自己去封装,或者可以使用 dayjs 这个库,更轻量化。
DllPlugin
这个适用于开发环境,每次构建都需要打包引入的第三方模块,如果不打包这些模块,那么构建速度将大大提升,这正是 DllPlugin 要做到的事。
说的明白点就是在构建项目之前,先把一些第三方包打包好,然后项目构建时直接引用这些打包好的第三方包,提升项目构建速度。
需要使用到两个 webpack 内置的插件,分别是 DllPlugin 和 DllReferencePlugin。使用 DllPlugin 插件打包出 dll 文件,使用 DllReferencePlugin 插件使用 dll 文件。
我这里以打包 lodash
这个第三方模块为例。首先要编写一个 webpack 配置文件用以打包 lodash
模块:
// webpack.dll.js const webpack = require('webpack') const path = require('path') module.exports = { mode:'development', entry:{ lodash:['lodash'] }, output:{ // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称 // 在这里 name 指的就是 lodash filename:'[name].dll.js', // 输出的文件放到这个目录下 path:path.join(__dirname,'public'), // 存放动态链接库的全局变量名称。这里就是 _dll_lodash library:'_dll_[name]' }, plugins:[ new webpack.DllPlugin({ // 该字段的值需要和 output.library 中保持一致 // 该字段的值也就是输出的 manifest.json 文件中的 name 字段的值 name:'_dll_[name]', // 描述动态链接库的 manifest.json 文件输出时的文件名称 path:path.join(__dirname,'public/[name].manifest.json') }) ] }
然后可以在 package.json
的 scripts
中写入一个命令:
scripts:{ // ... "dll": "webpack --config webpack.dll.js" }
此时执行 npm run dll
即可打包出 lodash
的 dll 文件。
接下来还有两个重要步骤,一个是要在模板文件中手动引入刚打包出来的文件:
<body> <script src="../public//lodash.dll.js"></script> </body>
在 vue.config.js 中使用 DllReferencePlugin 插件来引用映射文件:
{ // ... configureWebpack: config => { if (process.env.NODE_ENV === 'development') { config.plugins.push(new webpack.DllReferencePlugin({ manifest: require('./public/vendor/vendor.manifest.json') })) } } // ... }
这样一套配置下来后,项目构建时就不会每次都打包 lodash 这个包了。
结语
优化方案绝不止我上面提到的几种,比如路由懒加载也算是一种优化,vue 项目中建议都用上路由懒加载,再比如考虑给 babel 加上缓存优化,甚至添加 mode: production 都能达到优化的目的,webpack 在生产模式下会自动开启一些优化(Tree Shaking,Scope Histing 等),当然 vue-cli 项目里不需要我们手动添加模式。个人觉得优化方案选择合适的就好,没必要全上,可以把更多的心思花在业务代码的优化上。
有错处之处还望指出,谢谢。