优化webpack打包速度的几个插件

966

前言:

    在前端开发的过程中,webpack的使用基本已经成为了标配。如何配置webpack,可以参考官网:www.webpackjs.com/concepts/

    我相信,在前端开发过程中,你一定遇到过这样苦恼的事情,比如:1. 在开发过程中,对项目的某个页面进行小小的改动甚至只是修改一个国际化的内容,项目的编译时间都超过了让你难以接受的范围;2. 由于在开发模式下,可能会出现频繁的打包替换到生产环境进行自验证的问题,这个时候,你执行npm run build之后,webpack运行了长达10分钟之久,这个时候,我们的心态也许就崩了。既然如此,那为何不自己动手优化一下webpack的打包编译时间呢?

开始优化:

1. webpack当前配置

环境版本

|————webpack: 4.12.0
|————webpack-cli:3.0.8
|————node:v12.18.3

项目中的webpack配置如下:

const path = require('path');const VueLoaderPlugin = require('vue-loader/lib/plugin');const MiniCssExtractPlugin = require('mini-css-extract-plugin');const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');const webpack = require('webpack');const NODE_ENV = process.env.NODE_ENV || 'development';const isProd = NODE_ENV === 'production';const isQuickMode = process.env.QUICK_MODE === 'true';const isAnalysis = process.env.ANALYZE_BUNDLE === 'true';function resolve(dir) {  return path.join(__dirname, '..', dir);}const cssLoader = [  {    loader: 'css-loader',    options: { minimize: isProd },  },  'postcss-loader',  'sass-loader',];const webpackCommonPlugins = [  new VueLoaderPlugin(),  new MiniCssExtractPlugin({    filename: 'common.[contenthash].css',  }),  ...(isProd ? [] : [new FriendlyErrorsPlugin()]),  new HardSourceWebpackPlugin(),  // new webpack.optimize.ModuleConcatenationPlugin(),  new webpack.DefinePlugin({    'process.env.NODE_ENV': '"production"',  }),  new webpack.HotModuleReplacementPlugin(),];if (isQuickMode) {  webpackCommonPlugins.unshift(    new ForkTsCheckerWebpackPlugin({      memoryLimit: 1024 * 4,      workers: ForkTsCheckerWebpackPlugin.ONE_CPU_FREE,      ignoreDiagnostics: [2307, 1323],    }),  );}if (isAnalysis) {  webpackCommonPlugins.push(    new BundleAnalyzerPlugin({      async: false,    }),  );}module.exports = {  mode: NODE_ENV,  devtool: isProd ? false : '#cheap-module-source-map',  output: {    path: resolve('dist'),    publicPath: '/dist/',    library: 'PARTNER_ADMIN',    jsonpFunction: 'PARTNER_ADMINJsonp',    filename: '[name].[hash].js',    globalObject: 'this',  },  externals: {    jquery: 'jQuery',  },  resolve: {    alias: {      public: resolve('public'),      vue$: 'vue/dist/vue.esm.js',      '@': resolve('src'),    },    extensions: ['.webpack.js', '.web.js', '.ts', '.js', '.json', '.vue'],  },  module: {    noParse: /es6-promise\.js$/,    rules: [      {        test: /\.vue$/,        loader: 'vue-loader',        options: {          compilerOptions: {            preserveWhitespace: false,          },        },      },      {        enforce: 'pre',        test: /\.js$/,        include: [resolve('src')],        exclude: [resolve('node_modules')],        loader: 'eslint-loader',      },      {        test: /\.js$/,        loader: 'babel-loader?cacheDirectory=true',        include: [resolve('src')],      },      {        test: /\.ts$/,        loader: 'ts-loader',        options: {          appendTsSuffixTo: [/\.vue$/],          happyPackMode: isQuickMode,        },      },      {        test: /\.(png|jpg|gif|svg|woff|ttf)$/,        loader: 'url-loader',        options: {          limit: 20000,          name: '[name].[ext]?[hash]',        },      },      {        test: /\.s(c|a)ss$/,        use: [          'vue-style-loader',          'css-loader',          {            loader: 'sass-loader',            // Requires sass-loader@^8.0.0            options: {              implementation: require('sass'),              sassOptions: {                fiber: require('fibers'),                indentedSyntax: true, // optional              },            },          },        ],      },      {        test: /\.(sc|c)ss$/,        oneOf: [          {            test: /App/,            resourceQuery: /\?vue/,            use: [MiniCssExtractPlugin.loader, ...cssLoader],          },          {            use: ['vue-style-loader', ...cssLoader],          },        ],      },    ],  },  performance: {    maxEntrypointSize: 300000,    hints: isProd ? 'warning' : false,  },  plugins: webpackCommonPlugins,  devServer: {    contentBase: resolve('dist'),    hot: true,    open: true,  },};

这个时候,运行npm run build之后,构建的时间为11.84s (PS: 这个时间会因为每个人的电脑配置以及实际的项目大小而变化)。为了准确分析,每次修改都以三次构建的时间为准。

构建时间记录(3次):11.84s, 10.44s, 11.50s                           

2. 使用插件:hard-source-webpack-plugin

