程式碼分割與懶載入情況下(code-splitting+lazyload)抽離懶載入模組的公用模組程式碼

微雨微語發表於2019-02-16

前言

我們清楚,在 webpack 中通過CommonsChunkPlugin 可以將 entry 的入口檔案中引用多次的檔案抽離打包成一個公用檔案,從而減少程式碼重複冗餘

    entry: {
        main: `./src/main.js`,
        user: `./src/user.js`
    },
    ......
    new webpack.optimize.CommonsChunkPlugin({
        name: "commons",
        filename: `common.js`,
        minChunks: 2,
    })
    
    // 打包生成一個檔案common.js ,包含main.js 和 user.js 中引用兩次及以上的模組程式碼

那麼問題來了,當使用了類似 vue-router 的程式碼分割+懶載入功能的時候,每個路由對應的.vue檔案中,共同引用了多次的模組,要怎麼抽離出程式碼分割模組的公用模組程式碼出來呢?

問題實際場景

舉個例子

// 懶載入路由檔案 routes.js
const
    Index = () => import(/* webpackChunkName: "index" */ "page/index/Index.vue"),
    User = () => import(/* webpackChunkName: "userIndex" */ "page/user/Index.vue"),
    UserDetail = () => import(/* webpackChunkName: "userDetail" */ "page/user/Detail.vue"),
    ...
// page/index/Index.vue 首頁路由檔案
<template>首頁</template>
<script>
    import pub from `script/public.js`
    ...
</script>
// page/index/Index.vue 使用者頁路由檔案
<template>使用者頁</template>
<script>
    import pub from `script/public.js`
    ...
</script>

上述使用了vue-router懶載入打包出來的 首頁路由檔案index.js 和 使用者頁檔案userIndex.js 都會包含一份 public.js的程式碼,重複了。

那麼問題就是,在程式碼分割的程式碼中,怎麼自動抽離公共程式碼? 就像CommonsChunkPlugin的效果一樣,CommonsChunkPlugin怎麼在 code-splitting 的場景上使用呢 ?

解決方案

如問題所示,存在兩個使用了webpack code-splitting 和 懶載入的路由檔案,路由檔案都使用了公用的public.js模組。

// page/index/Index.vue 首頁路由檔案
<template>首頁</template>
<script>
    import pub from `script/public`
    ...
</script>
// 使用者頁
// page/index/Index.vue 使用者頁路由檔案
<template>使用者頁</template>
<script>
    import pub from `script/public`
    ...
</script>

要將 public.js公用模組抽離,有三種解決方案

方案一,CommonsChunkPlugin 具名模組

手動將所有共用的模組抽離在一個檔案。
建立檔案commons.js

// commons.js
import pub from `public`

webpack.config.jsCommonsChunkPlugin外掛指定commons 的entry

// webpack.config.js
entry:{
    main: `src/main.js`,
    commons: `src/commons.js`
},
...
    new webpack.optimize.CommonsChunkPlugin({
        name: "commons",   // 和 entry的commons對應,
        filename: `common.bundle.js`, // 抽離公共檔案
        minChunks: Infinity,
    })

這樣,如果路由檔案或其他模組使用到了 commons.js中的模組,都不會重複載入程式碼,而是在common.bundle.js中獲取。

方案二,CommonsChunkPlugin 設定 children 屬性

官方文件CommonsChunkPlugin 中 children屬性解釋

Move common modules into the parent chunk

With Code Splitting, multiple child chunks of an entry chunk can have common dependencies. To prevent duplication these can be moved into the parent. This reduces overall size, but does have a negative effect on the initial load time. If it is expected that users will need to download many sibling chunks, i.e. children of the entry chunk, then this should improve load time overall.

可知,設定 children 為 true 可以將code-splitting的模組的依賴模組抽離到父模組,這樣做的後果就是,確實抽離公用模組,降低了程式碼重複,減少了程式碼體積。但是同時,抽離到父模組,也意味著如果有一個懶載入的路由 ShopList.vue 沒有用到public.js 模組,但是實際上引入了父模組,也為這ShopList.vue也引入了public.js的程式碼。

這就需要CommonsChunkPluginasync 屬性。

方案三(最佳實踐),childrenasync 雙管齊下

Extra async commons chunk

Similar to the above one, but instead of moving common modules into the parent (which increases initial load time) a new async-loaded additional commons chunk is used. This is automatically downloaded in parallel when the additional chunk is downloaded.

設定了async, 會將上述懶載入的路由檔案公用的模組程式碼,抽離打包成一個單獨的檔案,並且該檔案是按需載入的,如果某個路由沒有使用到這些公用模組,是不會載入進來的。

舉個例子:
首頁路由模組(訪問路徑/index),引用了 public模組
使用者路由模組(訪問路徑/user),引用了 public模組
購物車模組(訪問路徑/shop),沒有引用 public模組

那麼,打包生成的檔案大概是

main.js - 根入口檔案
index.js - 首頁路由檔案
user.js - 使用者路由檔案
shop.js - 購物車路由檔案
0.js - 抽離路由的公用模組檔案

訪問url/index,載入的依賴檔案是main.js + index.js + 0.js
訪問url/user,載入的依賴檔案是main.js + user.js + 0.js
訪問url/shop,載入的依賴檔案是main.js + shop.js
基本解決了 lazy load + code-splitting 情況下的公用模組抽離。

以下附上簡單的webpack.config.js配置程式碼

entry: {
    main: `./src/main.js`
},
...
plugins: [
    ...
    new webpack.optimize.CommonsChunkPlugin({
        name: "main",
        minChunks: 2,
        children: true,
        // deepChildren: true,
        async: true,
    })
]

The CommonsChunkPlugin has been removed in webpack v4 legato. To learn how chunks are treated in the latest version, check out the SplitChunksPlugin.

PS: webpack 4 已經將CommonsChunkPlugin廢棄,解決方案僅能在webpack 3 以下使用。

參考資料

commons-chunk-plugin
CommonChunkPlugin: Feature – Select statically imported modules from chunks that were created from a dynamic import (require.ensure / System.import / import(“..”))

相關文章