一次webpack3升級爲webpack4的實踐

image

此次webpack升級提高了很多構建速度:生產打包提高了30%;開發構建提高40%,開發熱更新提高70%

以前嘗試過一些在webpack3的基礎上作的構建優化,例如引入HappyPack優化構建速度,開啓loader緩存和優化包查找路徑等等,詳情能夠查看前端webpack構建優化css

可是隨着時間的推移,這種優化產生的效果愈來愈弱化,手上的項目體積愈來愈大,對本地開發熱更新速度和生產打包發佈速度都有了很大的影響。html

webpack3升級到webpack4迫在眉睫,這篇博文將記錄一些我在升級過程當中遇到的坑。前端

當你遇到這些坑時,經過搜索引擎找到我這篇文章,若是可以解決了手上的webpack配置問題,而後發自心裏的感到 」Save my day!「,」It helps me!「,」Solved my problem!「,」Works for me!「 ,我會感受本身的這篇博文頗有意義。vue

  • 升級到 webpack 4
  • 移除 CommonsChunkPlugin,默認使用 SplitChunksPlugin 分割代碼
  • 升級 html-webpack-plugin
  • 移除 extract-text-webpack-plugin,引入 mini-css-extract-plugin 並配置 css-loader
  • 配置 mode 屬性
  • 升級 vue-loader 到 v14.2.2
  • 更新 HtmlWebpackPlugin 的 chunkSortMode
  • 修復大小限制的報錯
  • 重命名 app.js,生成 vendors.js
  • mini-css-extract-plugin 的配置放在 webpack.base.conf.js
  • 調試開發環境可用
  • 引入analyzer分析分析包大小
  • webpack3與webpack4打包對比
  • 文件能夠更小一些嗎?構建速度能夠更快一些嗎?
  • 升級vue-loader到v15而且替換happyPack爲thread-loader
  • webpack3與webpack4開發依賴對比
  • 總結

升級 webpack 到 4

"webpack": "^3.6.0" -> "webpack": "^4.43.0"node

yarn add -D webpack@4.43.0

移除 CommonsChunkPlugin

plugins: [
  // // split vendor js into its own file
  // new webpack.optimize.CommonsChunkPlugin({
  //   name: 'vendor',
  // }),
  // // extract webpack runtime and module manifest to its own file in order to
  // // prevent vendor hash from being updated whenever app bundle is updated
  // new webpack.optimize.CommonsChunkPlugin({
  //   name: 'manifest',
  //   minChunks: Infinity,
  // }),
  // // This instance extracts shared chunks from code splitted chunks and bundles them
  // // in a separate chunk, similar to the vendor chunk
  // // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
  // new webpack.optimize.CommonsChunkPlugin({
  //   name: 'app',
  //   async: 'vendor-async',
  //   children: true,
  //   minChunks: 3,
  // }),
];

升級 html-webpack-plugin

"html-webpack-plugin": "^2.30.1" -> "html-webpack-plugin": "^4.3.0"webpack

// https://stackoverflow.com/questions/49942558/deprecationwarning-tapable-plugin-is-deprecated-use-new-api-on-hooks-instea
// error
Tapable.apply is deprecated. Call apply on the plugin directly instead
yarn add -D html-webpack-plugin@latest

移除 extract-text-webpack-plugin,引入 mini-css-extract-plugin 並配置 css-loader

// const ExtractTextPlugin = require('extract-text-webpack-plugin');
// plugins:[
// extract css into its own file
// new ExtractTextPlugin({
//   filename: utils.assetsPath('css/[name].[contenthash].css'),
//   // Setting the following option to `false` will not extract CSS from codesplit chunks.
//   // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by
//   // webpack. It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit
//   // bundle as well when it's `false`, increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
//   allChunks: true,
// }),
// ]

// extract: true
// if (options.extract) {
//   return ExtractTextPlugin.extract({
//     use: loaders,
//     fallback: 'vue-style-loader',
//   });
// }
yarn add -D mini-css-extract-plugin
// webpack.prod.conf.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
plugins: [
    new MiniCssExtractPlugin(filename: utils.assetsPath('css/[name].[contenthash].css'))
];
// webpack.base.conf.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: process.env.NODE_ENV === "development",
            },
          },
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
    ],
  },
};

配置 mode 屬性

The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
mode: "production";

升級 vue-loader

"vue-loader": "^13.3.0" -> "vue-loader": "14.2.2"git

