代码分割与懒加载情况下(code-splitting+lazyload)抽离懒加载模块的公用模块代码

前言

我们清楚,在 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 首页路由文件

// page/index/Index.vue 用户页路由文件

上述使用了vue-router懒加载打包出来的 首页路由文件index.js 和 用户页文件userIndex.js 都会包含一份 public.js的代码,重复了。

那么问题就是,在代码分割的代码中,怎么自动抽离公共代码? 就像CommonsChunkPlugin的效果一样,CommonsChunkPlugin怎么在 code-splitting 的场景上使用呢 ?

解决方案

如问题所示,存在两个使用了webpack code-splitting 和 懒加载的路由文件,路由文件都使用了公用的public.js模块。

// page/index/Index.vue 首页路由文件

// 用户页
// page/index/Index.vue 用户页路由文件

要将 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(".."))

你可能感兴趣的:(构建工具,前端,node.js,webpack,javascript)