淺談Webpack打包優(yōu)化技巧
前端的打包工具從之前的browserify、grunt、gulp到現(xiàn)如今的rollup、webpack,涌現(xiàn)出了很多優(yōu)秀的打包工具,而目前最火的無疑是webpack,無論是當(dāng)前熱門的框架還是工具庫很多都選擇了它作為打包工具,因此在開發(fā)中webpack作為打包工具是一個很好的選擇。在最近的項目開發(fā)中我也用到了webpack,其中也碰到了不少優(yōu)化方面的問題,這里總結(jié)一下webpack打包優(yōu)化的一些細節(jié)和方法。
首先,這次項目用到的是vue的全家桶,在webpack的配置方面直接用的是 vue-cli 生成的默認配置,項目打包完成后發(fā)現(xiàn)生成的 vendor.js 文件體積特別大,其次打包過程相當(dāng)緩慢,因此想嘗試各種方式對其進行優(yōu)化。
定位體積大的模塊
要想對打包體積進行優(yōu)化,首先得找到體積大的模塊,在這里我們可以使用webpack插件 webpack-bundle-analyzer 來查看整個項目的體積結(jié)構(gòu)對比,它是以treemap的形式展現(xiàn)出來,很形象直觀,還有一些具體的交互形式。既可以查看你項目中用到的所有依賴,也可以直觀看到各個模塊體積在整個項目中的占比。

