9102年 Webpack4 升级

381 阅读5分钟

升级 webpack4 已经有一段时间了,将升级过程中记录下来。此次升级是基于 vue-cli 做的。

升级

将 Webpack 升级到 4.x 之后,直接运行webpack -xx,会提示还需要安装 webpack-cli。

The CLI moved into a separate package: webpack-cli. Please install webpack-cli in addition to webpack itself to use the CLI.

npm i webpack@latest webapck-cli -D

接下来升级所有的依赖,查看所有可以更新的包,进行升级。

npm outdate

默认配置

mode: 'development'
  • 自动通过 DefinePlugin 设 process.env.NODE_ENV 的值为 development
  • 自动开启 NamedChunksPlugin(固定 chunk id) 和 NamedModulesPlugin(开启 HMR 的时候使用该插件会显示模块的相对路径)
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- plugins: [
-   new webpack.NamedModulesPlugin(),
-   new webpack.NamedChunksPlugin(),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
mode: 'production'
  • 自动通过 DefinePlugin 设 process.env.NODE_ENV 的值为 production
  • 自动开启 FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin,OccurrenceOrderPlugin,SideEffectsFlagPlugin,TerserPlugin
module.exports = {
+  mode: 'production',
-  plugins: [
-    new UglifyJsPlugin(/* ... */),
-    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
-    new webpack.optimize.ModuleConcatenationPlugin(),
-    new webpack.NoEmitOnErrorsPlugin()
-  ]
}

DefinePlugin 允许创建一个在编译时可以配置的全局常量。 在此处设置可以全局访问的环境变量 process.env.NODE_ENV,以便做相应不同的操作 。

所谓的“零配置”就是通过设置mode,自动配置两个环境通常需要做的操作。

mini-css-extract-plugin

webpack4 更推荐使用 mini-css-extract-plugin 替代 extract-text-webpack-plugin。该插件将CSS提取到单独的文件中。它为每个包含CSS的JS文件分离出一个CSS文件,并且支持CSS和SourceMap的按需加载。

css 原本是内联在 js 文件中的,之所以要将 css 独立拆包,是为了让 css 和 js 的改动互不影响,比如改了css,只会让 css 的缓存失效,而不会让 js 的缓存失效。并且它还会根据 optimization.splitChunks 的配置来自动拆包,比如将 element-ui 单独拆分出来作为一个 bundle, 那么 element-ui 的 css 也被单独拆分出来,而不是像原来将所有 css 合并成一个文件,动辄几百 KB 的大小。

与 extract-text-webpack-plugin 相比:

  • Async loading [异步加载]
  • No duplicate compilation (performance) [没有重复编译(性能)]
  • Easier to use [更容易使用]
  • Specific to CSS [专门为 css 设计]
// webpack.prod.conf.js
module.exports = merge(baseWebpackConfig, {
  plugins: [
-   new MiniCssExtractPlugin({
-     filename: utils.assetsPath('css/[name].[contenthash].css')
-   }),
  ]
} 
// utils.js
// Extract CSS when that option is specified
// (which is the case during production build)
- if (options.extract) {
-   return ExtractTextPlugin.extract({
-  use: loaders,
-     fallback: 'vue-style-loader',
-     publicPath: '../../'
-   })
- } else {
-   return ['vue-style-loader'].concat(loaders)
- }
+ return [
+   options.extract ? {
+     loader: MiniCssExtractPlugin.loader,
+     options: {
+       publicPath: '../../'
+     }
+   } : 'vue-style-loader',
+ ].concat(loaders)

vue-loader

将 vue-loader 升级到15.x之后,需要增加如下代码:

const { VueLoaderPlugin } = require('vue-loader')
plugins: [
  new VueLoaderPlugin(),
] 

optimization

下面是 webpack4 默认的 optimization 配置。

  • mode: development
+ mode: 'development'
- optimization: {
-   namedModules: true,
-   namedChunks: true,
-   nodeEnv: 'development',
-   flagIncludedChunks: false,
-   occurrenceOrder: false,
-   sideEffects: false,
-   usedExports: false,
-   concatenateModules: false,
-   splitChunks: {
-     hidePathInfo: false,
-     minSize: 10000,
-     maxAsyncRequests: Infinity,
-     maxInitialRequests: Infinity,
-   },
-   noEmitOnErrors: false,
-   checkWasmTypes: false,
-   minimize: false,
-   removeAvailableModules: false
- },
  • mode: production
+  mode: 'production',
- optimization: {
-   namedModules: false,
-   namedChunks: false,
-   nodeEnv: 'production',
-   flagIncludedChunks: true,
-   occurrenceOrder: true,
-   sideEffects: true,
-   usedExports: true,
-   concatenateModules: true,
-   splitChunks: {
-     hidePathInfo: true,
-     minSize: 30000, // 压缩前 chunk 的体积大于等于300000 byte
-     maxAsyncRequests: 5, // 按需加载 bundle 的并发请求数小于等于5
-     maxInitialRequests: 3, // 页面初始加载 bundle 的并发请求数小于等于3
-   },
-   noEmitOnErrors: true,
-   checkWasmTypes: true,
-   minimize: true,
- },

splitChunk

webpack4 其中一个重要改变就是 optimization.splitChunk 替代原先的 webpack.optimize.CommonsChunkPlugin。

只要设置 mode: production,webpack4 就会自动开启 splitChunk 进行 Code Splitting。默认的 splitChunk 的配置如上。

Code Splitting 的目的就是为了更好的利用浏览器缓存。虽然 webpack4 默认的拆包已经很不错,但是根据自己项目的情况还是可以进行优化。

基础库 vendor

如 vue,vuex,vue-router,axios 等第三方库,是项目构成的基础,每个页面都有用到,并且升级频率不高,除了组件库之外还有一些体积不大的第三方库,这些都放在这个包里。

组件库

项目中使用的组件库是 element-ui,由于几乎所有的组件都有用到,所以没有做按需引入。体积很大,没 Gzip 之前有658.86 KB,Gzip 之后也有160多 KB。比整个 vendor 的体积都大,并且更新频率也比 vendor 里的高。如果不将 element -ui 分割出来,一旦更新,那么整个 vendor + element-ui 体积大小的 bundle 要重新从服务器获取资源,不能利用缓存。因此将组件库从 vendor 中分割了出来。

业务代码

我们平时写的业务代码。项目中使用了路由懒加载,通过 component: () => import(...) 引入路由组件,webpack 就会根据路由自动进行代码分割。

manifest

我们还需要将存有 chunk 之间映射关系的 runtime 代码提取出来,否则它将包含在 bundle 之中。manifest 描述了 webpack 应该加载哪些文件,提取出来可以更快的加载文件而不用等待 bundle 加载。并且,哈希值是根据 chunk 内容生成的,一旦哈希值改变,runtime 代码也会改变,那么runtime 所在的 bundle 也将缓存失效。

inline-manifest-webpack-plugin

manifest 提出出来得到的文件通常很小,并且这个文件每次上线都会改变,需要为了几 KB 浪费一次请求数。因此更推荐将 manifest 内联到 html 文件中,毕竟 html 文件 是每次都会改变的。使用的插件是 inline-manifest-webpack-plugin,具体用法见链接,这里就不再赘述。

根据上面所说的我们可以做出如下配置

runtimeChunk: {
  name: 'manifest',
},
splitChunks: {
  chunks: 'all',
  cacheGroups: {
    // 抽离第三方插件
    vendor: {
      test: /[\\/]node_modules[\\/]/,
      chunks: 'initial',
      name: 'vendor',
    },
    'vendor-element-ui': {
      name: 'vendor-element-ui', // split elementUI into a single package
      priority: 20, // the weight needs to be larger than vendor and app or it will be packaged into libs or app
      test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
      enforce: true,
     },
  },
},

minimizer

optimize-css-assets-webpack-plugin

将 css 分离出来后,还需要对 css 做压缩与优化。这是为什么还需要optimize-css-assets-webpack-plugin

optimization: {
  minimizer: [new OptimizeCSSAssetsPlugin()]
}

optimize-css-assets-webpack-plugin 默认使用 cssnano 来做 css 优化,压缩代码,删掉无用的注释,去掉冗余的代码,还会优化代码及书写顺序。因此大大减少了 css 文件的大小。

terser-webpack-plugin

在 mode: production 模式下,会自动开启 minimize: true,自动压缩代码,内置的插件就是 terser-webpack-plugin,但是 terser-webpack-plugin 的默认配置并不能满足我们的项目。

optimization: {
  minimizer: [
    new TerserPlugin({
      terserOptions: {
        drop_console: true,
        safari10: true,
       },
      sourceMap: config.build.productionSourceMap,
      parallel: true,
      cache: true,
    }),
  ]
}

打包速度对比

版本 第一次 第二次 第三次 第四次 第五次
webpack3 49324 52749 49431 51893 49725
webpack4 39034 33620 31968 30814 31874

可以看到打包速度有了质的飞跃,大幅降低上线所需要的时间。