webpack優化之code splitting
作為當前風頭正盛的打包工具,webpack風靡前端界。確實作為引領了一個時代的打包工具,很多方面都帶來了顛覆性的改進,讓我們更加的感受到自動化的快感。不過最為大家詬病的一點就是用起來太難了。
要想愉快的使用,要使用n多的配置項,究其原因在於文檔的不夠詳細、本身默認集成的不足。也不能說這是缺點吧,更多的主動權放給用戶就意味著配置工作量的增加,這裏就不過多探討了。當歷盡千辛萬苦,你的項目跑起來之後,可能會發現有一些不太美好的問題的出現,編譯慢、打包文件大等。那麽,我們還要花些時間來看看怎麽優化相關配置了。 下面一起看下code splitting
code splitting出現的背景
對於前端資源來說,文件體積過大是很影響性能的一項。特別是對於移動端的設備而言簡直是災難。此外對於某些只要特定環境下才需要的代碼,一開始就加載進來顯然也不那麽合理,這就引出了按需加載的概念了。
為了解決這些情況,代碼拆分就應運而生了。代碼拆分故名思意就是將大的文件按不同粒度拆分,以滿足解決生成文件體積過大、按需加載等需求。具體到webpack而言有下面幾種方式來達到我們的目的。
webpack實現代碼拆分的方式
webpack通過下面三種方式來達到以上目的
- Entry Points: 多入口分開打包
- Prevent Duplication:去重,抽離公共模塊和第三方庫
- Dynamic Imports:動態加載
這裏不去扒文檔上的定義了,我們從一個例子中來逐步體會他們不同的作用。
假設我們有這麽個項目,有下面幾個文件
代碼很簡單(示例而已,直接用commonjs的語法來寫了):
//a.js
var react = require(‘react‘)
var tool = require(‘./tool‘)
var b = require(‘./b‘)
function load (){
b()
tool()
console.log(‘全部文件都從一個入口打包‘)
}
load()
//b.js
var react = require(‘react‘)
var tool = require(‘./tool‘)
function b(){
tool()
console.log(‘這是bjs文件‘)
}
module.exports = b;
//tool.js
var react = require(‘react‘)
function tool(){
console.log(‘這是tooljs文件‘)
}
module.exports = tool;
配置很簡單:
var webpack = require(‘webpack‘);module.exports = {
entry: ‘./codesplitting/c1/a.js‘,
output: {
path: __dirname,
filename: ‘/dist/index.js‘
}
//*****
}
直接打包:可以看到文件大小有2047行,體積也變大了
目前只引入了react,並且業務代碼幾乎沒有的情況下。大家可以猜到實際項目中的情況了。來讓我們進行第一優化
Entry Points
如果業務中的項目不是單頁面應用,這一步可以忽略了,直接是多入口打包。這裏是為了演示效果,強行分一個模塊出來打包,假設我們的文件也很大,需要將b.js單獨打個包出來:
entry: {
index:‘./codesplitting/c1/a.js‘,
other:‘./codesplitting/c1/a.js‘
},
output: {
path: path.resolve(__dirname, ‘./dist‘),
filename: ‘[name].js‘
},
//***
這裏a.js也需要修改,去掉對b的引用。入口文件之間不能相互引用的。不然,問題就大了,到底以誰為主呢,這樣就陷入了循環引用的問題。
此時的生成文件如下:
看來文件竟然只小了那麽一點了吧?第一步的優化這裏就完成了,顯然你會認為我在開玩笑。
當然這只是萬裏長征第一步,看一下dist下的文件不難發現兩個文件中都把react這個第三方庫和tool.js這個可復用模塊打進去了,顯然這樣重復打包有點沒必要。
是不是可以把這些復用性強的模塊拿出來單獨打包呢?
這樣瀏覽器第一次請求之後就會將該文件緩存起來,從服務端請求的只有體積縮小之後的業務文件了,這樣的話加載速度顯然會有所提升。
如果你也是這麽想的,來一起繼續看下去。
Prevent Duplication
webpack去除重復引用是通過CommonsChunkPlugin插件來實現的。該插件的配置項如下:
{
//被抽離為公共文件的chunk名,例如common,可以是string或者數組
//顯然如果是單個的模塊,就是name多個就是names
name:string,
names:[],
//打包之後公共模塊的名稱模板
//例如‘[name].js‘
//如果省略,則和name名稱一致
filename:string,
//模塊被引的最小次數,也就是說至少有幾個組件引用了該模塊。
//如果是Infinity,則表明單純的創建,並不做任何事情
minChunks:2
}
具體在webpack中去重對於第三方庫顯示聲明vendor,公共模塊聲明common的方式來處理
entry: {
index:‘./codesplitting/c1/a.js‘,
other:‘./codesplitting/c1/b.js‘,
//第三方庫顯示聲明
vendor:[‘react‘],
//公共組件聲明為common
common:[‘./codesplitting/c1/tool‘]
},
//***
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names:["common", "vendor"],
filename: "[name].js"
})
]
打包結果如下:
可以看到index和other兩個業務包已經很小了,react被抽離到單獨的包中。
這樣還有一個問題,對於某些代碼可能只有在特定條件下才執行,或者可能就不執行。
我不希望在首屏就去加載它,也就是我們常說的按需加載是要怎麽做呢。一起看下去。
Dynamic Imports
webpack建議如下兩種方式使用動態加載。
1)、ECMAScript中出於提案狀態的import()
2)、webpack 特定的 require.ensure
我們這裏就是用第二種來看下效果(畢竟偷懶沒用babel...),在ajs中動態引入di.js
//雖然始終會加載,大家能明白就行
if(true){
require.ensure([],function(require){
var di = require(‘./di‘)
})
}
//新增動態加載的js
function di(){
tool()
console.log(‘這是動態引入的文件‘)
}
module.exports = di;
運行之後可以發現多了個2.2.js,打開可以發現就是我們新建的動態引入的di.js
大家可能會問怎麽確定就是動態引入的呢,雖然本示例只能看打包之後的例子(就不引入dev server了,畢竟是懶。。。)我們依然可以從代碼裏看到結果。
首先、查看index.js文件,可以看到下面的代碼:
var react = __webpack_require__(2)
var tool = __webpack_require__(1)
/****省略8*****/
//雖然始終會加載
if(true){
__webpack_require__.e/* nsure */(2, function(require){
var di = __webpack_require__(13)
})
}
與直接require的模塊不同,require.ensure被轉化為了 webpack_require.e方法,來繼續看一下該方法有什麽用。
__webpack_require__.e = function requireEnsure(chunkId, callback) {
// "0" is the signal for "already loaded"
if(installedChunks[chunkId] === 0)
return callback.call(null, __webpack_require__);
// an array means "currently loading".
if(installedChunks[chunkId] !== undefined) {
installedChunks[chunkId].push(callback);
} else {
// start chunk loading
installedChunks[chunkId] = [callback];
var head = document.getElementsByTagName(‘head‘)[0];
var script = document.createElement(‘script‘);
script.type = ‘text/javascript‘;
script.charset = ‘utf-8‘;
script.async = true;
script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"common","1":"index","3":"other"}[chunkId]||chunkId) + ".js";
head.appendChild(script);
}
};
結合註釋直接從源碼中可以看出來,最後面的條件語句來創建script標簽進而實現動態加載的。所謂動態加載本質還是要創建script標簽來實現的。
結束語
至此代碼分割部分的優化已經完成了,以上是個人關於代碼分割的簡單理解,拋磚引玉,共同學習進步。更多請移步github查看
webpack優化之code splitting