@bornkiller
2017-08-07T02:47:29.000000Z
字数 3236
阅读 3131
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 blockimport { 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 Nodeif (!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 localesgetSetGlobalLocale(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配置键为字符串精确匹配方式:
// 匹配externalsimport moment from 'moment;// 匹配externalsconst moment = require('moment');// 不匹配externalsconst moment = require('moment/moment.js');
那么肯定是某个依赖加载moment时,未使用前两种方式。
定位依赖有两种方案可选:
yarn remove moment,再运行打包,根据报错信息定位
npm ls moment,根据输出信息判断(不推荐)
经过验证,原因在于antd date-picker组件:
// antd/lib/date-picker/locale/zh_CN.jsrequire('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