解决 roadhog 单页应用提取公共模块的问题
前言 roadhog 在打包单页应用文件的时候如果采取异步加载模块文件的话,每个模块里面应用到的一些公用库(antd、moment等)会在每个生成的异步模块文件里面重复应用到,导致所有文件的总体积增加了很多(因为公用库被重复引用了)本文描述的是如何解决这个问题
相关仓库版本
- roadhog: 1.1.1
问题描述
在 cpbs PC 端开发过程中,发现在经过 roadhog build --debug --analyze 命令打包之后的文件体积很大,具体内容如下所示:
Compiled successfully in 25.6s.
File sizes after gzip:
1.56 MB dist\index.js
1.27 MB dist\role.async.js
1.26 MB dist\privilegeAudit.async.js
1.23 MB dist\merchant.async.js
1.23 MB dist\agent.async.js
1.23 MB dist\merchantAudit.async.js
1.23 MB dist\agentAudit.async.js
1.23 MB dist\merchantReview.async.js
1.23 MB dist\agentReview.async.js
1.22 MB dist\user.async.js
1.18 MB dist\privilege.async.js
1.15 MB dist\transactionTotal.async.js
1.14 MB dist\statisticRemit.async.js
1.14 MB dist\statisticContrast.async.js
1.14 MB dist\transactionFlow.async.js
239.48 KB dist\login.async.js
17.2 KB dist\index.css
3.93 KB dist\error.async.js
1.7 KB dist\antd.js
Analyze result is generated at dist/stats.html.
roadhog 将每个 model 都打包为一个单独的异步调用的 js 文件,这些文件即使在 gzip 压缩之后每个都需要平均 1M 的体积,在查看打包的具体内容 (stats.html)之后,发现体积过大的这些包都对一些共同使用到的组件进行了打包,而非引用模块部分的代码体积只有 300k 左右:

