加入新公司一個月,最近接手在做一個 chrom 瀏覽器插件的項目,開發過程中發現項目打包的時間很長,足足有30多秒,這是讓人很難接受的,而且構建的顯示了幾條包體積過大的提示信息:

可以看到,打包后有三個包超過了建議的體積,是什么導致了打包時間長和包的體積過大呢?
下面通過一些具體方法來分析原因和解決這個問題。
什么原因導致構建包變得這么大?
為了分析是什么導致構建包為什么會變得這么大,可以安裝 webpack-bundle-analyzer 插件,通過它可以直觀地查看構建包中所有項目的大小。
npm install —save-dev webpack-bundle-analyzer
對應的需要在 webpack.config.js 中做如下配置:
const { BundleAnalyzerPlugin } = require(‘webpack-bundle-analyzer’)
plugins: [
...,
new BundleAnalyzerPlugin({
analyzerPort: 8081,
}),
]
配置完成后再次運行構建 npm start,瀏覽器會自動打開 http://127.0.0.1:8081,在網頁上可以看到構建包中每個文件的詳細信息。

從圖中可以找出影響體積的罪魁禍首有:
jquery、Moment、mammoth、html2canvas、xlsx、cpexcel、dexie 等
那么這些體積龐大的依賴庫都需要打到項目的運行包里面嗎?當然不是的。那我們逐步來優化這些依賴。
減少 moment 大小

moment 在包中占用了 545k 的體積,查看分析圖可以看到,庫文件中主要是各種用於支持語言版本的的locale文件,
但是項目中並不需要這部分功能,因此這部分數據是應該優化的。
查看項目中引入 moment 的方式
import moment form 'moment'
這樣會將整個 moment 包都導入到文件中,為了避免導入不必要的文件,可以這么寫:
import moment from 'moment/src/moment'
但是這么寫會有個問題,如果項目中有新成員加入,極大的可能他不會這樣寫,而是像原來一樣導入了整個 moment 包,因此為了避免這樣的問題,可以考慮在 webpack 中創建一個別名,這樣每次導入 moment 的時候就默認只導入文件夾下面的 moment.js 文件了,如下:
resolve: {
好了,重新啟動服務進行打包,報錯提示無法找到 ./locale:

查看 moment 的 官方 issue 發現這是一個存在已久的問題:moment.js 總是會加載 locales,還假定 locales 存在。你不能讓 moment 只加載日期操作函數。
官方提供的解決方案是把 package.json 中的 Moment 的版本改成 2.18.1, 如下:

不過在 Stack Overflow 上找到了另一種解決方案:
簡單介紹一下 IgnorePlugin
- 這是webpack內置插件
- 作用:忽略第三方包指定目錄,讓這些指定目錄不要被打包進去
嘗試一下,在 webpack.config.js 中添加如下配置:
plugins:[ // moment這個庫中,如果引用了./locale/目錄的內容,就忽略掉,不會打包進去 new Webpack.IgnorePlugin(/\.\/locale/,/moment/), ]
按照上面的方法,減小了 moment 的打包體積,同時也避免了報錯,但是如果項目中需要用到語言包該怎么辦呢?很簡單,手動引入一下就可以了:
import moment from 'moment' //手動引入所需要的語言包 import 'moment/locale/zh-cn'; moment.locale('zh-cn'); const r = moment().endOf('day').fromNow(); console.log(r);
Ok,這么一來能夠顯示中文,又把不必要的語言包都忽略打包了,重新構建一下,看一下體積有沒有變化:

可以看到包已經縮減到了1.67M,打包時間縮短了29s(減少了4s),對應的觀察網頁上的顯示結果,moment 包的大小也從 545k 變成了 155.32k,小了很多,不是嗎?

使用 DllPlugin 加快打包速度
在用 Webpack 打包的時候,對於一些不經常更新的第三方庫,比如 react,lodash,vue ,可以將這些庫同項目代碼分離開來,提前打包,從而每次只打包項目自身的代碼,節省了打包時間。常用的方案是使用 DllPlugin。
如何使用 DllPlugin 呢?
首先在 webpack 文件夾下新建 webpack.dll.js文件
配置如下:
const webpack = require('webpack')
const path = require('path')
module.exports = {
entry: {
// manifest 的前綴名,這里會在webpack 文件下生成一個dll.manifest.json 文件
dll: [
'react',
'react-dom',
'antd',
'classnames',
'jquery',
'xlsx',
'mammoth',
'html2canvas',
'dexie',
'cheerio', // 這些都是比較穩定,不常做修改的庫文件
],
},
output: {
// 指定在 dist/static 下生成一個 dll.min.js 文件
path: path.join(__dirname, '../dist/static/'),
filename: '[name].min.js',
library: '[name]',
},
plugins: [
new webpack.DllPlugin({
// 指定在當前文件夾下生成 manifest 文件
path: path.resolve(__dirname, './dll.manifest.json'),
name: '[name]',
context: __dirname,
}),
// 壓縮,讓包更小一點
new webpack.optimize.UglifyJsPlugin({ minimize: true }),
],
}
配置完成后對應的需要在 webpack.config.js 中做如下修改:
const manifest = require('./dll.manifest.json')
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest,
})
]
然后在入口文件中引入 dll.min.js

對應的,為了方便啟動,在 package.json 中添加快捷命令:
"scripts": { "dll": "webpack —config webpack/webpack.dll.js", }
到這里,DllPlugin 的相關配置就完成了,打包的時候執行 npm run dll 會在 webpack 目錄下生成 dll.manifest.json 文件,在 dist/static 目錄下會生成 dll.min.js 文件,在打包過程中, webpack 會將 webpack.dll.js 中配置包含的庫做一個索引,並寫在 dll.manifest.json 文件中,而引用 dll 的代碼在打包的時候,只要讀取這個 manifest 獲取對應的庫就可以了。

生成的 dll.manifest.json 文件

最后執行 npm run build 測試打包速度:

發現現在的打包時間不到19秒,相比於原來的33s減少了將近一半,對應兩個比較大的包體積也各自減少了2/3 還多。所以使用了 DllPlugin 之后,對項目的打包效率的提升還是很明顯的。

總結
項目最開始開始構建,打包后需要將近 4M 的空間,通過手動修改 moment 庫的引入方式和引入 webpack 的 DllPlugin 進行優化,打包后最終體積減少到了 1.2M,壓縮了一半多,對應打包時間也縮短了將近一半,所以通過 webpack 進行打包優化還是很有效果的。這給我的啟發是,在實際開發和打包上線過程中,需要細致地評估項目的構建體積和打包時間,通過 webpack-bundle-analyzer 可以直觀的觀察構建包的構成和體積分布,並且根據分析的結果有針對性地進行優化,以此來精簡項目體積,提升應用效率。當然,打包優化的方式不僅限於此,還可以通過 HappyPack 利用 Node 的多線程充分使用電腦多核來提升構建速度(但是實際效果不一定會變快),此外,還可以使用 webpack 的 externals 不打包某些文件,而在其他地方通過 cdn 引入,利用緩存下載 cnd 文件達到減少打包時間的目的,有興趣可以在項目中嘗試,相信你會有很多收獲。
參考:
使用webpack的插件DllPlugin加快打包速度 - 前端下午茶