作用

    该插件的作用是为打包后的模块提供缓存,且缓存到本地硬盘上。默认的缓存路径是:mode_modules/.cache/hard-source

如何使用

插件的使用是比较简单的。

(1)首先安装插件

npm i -D hard-source-webpack-plugin 或者 yarn add -d hard-source-webpack-plugin

(2)导入并使用:在webpack的配置文件中, 

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const webpackCommonPlugins = [
  new VueLoaderPlugin(),
  new MiniCssExtractPlugin({
  filename: 'common.[chunkhash].css',}),
  ... isProd ? [] : [ new FriendlyErrorsPlugin() ],
  new HardSourceWebpackPlugin(), // 加在此处;

打包时间

当第一次build时,时间不会有太大变化,但从第二次开始,构建时间大概可以节约70%-80%的样子(这是网上流传的说法,不过实际上跟你本身配置也有一定关系)。这个插件也是我在实际使用的时候效果最明显的一个。下面记录从第二次开始的构建时间。

构建时间记录(3次):6.97s, 7.43s, 7.18s

通过上述三次时间的对比,可以看出大概优化了28%—40%左右的样子。

3. 使用DefinePlugin

插件说明及使用

  • 该插件将“production”替换到process.env.NODE_ENV;
  • UglifyJS会移除掉所有的if分支,因为“production” !== "production"永远返回false

它存在与webpack中,只要安装了webpack就可以使用,如下

new webpack.DefinePlugin({    'process.env.NODE_ENV': '"production"',  }),

打包时间

构建时间记录(3次):6.78s, 6.95s, 7.31s

通过上述三次时间的对比,可以看出使用DefinePlugin的效果并不明显。

4. 插件:UglifyJS-webpack-plugin

插件说明及使用

官网地址:www.html.cn/doc/webpack…

该插件支持parallel字段,可以进行并行化构建,可以显着加速构建。

安装方式:

npm i -D uglifyjs-webpack-plugin 或者 yarn add uglifyjs-webpack-plugin -D 

PS:值得注意的是,该插件已经在webpack4之后的版本被移除了,所以如果你正在使用的是webpack4,那使用uglifyjs-webpack-plugin将会报错。好吧,既然移除了,我们就不再使用了。感兴趣的小伙伴请参考官网配置。

5. 一个优秀的打包分析软件:webpack-bundle-analyzer

插件说明及使用

该插件可以生成项目中所以bundle的treemap图,让优化一目了然所以的包大小以及如何优化的方向。如下图:

安装方式:

npm i -D webpack-bundle-analyzer 或者 yarn add webpack-bundle-analyzer -D 

使用:

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

在对应的webpackCommonPlugins中添加即可:

new BundleAnalyzerPlugin(),

如何分析

首先,我们可以观察一下,这个树形图中都包含了哪些内容:

  • 每个打包后的bumdle文件中包含了html, css, img, js, modules, components等;
  • 每个bundle文件中,列出了具体的大小,包含start size, parsed size, gzip size具体的数值,便于使用者针对性的进行优化;

3个size大小分别表示:

  • start size: 原始大小,没有经过处理的;
  • parsed size: 处理之后的大小;
  • gzipped size: 经过gzip压缩后的大小;

文件出现的面积越大,打包后的文件就越大,针对性进行优化即可。

6. 一些优化的小动作

动作1:

我们给不需要loader进行处理的文件加上exclude,如:给eslint-loader加上exclude: [resolve('node_modules')], 这样一来他就不会去加载node_modules中的文件了。

 {
    enforce: 'pre',    test: /\.js$/,    include: [resolve('src')],    exclude: [resolve('node_modules')],    loader: 'eslint-loader',},

动作2:

给babel-loader加上缓存参数:cacheDirectory=true。

{    test: /\.js$/,    loader: 'babel-loader?cacheDirectory=true',    include: [resolve('src')],},

打包时间:

打包时间记录(3次):6.55s, 6.26s, 6.59s

从上述时间上来看,总体是减少了,但仍旧效果不明显。

7. devServer配置

思考:

随着项目后期越来越大,每次build的时间必然会比较长,这样会严重降低开发效率。并且注意:我们没有办法再打包时就去调试http请求。

针对这个问题,我们就可以在开发过程中引入webpack-dev-server,可参考官网:www.webpackjs.com/configurati…,它可以帮助我们在开发过程中起一个小型的服务器,设置为热更新模式,方便我们快速开发,提高效率。

安装:

npm i -D webpack-dev-server 或者 yarn add webpack-dev-server -D 

使用:

在webpack的配置文件中添加如下内容:

devServer: {    contentBase: resolve('dist'),    hot: true,    open: true,  },

然后再在packge.json中添加:

"dev": "webpack-dev-server",

此时执行npm run dev,就可以看到我们启动的服务了,且对于开发中的更改也会及时更新出来,根目录下不会产生dist文件夹,而是直接打包在了内存上

但我们不想因为修改了一小块东西就整体进行刷新,那么HotModuleReplacementPlugin()就可以帮助我们实现局部更新功能,所以加上就可以了。

plugins: [ 
    new webpack.HotModuleReplacementPlugin(),
  ],