該插件的使用方法可以直接通過 npm install webpack-bundle-analyzer --save-dev 安裝,并在webpack的配置信息中的 plugins: [new BundleAnalyzerPlugin()] 中添加即可。對于 vue-cli 中的配置方式,默認是安裝了該插件,但是沒有啟用,找到 config/index.js 文件在 build 下面會有 bundleAnalyzerReport 的配置,默認是 process.env.npm_config_report ,這里建議在 package.json 的 scripts 中添加一行 "analyz": "npm_config_report=true npm run build" ,這樣每次想啟用該插件時只需要 npm run analyze 即可。
提取公共模塊
對于webpack,它在模塊化打包上有兩點是其核心功能,一是它支持大量的模塊類型,無論是 TypeScript 、 CoffeeScript 還是 sass 、 stylus 等語言它都支持,二是它可以通過配置來控制打包文件的粒度,這個下面會講到。
在開發(fā)中我們往往會將所有的依賴庫單獨提取出來,而不與我們的項目代碼混在一起,這時我們會用到一個webpack自帶的插件 CommonsChunkPlugin ,從名字上就可以看出它是一個提取公共模塊的插件。從它的文檔中可以看出可以傳入一個對象最為參數(shù),在使用中常用的三個參數(shù)分別為:
- name(names)
- minChunks
- chunks
name 好理解,指的就是最后打包文件的名字,而如果使用的是 names 的話,傳入的必須是一個字符串?dāng)?shù)組。 minChunks 如果傳入的是一個數(shù)字的話,指的是如果該模塊被其他模塊的引用次數(shù)達到了這個數(shù)值的話該模塊就會被打包。如果傳入的是一個函數(shù)的話,其返回值必須是布爾類型來指明這個模塊是否應(yīng)該被打包進公共模塊。而 chunks 則會指定一個字符串?dāng)?shù)組,如果設(shè)置了該參數(shù),則打包的時候只會從其中指定的模塊中提取公共子模塊。
下面通過幾個實例來說明這個插件是如何工作的。
假設(shè)有兩個模塊 chunk1.js 和 chunk2.js 以及兩個項目文件 a.js 和 b.js ,結(jié)構(gòu)大致如下:
// a.js
require('./chunk1');
require('./chunk2');
require('jquery');
// b.js
require('./chunk1');
require('./chunk2');
require('vue');
// webpack配置如下
module.exports = {
entry: {
main: './main.js',
main1: './main1.js',
jquery:["jquery"],
vue:["vue"]
},
output: {
path: __dirname + '/dist',
filename: '[name].js'
},
plugins: [
new CommonsChunkPlugin({
name: ["common","jquery","vue","load"],
minChunks:2
})
] };
最終的打包結(jié)果是: jquery 被打包到 jquery.js , vue 被打包到 vue.js , common.js 打包的是公共模塊(chunk1和chunk2)。使用該插件打包時,會將滿足 minChunks 的模塊打包到 name 數(shù)組的第一個塊里,然后數(shù)組后面的依次打包,首先從 entry 中找,如果沒有則產(chǎn)生一個空塊。 name 數(shù)組中最后一個塊打包的是webpack的runtime代碼,在使用的時候必須先加載該塊。
現(xiàn)在看一看 vue-cli 對于該插件的配置文件:
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// 將node_modules中的依賴模塊全部提取到vendor文件中
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// webpack在使用CommonsChunkPlugin時會生成一段runtime代碼,并且打包進vendor中。
// 這樣即使不改變vendor代碼,每次打包時runtime會變化導(dǎo)致vendor的hash變化,這里
// 把獨立的runtime代碼抽離出來來解決這個問題
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
移除不必要的文件
在項目中我通過方法一定位到幾處體積占用較大的庫,其中一個便是 moment.js 這個日期處理庫。對于一個日期處理的功能,為何這個庫會占用如此大的體積,仔細查看發(fā)現(xiàn)當(dāng)引用這個庫的時候,所有的 locale 文件都被引入,而這些文件甚至在整個庫的體積中占了大部分,因此當(dāng)webpack打包時移除這部分內(nèi)容會讓打包文件的體積有所減小。
webpack自帶的兩個庫可以實現(xiàn)這個功能:
- IgnorePlugin
- ContextReplacementPlugin
IgnorePlugin 的使用方法如下:
// 插件配置
plugins: [
// 忽略moment.js中所有的locale文件
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// 使用方式
const moment = require('moment');
// 引入zh-cn locale文件
require('moment/locale/zh-cn');
moment.locale('zh-cn');
ContextReplacementPlugin 的使用方法如下:
// 插件配置
plugins: [
// 只加載locale zh-cn文件
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
// 使用方式
const moment = require('moment');
moment.locale('zh-cn');
通過以上兩種方式, moment.js 的體積大致能縮減為原來的四分之一。
模塊化引入
在項目中我使用了 lodash 這個很常用的工具庫,然而在代碼定位的時候發(fā)現(xiàn)這個庫也占了不少的體積。仔細想想,我們在使用這類工具庫的時候往往只使用到了其中的很少的一部分功能,但卻把整個庫都引入了。因此這里也可以進一步優(yōu)化,只引用需要的部分。
import {chain, cloneDeep} from 'lodash';
// 可以改寫為
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';
這樣就可以只打包我們需要的部分功能。
通過CDN引用
對于一些必要的庫,但又無法對該庫進行更好的體積優(yōu)化的話,可以嘗試通過外部引入的方式來減小打包文件的體積。采用該方法只需要在cdn站點找到需要引用的庫的外部鏈接,以及對webpack進行簡單配置即可。
// 在html中添加script引用 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
// 這里externals的key指的是使用時需要require的包名,value指的是該庫通過script引入后在全局注冊的變量名
externals: {
jquery: 'jQuery'
}
// 使用方法
require('jquery')
通過 DLLPlugin 和 DLLReferencePlugin 拆分文件
如果項目過大,打包的時間會相當(dāng)?shù)拈L,如果頻繁更新上線則會不斷對代碼進行編譯打包,浪費很多時間。這時我們便可以將那些不常更新的框架和庫(如vue.js等)進行單獨的編譯打包,這樣每次開發(fā)上線就只需要對我們的開發(fā)文件進行編譯打包,這樣可以極大地省去不必要的打包時間。而這種方法需要 DLLPlugin 和 DLLReferencePlugin 兩個插件的配合來完成。
DLLPlugin
在使用這個插件時,我們需要單獨創(chuàng)建一個配置文件,這里命名為 webpack.dll.config.js ,配置如下:
module.exports = {
entry: {
lib: ['vue', 'vuex', 'vue-resource', 'vue-router']
},
output: {
path: path.resolve(__dirname, '../dist', 'dll'),
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath,
library: '[name]_library'
},
plugins: [
new webpack.DefinePlugin({
'process.env': '"production"'
}),
/**
* path: manifest.json輸出文件路徑
* name: dll對象名,跟output.library保持一致
*/
new webpack.DllPlugin({
context: __dirname,
path: path.resolve(__dirname, '../dist/dll', 'lib.manifest.json'),
name: '[name]_library'
})
]
}
這里要注意幾點:
- entry 中寫明所有要單獨打包的模塊
- output 的 library 屬性可以將dll包暴露出來
- DLLPlugin 的配置中, path 指明 manifest.json 文件的生成路徑, name 暴露出dll的函數(shù)名
運行該配置文件便可生成打包文件和 manifest.json 文件。
DLLReferencePlugin
對于該插件的配置,不需要像上面一樣單獨寫配置文件,只需要在生產(chǎn)配置文件中添加如下代碼:
new webpack.DllReferencePlugin({
context: __dirname, // 同dll配置的路徑保持一致
manifest: require('../dist/dll/lib.manifest.json') // manifest的位置
}),
然后運行webpack,發(fā)現(xiàn)打包的速度得到了極大地提升,也不用每次更新代碼的時候重復(fù)編譯打包這些依賴庫了。
其他
對于webpack的打包優(yōu)化我大致就總結(jié)了上面的一些方法,而為了讓頁面更快的加載,有更好的用戶體驗,我們并不只是從打包上優(yōu)化,也可以有其他方面的優(yōu)化,這里我也簡單提一下我使用過的方法。
開啟Gzip壓縮
開啟gzip壓縮可以減少HTTP傳輸?shù)臄?shù)據(jù)量和時間,從而減少客戶端請求的響應(yīng)時間,由于降低了請求時間,頁面的加載速度也會得到提升,會有更快的渲染速度,極大地改善了用戶體驗。由于現(xiàn)在基本上所有的主流瀏覽器都支持Gzip的壓縮方式,只需要對服務(wù)器進行相關(guān)設(shè)置即可,這里就不具體講如何配置服務(wù)器。
壓縮混淆代碼
我們平常也會對代碼進行壓縮混淆,可以通過 UglifyJS 等工具來對js代碼進行壓縮,同時可以去掉不必要的空格、注釋、console信息等,也可以有效的減小代碼體積。
總結(jié)
本文到這里就結(jié)束了,主要是對webpack的打包優(yōu)化部分做了些講解,當(dāng)然能力和時間有限,只研究了部分方法,可能會有其他更多的優(yōu)化方法,無論是從編譯打包的體積還是速度上都能有更好的優(yōu)化。接觸了一段時間的webpack發(fā)現(xiàn)作為一個打包工具實在是過于復(fù)雜,無論從開始的官方文檔還是到新的高級特性,都很難去完全掌握,還得需要自己不斷去實踐去深入研究才行。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于JavaScript實現(xiàn)類似于百度學(xué)術(shù)高級檢索功能
這篇文章主要介紹了基于JavaScript實現(xiàn)類似于百度學(xué)術(shù)高級檢索功能 的相關(guān)資料,需要的朋友可以參考下2016-03-03
基于jsTree的無限級樹JSON數(shù)據(jù)的轉(zhuǎn)換代碼
基于jsTree的無限級樹JSON數(shù)據(jù)的轉(zhuǎn)換代碼,需要的朋友可以參考下。2010-07-07
javascript 使用正則test( )第一次是 true,第二次是false
這篇文章主要介紹了使用正則test( )第一次是 true,第二次是false的相關(guān)資料,需要的朋友可以參考下2017-02-02

