【javascript】webpack code split && tree shaking
webpack打包vue專案之後的檔案太大,本身我們專案的體量也比較大,首次載入太慢。
所以嘗試程式碼分割,對打包之後的app.js進行拆分。
現在vue專案裡面,有很多路由,一個路由對應著或者多個路由對應著一個元件,如果不進行程式碼拆分,所有的這些元件在打包的時候都會被打包在app.[hash].js裡面,但是很多元件在首次訪問的時候是不需要的。
我們可以將不同路由對應的元件,打包成不同的檔案,在訪問某一個特定路由的時候,才去載入這個路由所對應元件的js。訪問專案首頁的時候,我們只需要載入index page所需的元件即可。
比如我們之前的路由載入首頁元件是這樣的:
export default new Router({ routes: [ { path: '/', name: 'index', component: Index }, { path: '/plain-component'
然後我們加入其他的路由,在routes節點增加新的item,比如有新的需求,元件名為PlainComponent,是訪問路由/plain去訪問然後載入的。在webpack.prod.config.js裡面,將 UglifyJsPlugin暫時先註釋,關閉sourceMap,跑npm run build後,檢視打包檔案,你會發現 PlainComponent的內容被打包到了app.js裡面。這不是我們需要的,我們首頁訪問的時候 不需要載入這個元件。
這時候我們可以將這個元件設定為
我們動態引匯入此元件,webpack會自動將該元件打包成一個chunk檔案,可以手動命名,命名方式長這樣:
/*webpackChunkName: "your-chunk-name"*/
component: () => import(/* webpackChunkName: "DynamicComponent" */'../components/DynamicComponent') 為了方便對比,我把普通元件,和動態匯入元件的路由都留到了專案的路由檔案中, 現在路由檔案長這樣:
tip-1: 到這一步,我們已經拆分了app.js,但是你可能會有疑問,如果路由裡面多個路由對應一個元件,比如/dynamic-component-2 也會對應使用元件DynamicComponent元件【實際業務也會遇到】,這時候我們仍然可以使用動態匯入,無論是否使用同樣的webpackChunkName。這個元件只會被打包一次,最後只會生成一個chunk檔案,如果name不一樣以第一次命名為主。
tip-2: 隨著動態匯入的元件越多,各個元件裡面的引入的其他模組可能會被重複打包,比如說上面除了第一個動態匯入的DynamicComponent元件,還有另外的動態匯入的元件DynamicComponentTwo 和 DynamicComponentThree,都使用到了lodash的omit方法。打包之後,檢視對應的三個js檔案,你會發現omit被匯入了三次,這樣也就重複了三次。所以我們需要將這三個chunk的公用部分給提取出來,這時候需要用到 CommonsChunkPlugin。【程式碼請檢視後面的demo連線】
現在我們的路由檔案長這樣:
export default new Router({ routes: [ { path: '/', name: 'index', component: Index // component: require('../components/Index').default }, { path: '/plain-component', name: 'PlainComponent', component: PlainComponent }, { path: '/dynamic-component', name: 'DynamicComponent', component: () => import( /* webpackChunkName: "DynamicComponent" */ '../components/DynamicComponent') }, { path: '/dynamic-component-two', name: 'DynamicComponentTwo', component: () => import( /* webpackChunkName: "DynamicComponentTwo" */ '../components/DynamicComponentTwo' ) }, { path: '/dynamic-component-three', name: 'DynamicComponentThree', component: () => import( /* webpackChunkName: "DynamicComponentThree" */ '../components/DynamicComponentThree' ) } ]})2, CommonsChunkPlugin
commonsChunksPlugin可以幫助我們,抽取多個chunk的公共部分,正如上面我們的例子,三個動態匯入的chunk,都用到了lodash的omit方法,我們需要抽取出來。
這時候我們在webpack.prod.config.js檔案裡面plugins新增一個item:
new webpack.optimize.CommonsChunkPlugin({ name: 'app', async: "vendor-async", minChunks: 3 })我們先明白這個CommonsChunkPlugin的引數是什麼意思。
name首先分為兩種,第一種是屬性值是已知的chunk塊的名稱,這裡是app,說明我們要對app的子模組進行公共程式碼的抽取。【如果使用了新的name,則表示生成新的chunk檔案,在本例的原始碼中嘗試會有問題,因為三個子模組都是動態匯入的,若是三個非動態引入name + chunks(所有非動態引入元件)+ minChunks是可以執行的 】
async是抽取非同步chunk的意思,【和filename是衝突的,設定filename則會生成以filename屬性命名的chunk,但是是同步載入,當載入首頁的時候,會同時載入以filename命名的js檔案】,非同步的意思是載入首頁,我們不需要載入這一塊公共程式碼,只有某些特定的模組使用了這個chunk的資料時,載入這個特定的模組才會去載入這個非同步chunk。
minChunks這裡設定為3,表示至少3個模組以上出現的同樣的程式碼,才會將程式碼提取出來生成common chunk。
我們沒有抽取非同步公共元件之前,三個動態匯入的元件打包後是這樣的:
然後我們跑構建命令,然後檢視會多出來一個 vendor-async.xxx.js的檔案,這個檔案裡面就是三個動態引入的元件公共的程式碼,也就是lodash部分,然後再看三個檔案的大小,由70多k變成了1k以下:
3, tree shaking
到這一步就完成了嗎?完成了大部分的壓縮拆分檔案的效果了,但是開啟vendor-async.xxx.js檔案,你會發現,lodash的所有方法都被載入進了這個檔案 ,並且在訪問動態路由的時候,我們直接在chrome 的console裡面直接輸入_.isNil,或者其他lodash方法都會被打印出來,uglifyJS沒有移除那些沒有使用的方法,明明我們專案使用的只有一個omit方法,也就是說理想的情況是vendor-async 這個chunk不應該那麼大,所以我們應該考慮tree shaking 【移除沒有使用的程式碼】。 因為lodash沒有使用ES6的模組化語法,tree shaking的前提是webpack處理的程式碼,如果採用了ES6模組化語法,uglifyJS會自動幫我們去除的。
加入之後看vendor-async.xxx.js的大小
--------------------
上文提到的兩個例子demo:
第二個例子是基於第一個來做的,你可以檢視打包之後的vendor-async.xxx.js的大小對比,然後各自啟一個web server 檢視在載入動態元件的時候,console控制檯裡面關於 _ 的差異