TypeError: Cannot read property ' vueOptions' of undefined
yarn add -D vue-loader@latest
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config
// https://github.com/symfony/webpack-encore/issues/311
You probably use vue-loader v15 which was released yesterday and introduces a lot of changes compared to v14. One of these changes is that you have to use an extra plugin: VueLoaderPlugin (that's not handled yet by Encore).

In the meantime could you try removing your version of the vue-loader/VueLoaderPlugin and adding vue-loader@^14.2.2 instead?
yarn add -D vue-loader@14.2.2
(1:1) Unknown word

> 1 | // extracted by mini-css-extract-plugin

移除 postcss-loader。github

// postcss: generateLoaders()

更新 HtmlWebpackPlugin 的 chunkSortMode

// https://www.cnblogs.com/wyliunan/p/10238717.html
Unhandled rejection Error: "dependency" is not a valid chunk sort mode

設置爲 HtmlWebpackPlugin 的 chunkSortMode 爲"auto": https://github.com/jantimon/h...web

修復大小限制的報錯

AssetsOverSizeLimitWarning: asset size limit: The following asset(s) exceed the recommended size limit (244 KiB 250000Byte).
This can impact web performance.
// webpack.config.js
module.exports = {
  performance: {
    hints: "warning",
    maxEntrypointSize: 5000 * 1024,
    maxAssetSize: 5000 * 1024,
  },
};

生成 manifest.js,生成 vendors.js

// https://webpack.js.org/configuration/optimization/#optimizationsplitchunks
// 生成manifest.js
optimization: {
    runtimeChunk: {
        name:'manifest'
    }
},
// https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-1
// 生成 vendors.js
optimization: {
  splitChunks: {
    cacheGroups: {
      commons: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        chunks: 'all',
      },
    },
  },
}
output: {
-   chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
+   chunkFilename: utils.assetsPath('js/[name].[chunkhash].js'),
  },

調試開發環境可用

Error: Cannot find module 'webpack/bin/config-yargs'

https://github.com/mzgoddard/jest-webpack/issues/27
"webpack-cli": "^2.1.3",
"webpack-dev-server": "^3.1.4"
mode: 'development',
// webpack Error: Callback was already called.
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/493
// webpack.dev.js
plugins:[
    new MiniCssExtractPlugin(),
]
// https://segmentfault.com/q/1010000012054980
// BaseClient.js:12 Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

安裝transform-es2015-modules-commonjs而且在.babelrc中配置。npm

yarn add -D transform-es2015-modules-commonjs
// .babelrc
"plugins": [
    "transform-es2015-modules-commonjs"
]

引入analyzer分析分析包大小

// package.json
scripts:{
  "build:analyse": "NODE_ENV=production source_map=false npm_config_report=true node build/build.js"
}
// webpack.prod.conf.js
if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

webpack3與webpack4打包對比

版本 文件大小(Parsed) 文件大小(Gzipped) chunk數 生產構建時間 開發構建時間 開發熱更新體感
webpack3.6.0 6.09MB 1.76MB 73 52196ms 70103ms 慢(12079ms)
webpack4.43.0 7.07MB 1.98MB 88 40727ms 45448ms 快(3394ms)

機器參數:
MacBook Pro (15-inch, 2019)
處理器 2.3 GHz Intel Core i9
內存 16 GB 2400 MHz DDR4

文件能夠更小一些嗎?構建速度能夠更快一些嗎?

  • 未使用TerserPlugin而是用UglifyjsPlugin
  • OptimizeCSSPlugin位置放錯
  • 若是手動配置splitChunks的話,必定要把沒有配置的參數也配置上
  • devtool由最慢的「source-map」改成false

引入TerserPlugin的話,須要首先升級node到v10.17.0+。

sudo n v10.17.0
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      // Compress extracted CSS. We are using this plugin so that possible
      // duplicated CSS from different presentation can be deduped.
      new OptimizeCSSPlugin({
        cssProcessorOptions: config.build.productionSourceMap ? { safe: true, map: { inline: false } } : { safe: true },
      }),
      new TerserPlugin({
        cache: true,
        parallel: true,
        sourceMap: Boolean(config.build.productionSourceMap),
      }),
    ],
  }
}

增長下面的配置:

optimization: {
  splitChunks: {
    chunks: 'async',
    minSize: 30000,
    maxSize: 0,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    automaticNameMaxLength: 30,
  }
}

如今的webpack3和webpack4打包分析:

版本 文件大小(Parsed) 文件大小(Gzipped) chunk數 生產構建時間 開發構建時間 開發熱更新體感
webpack3.6.0 6.09MB 1.76MB 73 52196ms 70103ms 慢(12079ms)
webpack4.43.0(優化前) 7.07MB 1.98MB 88 40727ms 45448ms 快(3394ms)
webpack4.43.0(優化後) 7.02MB 1.98MB 88 34585ms 45448ms 快(3394ms)

經過對比發現,提高了大概5秒的打包速度。

升級vue-loader到v15而且引入thread-loader加速vue-loader

warning No parser and no filepath given, using 'babel' the parser now but this will throw an error in the future. Please specify a parser or a filepath so one can be inferred