经过分析,发现体积过大的原因有以下几点:
moment模块被重复引用moment模块引用了多余的本地化文件(约300k)antd重复引用到的模块,如Table、Button、Form等被各个模块重复打包,增加了体积
解决思路
- 将体积大的公用的模块进行打包(
moment、antd部分使用频率高的模块) - 移除
moment模块没有使用到的语言包
本文只讲述如何将公共模块抽离出来作为一个单独文件
解决过程
使用 CommonsChunkPlugin 将公用模块抽离到 ventor.js
修改 roadhog 的配置文件:
// .roadhogrc.js
...
"entry": "./src/index.js",
...
在根目录添加 webpack.config.js,输入以下内容:
const webpack = require('webpack')
module.exports = function wp(webpackConfig, env) {
// 对roadhog默认配置进行操作,比如:
if (env === 'production') {
// 上线环境使用分包打包方式
webpackConfig.entry = {
index: './src/index.js',
vendor: [
'moment',
'mockjs',
'lodash',
'react',
'react-dom',
'react-helmet',
],
}
// webpackConfig.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/))
webpackConfig.plugins.push(new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'],
minChunks: Infinity,
}))
}
return webpackConfig
}
至此,完成了将除 antd 之外的公用模块的抽离
使用 CommonsChunkPlugin 将 antd 的高频模块抽离到 antd.js
修改 webpack.config.js为:
const webpack = require('webpack')
module.exports = function wp(webpackConfig, env) {
// 对roadhog默认配置进行操作,比如:
if (env === 'production') {
// 上线环境使用分包打包方式
webpackConfig.entry = {
index: './src/index.js',
vendor: [
'moment',
'mockjs',
'lodash',
'react',
'react-dom',
'react-helmet',
],
antd: [
'antd/lib/button',
'antd/lib/icon',
'antd/lib/table',
'antd/lib/date-picker',
'antd/lib/form',
'antd/lib/modal',
'antd/lib/grid',
'antd/lib/input',
],
}
// webpackConfig.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/))
webpackConfig.plugins.push(new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'antd'],
minChunks: Infinity,
}))
}
return webpackConfig
}
修改 index.html 为 index.ejs
将 /src 目录下的文件进行如下调整:
|-index.ejs
|-index.js
|-index.less
|-router.js
各个文件的配置为:
<!-- index.ejs -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<body>
<div id="root"></div>
</body>
</html>
import 'babel-polyfill'
import dva from 'dva'
import createHistory from 'history/createBrowserHistory'
import createLoading from 'dva-loading'
import { message } from 'antd'
import moment from 'moment'
import 'moment/locale/zh-cn'
import './index.less'
import './styles/lib/animate.css' // 引入全局样式动画库
import './themes/index.less'
moment.locale('zh-cn')
const ERROR_MSG_DURATION = 3 // 3 秒
// 1. Initialize
const app = dva({
history: createHistory(),
onError(error) {
console.error(error)
message.error(error.message, ERROR_MSG_DURATION)
},
})
// 2. Plugins
app.use(createLoading({ effects: true }))
// 3. Model
// 4. Router
app.router(require('./router'))
// 5. Start
app.start('#root')
index.less 和 router.js 的内容这里不赘述
经过以上配置之后,index.ejs 能够根据 roadhog 里面的 entry 配置自动引入所有入口文件。
注意 CommonsChunkPlugin 配置里面的 name 的数组顺序会影响到 entry 的引用顺序,如果出现 webpack 未定义的错误,尝试改变这个属性值的顺序,因为 CommonsChunkPlugin 是按照抽离顺序将 webpack 的全局对象放到了 entry 的最后一个入口文件里面。
解决成果
经过上面的处理之后,打包的个文件体积为:
Compiled successfully in 21.1s.
File sizes after gzip:
1.41 MB dist\antd.js
596.22 KB dist\index.js
556.53 KB dist\role.async.js
549.17 KB dist\privilegeAudit.async.js
526.47 KB dist\merchant.async.js
524.27 KB dist\agent.async.js
520.04 KB dist\merchantAudit.async.js
519.58 KB dist\agentAudit.async.js
516.8 KB dist\merchantReview.async.js
516.34 KB dist\agentReview.async.js
508.46 KB dist\user.async.js
474.88 KB dist\privilege.async.js
443.19 KB dist\transactionTotal.async.js
434.33 KB dist\statisticRemit.async.js
433.45 KB dist\statisticContrast.async.js
429.59 KB dist\transactionFlow.async.js
279.46 KB dist\vendor.js
112.09 KB dist\login.async.js
17.2 KB dist\index.css
3.92 KB dist\error.async.js
Analyze result is generated at dist/stats.html.
可以看到被抽离了公用模块之后的各模块的体积基本减少到了 500k(具体抽离哪些模块还可以再精准研究),代码包的整体面积无疑是减少了很多的。
后续报道
发现在使用多入口方式的时候无法触发热更新,所以在开发环境之下还是使用旧有的打包方式,只在线上环境使用抽离式打包,解决方式就是在 webpack.config.js 里面添加一个环境变量判断,详细见上面代码。
const webpack = require('webpack')
module.exports = function wp(webpackConfig, env) {
// 对roadhog默认配置进行操作,比如:
if (env === 'production') {
// ...do what you want
}
return webpackConfig
}
资料来源
@Runtu4378 @sorrycc你好,我按这个配置生成了vendor.js和antd.js,但是其它打包出来的文件大小并没有变化,这个是bug吗?
@SmithXiong 有可复现的在线 demo 吗,这篇博文写时 roadhog 的版本是 1.1.1
2.x.x 版本的 roadhog 有内置 commons 的配置 你试下弃用 webpack.config.js 这个文件,结合下面的配置来查看有没有如愿提取到公用文件
https://github.com/sorrycc/roadhog#commons https://github.com/sorrycc/roadhog/issues/577
@Runtu4378 谢谢了,commons配置确实可以分离出来,原来打包的文件小了一些。
@Runtu4378 谢谢了,commons配置确实可以分离出来,原来打包的文件小了一些。
请问你是怎么配置commons的,我也在研究这个问题,没整出来啊