前言
我们清楚,在 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.js
的CommonsChunkPlugin
插件指定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
的代码。
这就需要CommonsChunkPlugin
的 async
属性。
方案三(最佳实践),children
与 async
双管齐下
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(“..”))