爲何引入thread-loader加速vue-loader?
由於HappyPack沒法加速vue-loader15。
https://github.com/vuejs/vue-...

yyx990803:vue-loader 15 does not support HappyPack. Use thread-loader instead.

順便升級eslint-loader到4。
"eslint-loader": "^1.7.1"->"eslint-loader": "^4.0.2"

// plugins: [
  //   new HappyPack({
  //     id: 'happy-eslint-loader',
  //     threadPool: happyThreadPool,
  //     loaders: ['eslint-loader?cacheDirectory=true'],
  //   }),
  //   new HappyPack({
  //     id: 'happy-vue-loader',
  //     threadPool: happyThreadPool,
  //     loaders: ['vue-loader?cacheDirectory=true'],
  //   }),
  //   new HappyPack({
  //     id: 'happy-babel-loader',
  //     threadPool: happyThreadPool,
  //     loaders: ['babel-loader?cacheDirectory=true'],
  //   }),
  // ]
rules: [
  {
    test: /\.(js|vue)$/,
    use: [
      { loader: 'thread-loader' },
      {
        loader: 'eslint-loader',
        options: {
          formatter: require('eslint-friendly-formatter'),
          emitWarning: !config.dev.showEslintErrorsInOverlay,
        },
      },
    ],
    enforce: 'pre',
    include: [resolve('src'), resolve('test')],
  },
  {
    test: /\.vue$/,
    use: ['thread-loader', 'vue-loader'],
    exclude: (file) => /node_modules/.test(file) && !/\.vue\.js/.test(file),
  },
  {
    test: /\.js$/,
    use: ['thread-loader', 'babel-loader'],
    include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')],
  },
  {
    test: /\.(sa|sc|c)ss$/,
    use: [
      {
        loader: process.env.NODE_ENV === 'development' ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
        options: {
          hmr: process.env.NODE_ENV === 'development',
        },
      },
      'css-loader',
      'postcss-loader',
      'sass-loader',
    ],
  },
]
error:despite it was not able to fulfill desired ordering with these modules:
new MiniCssExtractPlugin({
  ignoreOrder: true,
}),

如今的webpack3和webpack4打包分析:

版本 文件大小(Parsed) 文件大小(Gzipped) chunk數 生產構建時間 開發構建時間 開發熱更新體感
webpack3.6.0 6.09MB 1.76MB 73 52196ms 70103ms 慢(12079ms)
webpack4.43.0(優化前) 7.07MB 1.98MB 88 40727ms 45448ms 快(3394ms)
webpack4.43.0(第一次優化) 7.02MB 1.98MB 88 34585ms 45448ms 快(3394ms)
webpack4.43.0(第二次優化) 6.7MB 1.91MB 88 34585ms 41657ms 快(3394ms)

webpack3與webpack4開發依賴對比

// webpack3
"webpack": "^3.6.0"
"webpack-dev-server": "^2.9.1"
"eslint-loader": "^1.7.1"
"vue-loader": "^13.3.0"
"happypack": "^5.0.0"
"html-webpack-plugin": "^2.30.1"
"extract-text-webpack-plugin": "^3.0.0"
"uglifyjs-webpack-plugin": "^1.1.1"
// webpack4
"webpack": "^4.43.0"
"webpack-cli": "^3.3.11"
"webpack-dev-server": "^3.7.2"
"thread-loader": "^2.1.3"
"eslint-loader": "^4.0.2"
"vue-loader": "^15.9.2"
"html-webpack-plugin": "^4.3.0"
"mini-css-extract-plugin": "^0.9.0"
"terser-webpack-plugin": "^3.0.1"
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2"

總結

webpack3到webpack4的升級,主要作了如下這些事情

  • 升級依賴:升級webpack major version爲webpack4,升級vue-loader,升級html-webpack-plugin等等
  • 代碼分割:移除了CommonsChunkPlugin;引入SplitChunksPlugin,設置mode屬性爲production,optimization拆分出webpack3中的manifest和vendors
  • 壓縮css:移除extract-text-webpack-plugin;引入mini-css-extract-plugin,使用mini-css-extract-plugin的loader從新配置sass-loader,postcss-loader和css-loader,它會爲每一個包含css的js文件單獨構建一個js文件
  • 代碼熱更新:升級webpack-cli和webpack-dev-server,而且設置mode爲development

反思

  • 執行力是第一輩子產力
  • 此次webpack升級提高了很多構建速度:生產打包提高了30%;開發構建提高40%,開發熱更新提高70%
  • 此次webpack升級沒有減少包大小,有嘗試使用tree shaking,可是沒有成功,有待在tree shaking上繼續作實踐

期待和你們交流,共同進步,歡迎你們加入我建立的與前端開發密切相關的技術討論小組:

努力成爲優秀前端工程師!
相關文章
相關標籤/搜索