公司的一個管理后台項目是基於 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 項目里不需要我們手動添加模式。個人覺得優化方案選擇合適的就好,沒必要全上,可以把更多的心思花在業務代碼的優化上。
有錯處之處還望指出,謝謝。