1. 程式人生 > >【javascript】webpack code split && tree shaking

【javascript】webpack code split && tree shaking

webpack打包vue專案之後的檔案太大,本身我們專案的體量也比較大,首次載入太慢。

所以嘗試程式碼分割,對打包之後的app.js進行拆分。

1,動態載入 -(路由懶載入) -(按需載入)

    現在vue專案裡面,有很多路由,一個路由對應著或者多個路由對應著一個元件,如果不進行程式碼拆分,所有的這些元件在打包的時候都會被打包在app.[hash].js裡面,但是很多元件在首次訪問的時候是不需要的。

    我們可以將不同路由對應的元件,打包成不同的檔案,在訪問某一個特定路由的時候,才去載入這個路由所對應元件的js。訪問專案首頁的時候,我們只需要載入index page所需的元件即可。


    比如我們之前的路由載入首頁元件是這樣的:        

import Vue from 'vue' import Router from 'vue-router' import PlainComponent from '@/components/PlainComponent' import Index from '@/components/Index'
Vue. use( Router)
export default new Router({ routes: [ { path: '/', name: 'index', component: Index }, { path: '/plain-component', name: 'PlainComponent'
, component: PlainComponent } ] })

    然後我們加入其他的路由,在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')

   

   為了方便對比,我把普通元件,和動態匯入元件的路由都留到了專案的路由檔案中, 現在路由檔案長這樣:

export default new Router({ routes: [ { path: '/', name: 'index', component: Index }, { path: '/plain-component', name: 'PlainComponent', component: PlainComponent }, { path: '/dynamic-component', name: 'DynamicComponent', 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會自動幫我們去除的。


所以去掉lodash中沒有的程式碼,需要用到額外的外掛:babel-plugin-lodash 

 加入之後看vendor-async.xxx.js的大小


-------------------- 

    

上文提到的兩個例子demo:

    1, webpack-code-splitting

    2,   tree shaking


第二個例子是基於第一個來做的,你可以檢視打包之後的vendor-async.xxx.js的大小對比,然後各自啟一個web server 檢視在載入動態元件的時候,console控制檯裡面關於 _ 的差異