前端項目的webpack常用優化方案


本文主要介紹 webpack5 項目的打包優化方案

打包優化

速度分析:要進行打包速度的優化,首先我們需要搞明白哪一些流程的在打包執行過程中耗時較長。

這里我們可以借助 speed-measure-webpack-plugin 插件,它分析 webpack 的總打包耗時以及每個 plugin 和 loader 的打包耗時,從而讓我們對打包時間較長的部分進行針對性優化。

通過以下命令安裝插件:

yarn add speed-measure-webpack-plugin -D

webpack.config.js 中添加如下配置

// ...
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // ...
  plugins: [
    // ...
  ],
  module: {
    // ...
  }
});

執行 webpack 打包命令后,如下圖可以看到各個 loader 和 plugin 的打包耗時:

image.png

cdn 分包

對於項目中我們用的一些比較大和比較多的包,例如 react 和 react-dom,我們可以通過 cdn 的形式引入它們,然后將 reactreact-dom 從打包列表中排除,這樣可以減少打包所需的時間。

排除部分庫的打包需要借助 html-webpack-externals-plugin 插件,執行如下命令安裝:

yarn add html-webpack-externals-plugin -D

以 react 和 react-dom 為例,在 webpack 中添加如下配置:

const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackExternalsPlugin({
      externals: [
        {
          module: 'react',
          entry: 'https://unpkg.com/react@17.0.2/umd/react.production.min.js',
          global: 'React',
        },
        {
          module: 'react-dom',
          entry:
            'https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js',
          global: 'ReactDOM',
        },
      ],
    }),
  ],
};

效果對比如下:

優化前打包時間約為 2s: image.png 優化后打包時間不到 1s: image.png

多進程構建

對於耗時較長的模塊,同時開啟多個 nodejs 進程進行構建,可以有效地提升打包的速度。可以采取的一些方式有:

  • thread-loader
  • HappyPack(作者已經不維護)
  • parallel-webpack

下面以官方提供的 thread-loader 為例,執行以下命令安裝 thread-loader:

yarn add thread-loader -D

webpack.config.js 中添加如下配置:

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        include: path.resolve('src'),
        use: [
          "thread-loader",
          // 耗時的 loader (例如 babel-loader)
        ],
      },
    ],
  },
};

使用時,需將此 loader 放置在其他 loader 之前,放置在此 loader 之后的 loader 會在一個獨立的 worker 池中運行。

每個 worker 都是一個獨立的 node.js 進程,其開銷大約為 600ms 左右。同時會限制跨進程的數據交換。所以請僅在耗時的操作中使用此 loader!(一般只在大型項目中的 ts、js 文件使用)

並行壓縮

一些插件內置了 parallel 參數(如 terser-webpack-plugin, css-minimizer-webpack-plugin, html-minimizer-webpack-plugin),開啟后可以進行並行壓縮。

webpack5版本內置了 terser-webpack-plugin 的配置,如果是 v4 或者更低版本,執行以下命令安裝 terser-webpack-plugin :

yarn add terser-webpack-plugin -D

webpack.config.js 進行如下配置:

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};

預編譯資源模塊

通過預編譯資源模塊,可以代替 cdn 分包的方式,解決每個模塊都得引用一個 script 的缺陷。

還是以 react 和 react-dom 為例,新建一個 webpack.dll.js 文件,用於預編譯資源的打包,例如要對 react 和 react-dom 進行預編譯,配置如下:

const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production',
  entry: {
    library: ['react', 'react-dom'],
  },
  output: {
    filename: 'react-library.dll.js',
    path: path.resolve(__dirname, './dll'),
    library: '[name]_[hash]', // 對應的包映射名
  },
  plugins: [
    new webpack.DllPlugin({
      context: __dirname,
      name: '[name]_[hash]', // 引用的包映射名
      path: path.join(__dirname, './dll/react-library.json'),
    }),
  ],
};

package.json 中新增一條如下命令:

{
  // ...
  "scripts": {
    // ...
    "build:dll": "webpack --config ./webpack.dll.js"
  },
  // ...
}

執行 npm run build:dll 后,會在 /build/library 目錄下生成如下內容,library.js 中打包了 react 和 react-dom 的內容,library.json 中添加了對它的引用:

image.png

然后在 webpack.config.js 中新增如下內容:

