webpack的CommonsChunkPlugin分析与优化

4

前言

在前端工程的的打包史中,common文件向来都不是一个好处理的方面。在这一块,webpack提供了CommonsChunkPlugin来处理这个事情,但是在由于文档的模棱两可,再加上各种配置选项的多样性和某些bug,还是有不少坑的。

分析包

所谓工欲善其事必先利其器,我们既然想做common方面的优化,那么首先肯定要知道打包后的文件体积庞大的主要原因。说到这里就不得不提到一个相当好用的工具:webpack-bundle-analyzer

它既是一个webpack插件,又是一个命令行工具。能够将webpack包的内容转换成可缩放的树状图,方便进行交互分析。恩。。。就是这玩意:
image

安装

npm install --save-dev webpack-bundle-analyzer

作为插件使用

webpack.config.js中:

var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

// ...
plugins: [new BundleAnalyzerPlugin()]
// ...

默认配置如下:

new BundleAnalyzerPlugin({
  // Can be `server`, `static` or `disabled`.
  // In `server` mode analyzer will start HTTP server to show bundle report.
  // In `static` mode single HTML file with bundle report will be generated.
  // In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`.
  analyzerMode: 'server',
  // Host that will be used in `server` mode to start HTTP server.
  analyzerHost: '127.0.0.1',
  // Port that will be used in `server` mode to start HTTP server.
  analyzerPort: 8888,
  // Path to bundle report file that will be generated in `static` mode.
  // Relative to bundles output directory.
  reportFilename: 'report.html',
  // Automatically open report in default browser
  openAnalyzer: true,
  // If `true`, Webpack Stats JSON file will be generated in bundles output directory
  generateStatsFile: false,
  // Name of Webpack Stats JSON file that will be generated if `generateStatsFile` is `true`.
  // Relative to bundles output directory.
  statsFilename: 'stats.json',
  // Options for `stats.toJson()` method.
  // For example you can exclude sources of your modules from stats file with `source: false` option.
  // See more options here: https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
  statsOptions: null,
  // Log level. Can be 'info', 'warn', 'error' or 'silent'.
  logLevel: 'info'
})

命令行使用

第一步:

webpack --profile --json > stats.json

第二步:

webpack --profile --json | Out-file 'stats.json' -Encoding OEM

执行成功后,你将看到以下动态网页:

图片描述

在这里顺便放上线上文件加载waterfall,作为对比。
图片描述

问题

通过图表可以看到,在以下配置下:

config.plugins.push(new CommonsChunkPlugin('commons', 'js/commons.[hash].bundle.js'));

打包出来的文件还是有很多问题的:

  • common包规划不合理, swiper.js ,area.json等公用文件大量重复加载。

  • antd 没有抽离出来,无法并行加载,也无法进一步做运行时按需加载。

  • echarts在每个使用的包都单独打包一份,只要包含echarts的包,基本一百多kb。(线上压缩并开启gzip)

  • import {ImgBigSwiper} from 'components/src/index'; 这种写法会导致将components里面的所有组件全部打包进页面的js。应该这样写:import ImgBigSwiper from 'components/src/ImgBigSwiper';挨个引入,见webpack将ES6编译成CommonJs后只引入用到的模块

  • common.js独占490kb,要等这个包加载完后index才开始解析路由。

在这个过程中,会发现一个有趣的事情。就是index.html页面的script加载分为以下两个部分:

......
<script type="text/javascript" src="//res.dinghuo123.com/src/common/ueditor/ueditor.config.js"></script>
<script type="text/javascript" src="//res.dinghuo123.com/src/common/ueditor/ueditor.config.js"></script>
<script type="text/javascript" src="//res.dinghuo123.com/src/common/ueditor/ueditor.config.js"></script>
......
<script>
    document.write('<script src="https://resource.dinghuo123.com/dist/ydhv2/webpack.assets.js?v=' + Math.random() + '"><\/script>');
    document.write('<script src="' + window.WEBPACK_ASSETS['commons'].js + '"><\/script>');
    document.write('<script src="//res.dinghuo123.com/src/common/ueditor/ueditor.config.js"><\/script>');
    document.write('<script src="' + window.WEBPACK_ASSETS['index'].js + '"><\/script>');
</script>

然后你会发现,是上面一块的script并行加载完,才并行加载下一个script标签的内容。大家可以思考一下为什么。

改进配置

进过调整之后的CommonsChunkPlugin配置:

config.plugins.push(new CommonsChunkPlugin({
  name: 'commons',
  minChunks: Infinity // 随着 入口chunk 越来越多,这个配置保证没其它的模块会打包进 公共chunk
}));

config.plugins.push(new CommonsChunkPlugin({
  async:'antd',
  minChunks(module) {
    var context = module.context;
    return context && context.indexOf('antd/dist') >= 0;
  }
}));

config.plugins.push(new CommonsChunkPlugin({
  async:'echarts',
  minChunks(module) {
    var context = module.context;
    return context && (context.indexOf('echarts') >= 0 || context.indexOf('zrender') >= 0);
  }
}));

这里用到了minChunks和async两个配置。

minChunks

其中第一name的commons是一个entry入口,里面是一个依赖包的数组。minChunks设置为Infinity这个配置保证没其它的模块会打包进 公共chunk。因为说实话,CommonsChunkPlugin的commons分析实在是不怎么只能,还是手动控制会更好一些。
当然,你可以传入一个 function ,以添加定制的逻辑(默认是 chunk 的数量),这个函数会被 CommonsChunkPlugin 插件回调,并且调用函数时会传入 module 和 count 参数。
module 参数代表每个 chunks 里的模块,这些 chunks 是你通过 name/names 参数传入的。

  • module.context: The directory that stores the file. For example: '/my_project/node_modules/example-dependency'

  • module.resource: The name of the file being processed. For example: '/my_project/node_modules/example-dependency/index.js'

  • count 参数表示 module 被使用的 chunk 数量。

当你想要对 CommonsChunk 如何决定模块被打包到哪里的算法有更为细致的控制, 这个配置就会非常有用。

async

下面的内容是官网弄过来的,其实我也看不太懂。。。

如果设置为 true,一个异步的 公共chunk 会作为 options.name 的子模块,和 options.chunks 的兄弟模块被创建。它会与 options.chunks 并行被加载。可以通过提供想要的字符串,而不是 true 来对输出的文件进行更换名称。

结果

还是先看打包分析的结果吧:
图片描述

通过上面分析可以看到:

  1. common合理划分,抓大放小。

  2. antd和echarts提取出来,并行加载。

  3. components 用到什么打包什么。(手动控制的)

  4. 大大减小了其他业务包的体积,93%的业务包大小控制在25K以内,剩下7%的业务包大小控制在50k以内。(开启gzip)

  5. 首屏加载资源的总大小几乎没有变化。

接下来的方向

  1. echarts 和Ueditor运行时按需加载。

  2. tree-shaking的探索

参考

Vendor and code splitting in webpack 2
webpack 按需打包加载
weboack Split app and vendor code
awesome-webpack-cn


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

yangholmes · 2017年07月21日

请教一下LZ,webpack什么版本,操作系统什么版本。我 webpack 3.x+ 和 Windows 10 ,执行webpack --profile --json > stats.json指令收到错误:

DeprecationWarning: loaderUtils.parseQuery() received a non-string value which can be problematic

回复

0
yangholmes · 2017年07月21日
载入中...