webpack4配置优化

先说一下我在升级中发现的可以优化的点,大家有什么建议和想法可以一并提出。

1.1 优化第三方库

优化第三方库最简单粗暴并且及其有效的一个方式就是使用webpack的DllPlugin。它可以将我们经常使用但是修改频率极低的第三方库与自己的代码完全分离开, 每次打包的时候会根据索引判断是否需要重新打包第三方库,大大提高了打包速度。用法我就不细说了,网上有很多教程,感兴趣的可以去了解下。

库的索引一般保存在一个manifest.json文件中。我们可以通过这个文件看到自己项目对第三方库的打包情况。

优化思路:

  • 项目里安装的第三方库的需要评估必要性,如果很少使用到库的不要在webpackvendor入口指定打包进来。
  • 同时可以升级某些第三方库,如react v16对比react v15,加上react-dom,体积上降低了30%,因此果断升级。
  • 分析manifest.json文件后发现某些库体积特别大,例如使用很广泛的moment.js库。研究后发现webpack把库下面所有的locale文件打包了进来。这是一些支持多语言的文件。即我们可以通过ContextReplacementPlugin插件来挑选我们需要的文件。
  1. plugins: [

  2. new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),

  3. ]

  4. 复制代码

  • 有时候通过依赖分析会发现项目里打包了两份第三方库,一份是es module版的,另一份则是umd版的。如moment通过import 'moment'导入的是es module版的,locale文件中却会用相对路径导入umd版的。这时我们可以通过配置别名将库指向同一个版本。
  1. resolve: {

  2. alias: {

  3. 'moment$': path.resolve('node_modules/moment/moment'),

  4. },

  5. }

  6. 复制代码

  • 有时候项目里并不需要某个库的全部功能,这时候可以考虑用更轻量的库来替代,或者使用定制阉割版(如Echarts, 官网上可定制),并且用import()语法和react-loadable这个库将react组件包装成异步组件,在需要时才进行加载,这样webpack就会将异步组件抽取为异步加载的chunk,就不会多次加载。下面就说如何抽取。

1.2 抽取异步加载的chunk中的公共代码

在webpack4之前,我们使用CommonsChunkPlugin来抽取异步组件,但现在CommonsChunkPlugin已经被废弃了,取而代之的是SplitChunksPlugin

使用方法有两种,一种是像Webpack3那样在plugins中配置,一种是在Webpack4中的optimization属性中的splitChunks配置

  1. optimization: {

  2. splitChunks: { // 拆分代码规则(符合即单独拆分到一个chunk中)

  3. maxInitialRequests: 4, // 在初始化加载时,请求数量大于4

  4. automaticNameDelimiter: '-',

  5. name: true, // 代码块的名字,设置为true则表示根据模块和缓存组秘钥自动生成, 实现固化 chunkId,保持缓存的能力

  6. /* 缓存组,用于继续细分代码。

  7. 缓存组默认将node_modules中的模块拆分带一个叫做vendors的代码块, 将最少重复引用两次的模块放入default中。

  8. 或者自定义将符合规则的模块单独拆分进一个chunk中。*/

  9. cacheGroups: {

  10. default: false, // 禁用默认规则

  11. vendors: false, // 禁用默认规则

  12. polyfill: {

  13. chunks: 'initial',

  14. test: 'polyfill',

  15. name: 'polyfill',

  16. },

  17. vendor: {

  18. chunks: 'initial',

  19. test: 'vendor',

  20. name: 'vendor',

  21. },

  22. // 对异步组件进行抽取

  23. 'async-vendor': {

  24. name: 'async-vendor',

  25. chunks: 'async',

  26. test: (module) => { // 缓存组的规则,表示符合条件的的放入当前缓存组

  27. if (/[\\/]node_modules[\\/](echarts|zrender)/.test(module.context)) {

  28. return false;

  29. }

  30. return /[\\/]node_modules[\\/]/.test(module.context);

  31. },

  32. },

  33. 'async-biz': {

  34. name: 'async-biz',

  35. chunks: 'async',

  36. minChunks: 2, // 被引用次数大于2时进行拆分

  37. },

  38. // css文件单独打包进一个文件中

  39. 'styles': {

  40. name: 'styles',

  41. test: /\.css$/,

  42. chunks: 'async',

  43. enforce: true,

  44. },

  45. },

  46. },

  47. },

  48. 复制代码

当抽取异步加载组件时,我们需要在业务代码中使用下面的写法,webpack才能识别:

基于react-loadable库,简介: React Loadable 简介

  1. import Loadable from 'react-loadable';

  2. const loder1 = import(/* webpackChunkName: "chunkname2" */'path/to/module2')

  3. const Component = Loadable({

  4. loader,

  5. loadingComponent, // loding时使用的组件

  6. })

  7. 复制代码

1.3 合理使用异步加载

若我们项目中的首屏也是使用异步加载的方法,并且用到了async-vendor里的组件,首屏加载时就会同时加载async-vendor并做缓存,这样之后的页面使用到了async-vendor里面的组件之后就无需再次加载新的代码。但是这样就会可能拖慢我们的的首屏速度。

解决办法一是拆分async-vendor的首屏部分;或是取消首屏页面的异步加载,将其打包到main中,这样就避免了加载async-vendor,大大减少了首屏体积。

1.4 分离出webpack runtime代码

webpack在客户端运行时会首先加载webpack相关的代码,例如require函数等,这部分代码会随着每次修改业务代码后发生变化,原因是这里面会包含chunk id等容易变化的信息。如果不抽取出来将会被打包在vendor当中,导致vendor每次都要被用户重新加载。抽离的配置写在optimization属性中的runtimeChunk配置中:

  1. optimization: {

  2. runtimeChunk: {

  3. name: 'manifest',

  4. },

  5. }

  6. 复制代码

1.5 webpack内部优化

webpack4之前,内部优化主要使用两个插件:HashedModuleIdsPluginModuleConcatenationPlugin

默认情况下,webpack会为每个模块用数字做为ID,这样会导致同一个模块在添加删除其他模块后,ID会发生变化,不利于缓存。

为了解决这个问题,有两种选择:NamedModulesPluginHashedModuleIdsPlugin,前者会用模块的文件路径作为模块名,后者会对路径进行md5处理。因为前者处理速度较快,而后者打包出来的文件体积较小,所以应该开发环境时选择前者,生产环境时选择后者。

ModuleConcatenationPlugin主要是作用域提升,将所有模块放在同一个作用域当中,一方面能提高运行速度,另一方面也能降低文件体积。前提是你的代码是用es模块写的。

在 webpack4 中,只需要optimization的配置项中设置 moduleIdshashed或者named, 设置modeproduction即可。

    1. mode:'production',

    2. optimization : {

    3. moduleIds: 'hashed',

    4. }