const webpack = require('webpack');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./dll/react-library.json'),
    }),
    // 打包后的 .dll.js 文件需要引入到 html中,可以通過 add-asset-html-webpack-plugin 插件自動引入
    new AddAssetHtmlPlugin({ 
      filepath: require.resolve('./dll/react-library.dll.js'),
      publicPath: '',
    }),
  ],
};

效果對比如下:

使用 dll 預編譯資源之前,打包效果如下,總打包耗時 1964ms,且需要打包 react: image.png 使用 dll 預編譯資源之后,打包效果如下,總打包耗時 1148ms,不需要打包 react: image.png

使用緩存

通過使用緩存,能夠有效提升打包速度。緩存主要有以下幾種方案:

  • 使用 webpack5 內置的 cache 模塊
  • cache-loader(webpack5內置了 cache 模塊后可棄用 cache-loader)

內置的 cache 模塊

webpack5 內置了 cache 模塊,緩存生成的 webpack 模塊和 chunk,來改善構建速度。它在開發環境下會默認設置為 type: 'memory' 而在生產環境中被禁用。cache: { type: 'memory' }cache: true 作用一樣,可以通過設置 cache: { type: 'filesystem' } 來開放更多配置項。

例如在 webpack.config.js 中作如下配置:

module.exports = {
  cache: {
    type: 'filesystem',
  },
};

會在 node_modules 目錄下生成一個 .cache 目錄緩存文件內容,且二次打包速度顯著提升:

image.png

cache-loader

在一些性能開銷較大的 loader 之前添加 cache-loader,能將結果緩存到磁盤里。保存和讀取這些緩存文件會有一些時間開銷,所以請只對性能開銷較大的 loader 使用。

執行如下命令安裝 cache-loader:

npm install cache-loader -D

webpack.config.js 對應的開銷大的 loader 前加上 cache-loader:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          'cache-loader',
          'babel-loader'
        ]
      }
    ]
  }
}

同樣會在 node_modules 目錄下生成一個 .cache 目錄緩存文件內容,且二次打包速度顯著提升:

image.png

縮小構建范圍

通過合理配置 rules 中的文件查找范圍,可以減少打包的范圍,從而提升打包速度。

webpack.config.js 中新增如下配置:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        exclude: /node_modules/,
      },
    ],
  },
};

效果對比如下:

配置前,編譯總耗時 1867ms: image.png 配置后,編譯總耗時 1227ms: image.png

加快文件查找速度

通過合理配置 webpack 的 resolve 模塊,可以加快文件的查找速度,例如可以對如下的選項進行配置:

  • resolve.modules 減少模塊搜索層級,指定當前 node_modules,慎用。
  • resovle.mainFields 指定包的入口文件。
  • resolve.extension 對於沒有指定后綴的引用,指定解析的文件后綴查找順序
  • 合理使用 alias,指定第三方依賴的查找路徑

webpack.config.js 作如下配置:

module.exports = {
  resolve: {
    alias: {
      react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
    },
    modules: [path.resolve(__dirname, './node_modules')],
    extensions: ['.js', '.jsx', '.json'],
    mainFields: ['main'],
  },
};

打包體積優化

體積分析

同速度優化一樣,我們要對體積進行優化,也需要了解打包時各個模塊的體積大小。這里借助 webpack-bundle-analyzer 插件,它可以分析打包的總體積、各個組件的體積以及引入的第三方依賴的體積。

執行如下命令安裝 webpack-bundle-analyzer:

yarn add webpack-bundle-analyzer -D

webpack.config.js 中添加如下配置:

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

module.exports = {
  // ...
  plugins: [
    new BundleAnalyzerPlugin()
  ],
};

然后執行 webpack 打包命令,會在 localhost:8888 頁面看到打包后的體積分析:

image.png

提取公共模塊

假如我們現在有一個 MPA(多頁面應用) 的 react 項目,每個頁面的入口文件及其依賴的組件中都會引入一份 reactreact-dom ,那最終打包后的每個頁面中同樣也會有一份以上兩個包的代碼。我們可以將這兩個包單獨抽離出來,最終在每個打包后的頁面入口文件中引入,從而減少打包后的總體積。

webpack.config.js 中添加如下配置:

