[关闭]
@bornkiller 2017-08-07T02:47:29.000000Z 字数 3236 阅读 2766

webpack优化系列--精简bundle file

webpack


前言

最近将历史内部管理系统(Angular v1)从gulp + ES5切换到webpack + ES6,但是编译时间增长较快,开发后需要等待较长时间,影响开发效率,不得不进行优化。为方便解释,演示代码非实际代码。

代码样例

  1. /**
  2. * @description - webpack optimize series-1 demo
  3. * @author - huang.jian <hjj491229492@hotmail.com>
  4. */
  5. import React, { Component } from 'react';
  6. import { render } from 'react-dom';
  7. import moment from 'moment';
  8. import { Card, DatePicker } from 'antd';
  9. // reactive block
  10. import { Observable } from 'rxjs/Observable';
  11. import 'rxjs/add/observable/interval';
  12. import 'rxjs/add/operator/scan';
  13. import 'rxjs/add/operator/take';
  14. class Demo extends Component {
  15. constructor(props) {
  16. super(props);
  17. this.state = {
  18. date: moment()
  19. };
  20. }
  21. componentDidMount() {
  22. this.subscription = Observable.interval(1500)
  23. .scan((count) => count + 1, 0)
  24. .take(20)
  25. .subscribe((count) => {
  26. this.setState({
  27. date: moment().add(count, 'day')
  28. });
  29. });
  30. }
  31. componentWillUnmount() {
  32. this.subscription.unsubscribe();
  33. }
  34. render() {
  35. return (
  36. <Card title="Webpack Optimize">
  37. <DatePicker value={this.state.date}/>
  38. </Card>
  39. );
  40. }
  41. }
  42. render(<Demo/>, document.querySelector('#main'));

.babelrc配置如下:

  1. {
  2. "presets": [
  3. [
  4. "env",
  5. {
  6. "modules": false,
  7. "loose": true
  8. }
  9. ],
  10. "stage-3",
  11. "react"
  12. ],
  13. "plugins": [
  14. [
  15. "import",
  16. {
  17. "libraryName": "antd",
  18. "style": "css"
  19. }
  20. ],
  21. "transform-runtime",
  22. "transform-class-properties"
  23. ]
  24. }

webpack部分配置如下:

  1. module.exports = {
  2. externals: {
  3. 'react': 'React',
  4. 'react-dom': 'ReactDOM'
  5. },
  6. };

优化流程

上述代码直接打包后,体积超过1MB,耗时3256ms,在react, react-dom已经外源脚本引入的前提下,显得比较诡异。

image.png-285kB

首先需要明确bundle的文件构成,可以使用命令行参数,也可以使用 webpack-bundle-analyzer 插件。

  1. NODE_ENV=prod webpack --profile --display-modules
  1. {
  2. "plugins": [
  3. new BundleAnalyzerPlugin({
  4. analyzerMode: 'static'
  5. })
  6. ]
  7. }

如图所示,moment将近占据一半,而且加载了所有语言包,缩减bundle size优先处理moment依赖。

wepback-series-2.png-416.6kB

moment源码中加载语言包代码如下:

  1. function loadLocale(name) {
  2. var oldLocale = null;
  3. // TODO: Find a better way to register and load all the locales in Node
  4. if (!locales[name] && (typeof module !== 'undefined') &&
  5. module && module.exports) {
  6. try {
  7. oldLocale = globalLocale._abbr;
  8. require('./locale/' + name);
  9. // because defineLocale currently also sets the global locale, we
  10. // want to undo that for lazy loaded locales
  11. getSetGlobalLocale(oldLocale);
  12. } catch (e) { }
  13. }
  14. return locales[name];
  15. }

require路径存在变量,称之为context modulewebpack会加载'./locale/'目录下所有文件,所以才会有如此多的语言包加载,我们只需要简体中文语言包。搜索之后发现ContextReplacementPlugin插件,添加如下配置:

  1. plugins: [
  2. new ContextReplacementPlugin(
  3. /moment\/locale$/,
  4. /zh-cn/
  5. )
  6. ]

重新打包之后,时间提升约500ms,文件缩小300+kb

webpack-series-4.png-313.7kB

由于moment在其他项目中频繁使用,所以计划将moment外源性脚本方式引入,进一步提升构建性能。

  1. externals: {
  2. 'react': 'React',
  3. 'react-dom': 'ReactDOM',
  4. 'moment': 'moment'
  5. }

再次打包之后,编译时间不仅没有缩短,反而恢复到未优化前的状态。

image.png-285kB

经过查证[1]externals配置键为字符串精确匹配方式:

  1. // 匹配externals
  2. import moment from 'moment;
  3. // 匹配externals
  4. const moment = require('moment');
  5. // 不匹配externals
  6. const moment = require('moment/moment.js');

那么肯定是某个依赖加载moment时,未使用前两种方式。

定位依赖有两种方案可选:

webpack-moment-locate2.png-281.5kB

webpack-moment-locate.png-72.4kB

经过验证,原因在于antd date-picker组件:

  1. // antd/lib/date-picker/locale/zh_CN.js
  2. require('moment/locale/zh-cn');
  1. // moment/locale/zh-cn.js
  2. (function (global, factory) {
  3. typeof exports === 'object' && typeof module !== 'undefined'
  4. && typeof require === 'function' ? factory(require('../moment')) :
  5. typeof define === 'function' && define.amd ? define(['../moment'], factory) :
  6. factory(global.moment)
  7. })

其中../moment并未匹配externals,所以造成moment依旧被打入bundle。解决如下:

  1. externals: {
  2. 'react': 'React',
  3. 'react-dom': 'ReactDOM',
  4. 'moment': 'moment',
  5. '../moment': 'moment'
  6. }

最终bundle size 1090kb --> 623kb,编译时间有波动,编译时间降低600ms左右,在样例中,降低bundle size对编译效率的影响基本到极限,在大型项目中,依旧优先执行此策略,后续篇介绍其它方案。

webpack-series-6.png-264.3kB

联系本人

Mail: hjj491229492@hotmail.com

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注