@bornkiller
2017-08-07T10:47:29.000000Z
字数 3236
阅读 2885
webpack
最近将历史内部管理系统(Angular v1
)从gulp + ES5
切换到webpack + ES6
,但是编译时间增长较快,开发后需要等待较长时间,影响开发效率,不得不进行优化。为方便解释,演示代码非实际代码。
/**
* @description - webpack optimize series-1 demo
* @author - huang.jian <hjj491229492@hotmail.com>
*/
import React, { Component } from 'react';
import { render } from 'react-dom';
import moment from 'moment';
import { Card, DatePicker } from 'antd';
// reactive block
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/scan';
import 'rxjs/add/operator/take';
class Demo extends Component {
constructor(props) {
super(props);
this.state = {
date: moment()
};
}
componentDidMount() {
this.subscription = Observable.interval(1500)
.scan((count) => count + 1, 0)
.take(20)
.subscribe((count) => {
this.setState({
date: moment().add(count, 'day')
});
});
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return (
<Card title="Webpack Optimize">
<DatePicker value={this.state.date}/>
</Card>
);
}
}
render(<Demo/>, document.querySelector('#main'));
.babelrc
配置如下:
{
"presets": [
[
"env",
{
"modules": false,
"loose": true
}
],
"stage-3",
"react"
],
"plugins": [
[
"import",
{
"libraryName": "antd",
"style": "css"
}
],
"transform-runtime",
"transform-class-properties"
]
}
webpack
部分配置如下:
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
};
上述代码直接打包后,体积超过1MB
,耗时3256ms
,在react
, react-dom
已经外源脚本引入的前提下,显得比较诡异。
首先需要明确bundle
的文件构成,可以使用命令行参数,也可以使用 webpack-bundle-analyzer 插件。
NODE_ENV=prod webpack --profile --display-modules
{
"plugins": [
new BundleAnalyzerPlugin({
analyzerMode: 'static'
})
]
}
如图所示,moment
将近占据一半,而且加载了所有语言包,缩减bundle size
优先处理moment
依赖。
moment
源码中加载语言包代码如下:
function loadLocale(name) {
var oldLocale = null;
// TODO: Find a better way to register and load all the locales in Node
if (!locales[name] && (typeof module !== 'undefined') &&
module && module.exports) {
try {
oldLocale = globalLocale._abbr;
require('./locale/' + name);
// because defineLocale currently also sets the global locale, we
// want to undo that for lazy loaded locales
getSetGlobalLocale(oldLocale);
} catch (e) { }
}
return locales[name];
}
require
路径存在变量,称之为context module
,webpack
会加载'./locale/'
目录下所有文件,所以才会有如此多的语言包加载,我们只需要简体中文语言包。搜索之后发现ContextReplacementPlugin插件,添加如下配置:
plugins: [
new ContextReplacementPlugin(
/moment\/locale$/,
/zh-cn/
)
]
重新打包之后,时间提升约500ms
,文件缩小300+kb
。
由于moment
在其他项目中频繁使用,所以计划将moment
外源性脚本方式引入,进一步提升构建性能。
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'moment': 'moment'
}
再次打包之后,编译时间不仅没有缩短,反而恢复到未优化前的状态。
经过查证[1],externals
配置键为字符串精确匹配方式:
// 匹配externals
import moment from 'moment;
// 匹配externals
const moment = require('moment');
// 不匹配externals
const moment = require('moment/moment.js');
那么肯定是某个依赖加载moment
时,未使用前两种方式。
定位依赖有两种方案可选:
yarn remove moment
,再运行打包,根据报错信息定位npm ls moment
,根据输出信息判断(不推荐)经过验证,原因在于antd date-picker
组件:
// antd/lib/date-picker/locale/zh_CN.js
require('moment/locale/zh-cn');
// moment/locale/zh-cn.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
})
其中../moment
并未匹配externals
,所以造成moment
依旧被打入bundle
。解决如下:
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'moment': 'moment',
'../moment': 'moment'
}
最终bundle size 1090kb --> 623kb
,编译时间有波动,编译时间降低600ms
左右,在样例中,降低bundle size
对编译效率的影响基本到极限,在大型项目中,依旧优先执行此策略,后续篇介绍其它方案。
Mail: hjj491229492@hotmail.com