module.exports = {
  optimization: {
    splitChunks: {
      minSize: 20000,
      cacheGroups: {
        react: {
          test: /(react|react-dom)/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  }
};

效果對比:

優化前總體積 473 kb: image.png 優化后總體積 296 kb: image.png

壓縮代碼

html 壓縮

安裝 html-webpack-plugin 插件,生產環境下默認會開啟 html 壓縮:

npm install html-webpack-plugin

webpack.config.js 做如下配置:

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '../', 'public/index.html'),
    }),
  ],
};

css 壓縮

css-minimizer-webpack-plugin 插件可以壓縮 css 文件代碼,但由於壓縮的是 css 代碼,所以還需要依賴 mini-css-extract-plugin 將 css 代碼單獨抽離:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  optimization: {
    minimizer: [
      // webpack5 可以使用 '...' 訪問 minimizer 數組的默認值
      '...',
      new CssMinimizerPlugin(),
    ],
  },
};

js 壓縮

生產環境下會默認開啟 js 的壓縮,無需單獨配置。

圖片壓縮

使用 image-minimizer-webpack-plugin 配合 imagemin 可以在打包時實現圖片的壓縮。

執行如下命令安裝 image-minimizer-webpack-plugin 配合 imagemin

npm install image-minimizer-webpack-plugin imagemin imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev

webpack.config.js 中新增如下配置:

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const { extendDefaultPlugins } = require("svgo");

module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        type: "asset",
      },
    ],
  },
  optimization: {
    minimizer: [
      "...",
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminMinify,
          options: {
            // Lossless optimization with custom option
            // Feel free to experiment with options for better result for you
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              // Svgo configuration here https://github.com/svg/svgo#configuration
              [
                "svgo",
                {
                  plugins: extendDefaultPlugins([
                    {
                      name: "removeViewBox",
                      active: false,
                    },
                    {
                      name: "addAttributesToSVGElement",
                      params: {
                        attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],
                      },
                    },
                  ]),
                },
              ],
            ],
          },
        },
      }),
    ],
  },
};

效果對比如下:

壓縮前圖片打包后 1.1m: image.png 壓縮后 451kb: image.png

移除無用的 css

通過 purgecss-webpack-plugin,可以識別沒有用到的 class,將其從 css 文件中 treeShaking 掉,需要配合 mini-css-extract-plugin 一起使用。

執行如下命令安裝 purgecss-webpack-plugin

npm install purgecss-webpack-plugin -D

webpack.config.js 文件中做如下配置:

const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgecssPlugin = require('purgecss-webpack-plugin')

const PATHS = {
  src: path.join(__dirname, 'src')
}

module.exports = {
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader"
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
    }),
  ]
}

在 css 文件中添加一段未用到的 css 代碼:

div {
  font-size: 44px;
  display: flex;
}
// 此段為用到:
.unuse-css {
  font-size: 20px;
}

使用 purgecss-webpack-plugin 之前,打包結果如下: image.png 使用 purgecss-webpack-plugin 之后,打包結果如下,無用代碼已經移除: image.png

polyfill service

我們在項目使用了 es6+ 語法時,往往需要引入 polyfill 去兼容不同瀏覽器。目前我們常采用的方案一般是 babel-polyfill 或者 babel-plugin-transform-runtime,然而在部分不同的瀏覽器上,它們一般都會與冗余,從而導致項目一些不必要的體積增大。

以下是幾種常見 polyfill 方案的對比:

方案 優點 缺點
babel-polyfill 功能全面 體積太大超過200kb,難以抽離
babel-plugin-transform-runtime 只polyfill用到的類或者方法,體積相對較小 不能polyfill原型上的方法,不適合復雜業務
團隊維護自己的polyfill 定制化高,體積小 維護成本太高
polyfill service 只返回需要的polyfill,體積最小 部分奇葩瀏覽器的UA不識別,走優雅降級方案返回全部polyfill

這里我們可以采用 polyfill service 方案,它能夠識別 User Agent,下發不同的 polyfill,做到按需加載需要的 polyfill,從而優化我們項目的體積。

polyfill.io/ 查看最新的 polyfill service 的 url,例如目前是:

https://polyfill.io/v3/polyfill.min.js

直接在項目的 html 中通過 script 引入即可:

<script src="https://polyfill.io/v3/polyfill.min.js"></script>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM