淺談 webpack 性能優(yōu)化(內(nèi)附巨詳細 webpack 學(xué)習(xí)筆記)

前言

筆者最近在整理關(guān)于 webpack 相關(guān)的知識點,一方面是因為自己掌握的知識點比較零碎、不夠系統(tǒng),有時候碰到問題不知從何下手,另外一方面 webpack5.0 已經(jīng)在路上了,這的確是一個讓人頭禿的消息。

image

所以這就促使了我去系統(tǒng)性的回顧了一遍 webpack4.0 的所有知識點,包括 webpack 的由來,各種配置的使用、性能優(yōu)化、Webpack 的底層原理、相關(guān)腳手架的配置分析,都回顧了一波,大致目錄如下圖:

image

筆者把系列的文章都扔在了這個倉庫:webpack 學(xué)習(xí)整理文檔,有興趣的同學(xué)可以去看一波。

今天這篇文章也是筆者就學(xué)習(xí)文檔中的性能優(yōu)化這一塊內(nèi)容做的整理與回顧。

文章中使用到的案例代碼鏈接放在了最底部,大家自取。

?

為什么要優(yōu)化

先來說說為什么要優(yōu)化?當(dāng)然如果你的項目很小,構(gòu)建很快,其實不需要特別關(guān)注性能方面的問題。

但是隨著項目涉及到的頁面越來越多,功能和業(yè)務(wù)代碼也會越來越多,相應(yīng)的 webpack 的構(gòu)建時間也會越來越久,這個時候我們就不得不考慮性能優(yōu)化的事情了。

因為這個構(gòu)建時間與我們的日常開發(fā)是密切相關(guān),當(dāng)我們本地開發(fā)啟動 devServer 或者 build 的時候,如果時間過長,會大大降低我們的工作效率。

試想一個場景,我們突然碰到一個緊急 bug,項目啟動需要花費 3/4 分鐘,改完后項目 build 上線也要 3/4 分鐘,這個時候腦瓜是不是 duang、duang、duang...

image

那接下來我們看一下如何優(yōu)化 webpack 的性能,提升 webpack 的構(gòu)建速度。

?

分析工具

在動手優(yōu)化之前,我們需要有一個量化的指標(biāo),得知道影響構(gòu)建時間的問題究竟出在哪里,是某個 chunk 文件太大了,還是哪一個 loader 或者 plugin 耗時太久了等等。

我們可以對通過一些工具對項目進行相應(yīng)的 體積速度 分析, 然后對癥下藥。

體積分析

初級分析

可以通過官方提供的 stat.json 文件幫助我們分析打包結(jié)果,stat.json 文件可以通過下面語句快速生成:

webpack --profile --json > stats.json

接著我們通過官網(wǎng)提供的 stats.json 分析工具 進行分析,上傳 stats.json 文件之后,就可以得到如下圖所示分析結(jié)果:

image

其中包括 webpack 的版本、打包時間、打包過程的 hash 值、模塊數(shù)量( modules )、chunk 數(shù)量、打包生層的靜態(tài)文件 assets 以及打包的警告和錯誤數(shù)。

我們可以分析其提供的內(nèi)容,進行大致問題的定位。

第三方工具

webpack-bundle-analyzer 是打包分析神器,它的界面?zhèn)€人覺得很好看,而且能很直觀的給出每一個打包出來的文件的大小以及各自的依賴,能夠更加方便的幫助我們對項目進行分析。

image

使用如下:

// config/webpack.common.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

const commonConfig = {
  // ...
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerPort: 8889, // 指定端口號
      openAnalyzer: false,
    }),
  ]
  // ...
}

webpack-bundle-analyzer 其底層也是依賴 stat.json 文件的,通過對 stat.json 的分析,得出最后的分析頁面

通過分析工具的分析,我們可以知道哪些文件耗時比較多,打包出來的體積比較大,從而對有問題的文件進行優(yōu)化。

?

速度分析

我們可以通過 speed-measure-webpack-plugin 這個插件幫助我們分析整個打包的總耗時,以及每一個loader 和每一個 plugins 構(gòu)建所耗費的時間,從而幫助我們快速定位到可以優(yōu)化 Webpack 的配置。

image

如上圖,耗時比較長的會以紅色標(biāo)出。

使用

引入此插件,創(chuàng)建一個 plugins 實例 smp 包裹 webpack 配置文件即可,我們修改一下 webpack 的公共配置文件 webpack.common.js

// config/webpack.common.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// ...
module.exports = (production) => {
  if (production) {
    const endProdConfig = merge(commonConfig, prodConfig);
    return smp.wrap(endProdConfig);
  } else {
    const endDevConfig = merge(commonConfig, devConfig);
    return smp.wrap(endDevConfig);
  }
};

筆者文章演示的代碼配置文件分為三個,分別為 開發(fā)環(huán)境配置文件生產(chǎn)環(huán)境配置文件,以及前二者共用的公共配置文件,如下:

  • webpack.dev.js:開發(fā)環(huán)境使用的配置文件
  • webpack.prod.js:生產(chǎn)環(huán)境使用的配置文件
  • webpack.common.js:公共配置文件

執(zhí)行打包之后,可以看到如下效果圖:

image

注意:speed-measure-webpack-plugin 對于 webpack 的升級還不夠完善,暫時還無法與你自己編寫的掛載在 html-webpack-plugin 提供的 hooks 上的自定義 Pluginadd-asset-html-webpack-plugin 就是此類)共存,有人已經(jīng)在 github 上提了 issue 了,但是貌似還是沒有解決。

?

優(yōu)化策略

經(jīng)過相應(yīng)的體積分析和速度分析之后,我們便可以著手進行優(yōu)化了。

使用新版本

這個是 webpack 性能優(yōu)化的萬能膏藥,升級版本必定能帶來性能提升,而且提升很明顯。

我們可以看一張對比圖:

image
image

從上圖中我們可以看到,webpack4.0 的構(gòu)建速度遠遠快于 webpack3.0,官方也說升級之后,升級版本之后,構(gòu)建時間可以降低 60% - 98% 左右。

在每一個版本的更新,webpack 內(nèi)部肯定會做很多優(yōu)化,而 webpack 是依賴 Nodejs 運行環(huán)境,升級他們對應(yīng)的版本,webpack 的速度肯定也能夠獲得提升。

說不定在 webpack5.0 出來之后,我們今天講到的大部分性能優(yōu)化方法都會被集成到 webpack 自身中去,我們只需要通過幾個簡單的配置就能完成性能配置。

同時新版本的包管理工具(Npm、Yarn)也可以更快的幫助我們分析一些包的依賴和引入,從而提高打包速度。

webpack4.0 帶來的優(yōu)化

  • v8 引擎帶來的優(yōu)化(for of 替代 forEach、MapSet 替代 Object、includes 替代 indexOf
  • 默認使用更快的 md4 hash 算法
  • webpack AST 可以直接從 loader 傳遞給 AST,減少解析時間
  • 使用字符串方法替代正則表達式

我們可以在 github 上的 webpack 庫的 releases 版本迭代 頁面中查看其帶來的性能優(yōu)化:

image

一個 v8 性能優(yōu)化例子:

我們可以來看一個例子,比較使用 includes 替代 indexOf 之后帶來的速度提升,創(chuàng)建 compare-includes-indexof.js 文件,在這個文件中建一個 10000000 長度的數(shù)組,記錄兩個函數(shù)分別消耗的時間:

const ARR_SIZE = 10000000;
const hugeArr = new Array(ARR_SIZE).fill(1);

// includes
const includesTest = () => {
  const arrCopy = [];
  console.time('includes')
  let i = 0;
  while (i < hugeArr.length) {
    arrCopy.includes(i++);
  }
  console.timeEnd('includes');
}

// indexOf
const indexOfTest = () => {
  const arrCopy = [];
  console.time('indexOf');
  for (let item of hugeArr) {
    arrCopy.indexOf(item);
  }
  console.timeEnd('indexOf');
}

includesTest();
indexOfTest();

可以發(fā)現(xiàn) includes 的速度遠遠快于 indexOf

  • includes12.224ms
  • indexOf147.638ms
image

所以在項目上盡可能使用比較新的 webpack、NodeNpm、Yarn 版本,是我們提升打包速度的第一步。

?

體積優(yōu)化

webpack 是個項目打包工具,一般項目打完包以后,需要發(fā)布到服務(wù)器上供用戶使用,為了用戶體驗,我們的項目體積需要越小越好,所以 webpack 中打包的體積是 webpack 中重要的一環(huán)。

js 壓縮

webpack4.0 默認在生產(chǎn)環(huán)境的時候是支持代碼壓縮的,即 mode=production 模式下。

實際上 webpack4.0 默認是使用 terser-webpack-plugin 這個壓縮插件,在此之前是使用 uglifyjs-webpack-plugin,兩者的區(qū)別是后者對 ES6 的壓縮不是很好,同時我們可以開啟 parallel 參數(shù),使用多進程壓縮,加快壓縮。

// config/webpack.common.js
const TerserPlugin = require('terser-webpack-plugin');
// ...
const commonConfig = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: 4, // 開啟幾個進程來處理壓縮,默認是 os.cpus().length - 1
      }),
    ],
  },
  // ...
}

CSS 壓縮

壓縮 CSS

我們可以借助 optimize-css-assets-webpack-plugin 插件來壓縮 css,其默認使用的壓縮引擎是 cssnano。 具體使用如下:

// config/webpack.prod.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
// ...
const prodConfig = {
  // ...
  optimization: {
    minimizer: [
      new OptimizeCSSAssetsPlugin({
        assetNameRegExp: /\.optimize\.css$/g,
        cssProcessor: require('cssnano'),
        cssProcessorPluginOptions: {
          preset: ['default', { discardComments: { removeAll: true } }],
        },
        canPrint: true,
      })
    ]
  },
}
擦除無用的 CSS

使用 PurgeCSS 來完成對無用 css 的擦除,它需要和 mini-css-extract-plugin 配合使用。

// config/webpack.common.js
const PurgecssPlugin = require('purgecss-webpack-plugin');
// ...
const PATHS = {
  src: path.join(__dirname, './src')
};

const commonConfig = {
  // ...
  plugins: [
    // ...
    new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
    }),
  ]
  // ...
}

在未使用此插件之前,比如我們只用到了 navcontact 這個類,其他的都沒有用到,我們在未引入之前打包一下,發(fā)現(xiàn)未用到的 css 還是會被打包進去:

image

引入插件后,重新進行打包,發(fā)現(xiàn)沒有用到的 css 都被擦除了:

image

更多使用大家可參考 PurgeCSS 文檔

圖片壓縮

一般來說在打包之后,一些圖片文件的大小是遠遠要比 js 或者 css 文件要來的大,所以我們首先要做的就是對于圖片的優(yōu)化,我們可以手動的去通過線上的圖片壓縮工具,如 tiny png 幫我們來壓縮圖片。

但是這個比較繁瑣,在項目中我們希望能夠更加自動化一點,自動幫我們做好圖片壓縮,這個時候我們就可以借助 image-webpack-loader 幫助我們來實現(xiàn)。它是基于 imagemin 這個 Node 庫來實現(xiàn)圖片壓縮的。

使用很簡單,我們只要在 file-loader 之后加入 image-webpack-loader 即可:

// config/webpack.common.js
// ...
module: {
  rules: [
    {
      test: /\.(png|jpg|gif)$/,
      use: [
        {
          loader: 'file-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/',
          }
        },
        {
          loader: 'image-webpack-loader',
          options: {
            // 壓縮 jpeg 的配置
            mozjpeg: {
              progressive: true,
              quality: 65
            },
            // 使用 imagemin**-optipng 壓縮 png,enable: false 為關(guān)閉
            optipng: {
              enabled: false,
            },
            // 使用 imagemin-pngquant 壓縮 png
            pngquant: {
              quality: '65-90',
              speed: 4
            },
            // 壓縮 gif 的配置
            gifsicle: {
              interlaced: false,
            },
            // 開啟 webp,會把 jpg 和 png 圖片壓縮為 webp 格式
            webp: {
              quality: 75
            }
          }
        }
      ]
    },
  ]
}         
// ...

我們先不使用這個 loader 打包一下,圖片大小是 2.1MB

image

使用 image-webpack-loader 之后,圖片大小是 666KB

image

壓縮的效果還是很明顯的。

拆分代碼

有時候我們寫的某些模塊根本沒有使用,但是還是被打包了,這樣實際上會拖累 webpack 的打包速度,而且也會增加打包文件的體積,所以我們可以使用 tree-shaking 將這些代碼剔除掉。

或者也可以使用 splitChunksPlugin 把一個大的文件分割成幾個小的文件,這樣也可以有效的提升 webpack 的打包速度,詳細的配置介紹大家可以看筆者寫的 配置 SplitChunksPlugin,里面詳細介紹了怎么配置 splitChunks,以及各參數(shù)的用法與意義,在這里就不展開講了。

?

速度優(yōu)化

講完打包體積的優(yōu)化,我們來看一下在速度方面的優(yōu)化。

分離兩套配置

一般來說在項目開發(fā)中,我們會區(qū)分開發(fā)和生產(chǎn)環(huán)境兩套配置,各司其職。

在開發(fā)階段:我們需要 webpack-dev-server 來幫我們進行快速的開發(fā),同時需要 HMR 熱更新 幫我們進行頁面的無刷新改動,而這些在 生產(chǎn)環(huán)境 中都是不需要的。

在生產(chǎn)階段:我們需要進行 代碼壓縮、目錄清理、計算 hash、提取 CSS 等等;

實現(xiàn)起來很簡單,我們前面也提到過,就新建三個 webpack 的配置文件就行:

  • webpack.dev.js:開發(fā)環(huán)境的配置文件
  • webpack.prod.js:生產(chǎn)環(huán)境的配置文件
  • webpack.common.js:公共配置文件

通過 webpack-merge 來整合兩個配置文件共同的配置 webpack.common.js,具體可以參照源碼。

?

減少查找過程

webpackresolve 參數(shù)進行合理配置,使用 resolve 字段告訴 webpack 怎么去搜索文件。

合理使用 resolve.extensions

在導(dǎo)入語句沒帶文件后綴時,webpack 會自動帶上后綴后去嘗試詢問文件是否存在,查詢的順序是按照我們配置 的 resolve.extensions 順序從前到后查找,webpack 默認支持的后綴是 jsjson。

舉個??:如果我們配置 resolve.extensions= ['js', 'json'],那么 webpack 會先找 xxx.js

如果沒有則再查找 xxx.json,所以我們應(yīng)該把常用到的文件后綴寫在前面,或者 我們導(dǎo)入模塊時,盡量帶上文件后綴名。

雖然 extensions 會優(yōu)先查找數(shù)組內(nèi)的值,但是我們不要一股腦兒的把所有后綴都往里面塞,這會調(diào)用多次文件的查找,這樣就會減慢打包速度。

優(yōu)化 resolve.modules

這個屬性告訴 webpack 解析模塊時應(yīng)該搜索的目錄,絕對路徑和相對路徑都能使用。使用絕對路徑之后,將只在給定目錄中搜索,從而減少模塊的搜索層級:

// config/webpack.common.js
// ...

const commonConfig = {
  // ...
  resolve: {
    extensions: ['.js', '.jsx'],
    mainFiles: ['index', 'list'],
    alias: {
      alias: path.resolve(__dirname, '../src/alias'),
    },
    modules: [
      path.resolve(__dirname, 'node_modules'), // 指定當(dāng)前目錄下的 node_modules 優(yōu)先查找
      'node_modules', // 將默認寫法放在后面
    ]
  },
  // ...
}
// ...
使用 resolve.alias 減少查找過程

alias 的意思為 別名,能把原導(dǎo)入路徑映射成一個新的導(dǎo)入路徑。

比如我們項目中可能會有一些相對路徑的寫法,就可以使用 alias 配置來減少查找過程;

還比如我們經(jīng)常使用的 react 庫,其實我們可以直接使用其 dist 目錄下打包好的 react.min.js,這樣就能跳過耗時的模塊解析,具體示例配置如下:

// config/webpack.common.js
// ...
const commonConfig = {
  // ...
  resolve: {
    // ...
    alias: {
      react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
      @alias: path.resolve(__dirname, '../src/alias'),
    },
  },
  // ...
}
// ...

這個筆者在實際項目中沒有嘗試過,不過也是一個思路,大家有機會可以嘗試一波。

?

縮小構(gòu)建目標(biāo)

排除 Webpack 不需要解析的模塊,即使用 loader 的時候,在盡量少的模塊中去使用。

我們可以借助 includeexclude 這兩個參數(shù),規(guī)定 loader 只在那些模塊應(yīng)用和在哪些模塊不應(yīng)用。

我們修改公共配置文件 webpack.common.js

// config/webpack.common.js
// ...
const commonConfig = {
  // ...
  module: {
    rules: [
      { 
        test: /\.js|jsx$/, 
        exclude: /node_modules/,
        include: path.resolve(__dirname, '../src'),
        use: ['babel-loader']
      },
      // ...
    ]
  },
}
// ...

首先我們不加 excludeinclude 兩個參數(shù),打包一下 npm run build,打包時間 3350ms 左右:

image

接著我們加上這兩個參數(shù),意思分別是:

  • exclude: /node_modules/:排除 node_modules 下面的文件
  • include: path.resolve(__dirname, '../src'):只對 src 下面的文件使用

重新打包一下,打包時間變成了 1400ms 左右:

image

?

利用多線程提升構(gòu)建速度

由于運行在 Node.js 之上的 webpack 是單線程模型的,所以 webpack 需要處理的事情需要一件一件的做,不能多件事一起做。

如果 webpack 能同一時間處理多個任務(wù),發(fā)揮多核 CPU 電腦的威力,那么對其打包速度的提升肯定是有很大的作用的。

HappyPack

原理:每次 webapck 解析一個模塊,HappyPack 會將它及它的依賴分配給 worker 線程中。處理完成之后,再將處理好的資源返回給 HappyPack 的主進程,從而加快打包速度。

image

webpack4.0 中使用 happypack 需要使用其 5.0 版本。

我們將 HappyPack 引入公共配置文件,他的用法就是將相應(yīng)的 loader 替換成 happypack/loader,同時將替換的 loader 放入其插件的 loaders 選項,我們暫且替換一下 babel-loader

// config/webpack.common.js
// ...
const makePlugins = (configs) => {
  const plugins = [
    // ...
    new HappyPack({
      loaders: ['babel-loader']
    }),
  ];
  // ...
  return plugins;
}
// ...

const commonConfig = {
  entry: {
    main: "./src/index.js",
    entry2: "./src/entry2.js",
    entry3: "./src/entry3.js",
    entry4: "./src/entry4.js",
    entry5: "./src/entry5.js",
    entry6: "./src/entry6.js",
  },
  // ...
  module: {
    rules: [{ 
      test: /\.jsx?$/, 
      // exclude: /node_modules/,
      // include: path.resolve(__dirname, '../src'), 
      use: [
        'happypack/loader'
        // 'babel-loader'
      ]
    }]
  },
  // ...
}
// ...

為了讓效果更加明顯一點,我們在項目下多增加幾個入口文件,在不使用 happypack 的情況下,進行一次打包,時間差不多是 8s 多:

image

開啟 happypack 之后,我們可以從控制臺中看到,happypack 默認幫我們開啟了 3 個進程,打包時間變成了6.5s 左右:

image

注意:HappyPack 的作者現(xiàn)在基本上也不維護這個插件了,因為作者對此項目的興趣正在減弱。他也推薦我們使用 webpack 官方 thread-loader。

更多參數(shù)大家可以參考 HappyPack 官網(wǎng)

thread-loader

webpack 官方推出的一個多進程方案,用來替代 HappyPack。

原理和 HappyPack 類似,webpack 每次解析一個模塊,thread-loader 會將它及它的依賴分配給 worker 線程中,從而達到多進程打包的目的。

使用很簡單,直接在我們使用的 loader 之前加上 thread-loader 就行,我們需要先注釋掉 HappyPack 代碼:

// config/webpack.common.js
// ...
const commonConfig = {
  // ...
  module: {
    rules: [{ 
      test: /\.jsx?$/, 
      // exclude: /node_modules/,
      // include: path.resolve(__dirname, '../src'), 
      use: [
        {
          loader: 'thread-loader',
          options: {
            workers: 3, // 開啟幾個 worker 進程來處理打包,默認是 os.cpus().length - 1
          }
        },
        'babel-loader'
      ]
    }]
  },
  // ...
}
// ...

我們重新運行一下,也是差不多 6.5s 左右:

image

預(yù)先編譯資源模塊(DllPlugin)

我們在打包的時候,一般來說第三方模塊是不會變化的,所以我們想只要在第一次打包的時候去打包一下第三方模塊,并將第三方模塊打包到一個特定的文件中,當(dāng)?shù)诙?webpack 進行打包的時候,就不需要去 node_modules 中去引入第三方模塊,而是直接使用我們第一次打包的第三方模塊的文件就行。

webpack.DllPlugin 就是來解決這個問題的插件,使用它可以在第一次編譯打包后就生成一份不變的代碼供其他模塊引用,這樣下一次構(gòu)建的時候就可以節(jié)省開發(fā)時編譯打包的時間。

添加配置文件

我們在配置文件目錄 config 下新建一個 webpack.dll.js,此文件用于將我們的第三方包文件打包到 dll 文件夾中去:

// config/webpack.dll.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production', // 環(huán)境
  entry: {
    vendors: ['lodash'], // 將 lodash 打包到 vendors.js 下
    react: ['react', 'react-dom'], // 將 react 和 react-dom 打包到 react.js 下
  },
  output: {
    filename: '[name].dll.js', // 輸出的名字
    path: path.resolve(__dirname, '../dll'), // 輸出的文件目錄
    library: '[name]' // 將我們打包出來的文件以全部變量的形式暴露,可以在瀏覽器變量的名字進行訪問
  },
  plugins: [
    // 對生成的庫文件進行分析,生成庫文件與業(yè)務(wù)文件的映射關(guān)系,將結(jié)果放在 mainfest.json 文件中
    new webpack.DllPlugin({
      name: '[name]', // 和上面的 library 輸出的名字要相同
      path: path.resolve(__dirname, '../dll/[name].manifest.json'),
    })
  ]
}
  • 上面的 library 的意思其實就是將 dll 文件以一個全局變量的形式導(dǎo)出出去,便于接下來引用,如下圖:
  • mainfest.json 文件是一個映射關(guān)系,它的作用就是幫助 webpack 使用我們之前打包好的 ***.dll.js 文件,而不是重新再去 node_modules 中去尋找。

我們在命令行中打包一下 dll 文件,可以看到根目錄生成了一個 dll 文件夾,并且在下面生成了相應(yīng)的文件,并且 loader 打包到了 vendor.dll.js 中,reactreact-dom 打包到了 react.dll.js 中了:

image

接著我們需要去修改公共配置文件 webpack.common.js,將我們之前生成的 dll 文件導(dǎo)入到 html 中去,如果我們不想自己手動向 html 文件去添加 dll 文件的時候,我們可以借助一個插件 add-asset-html-webpack-plugin,此插件顧名思義,就是將一些文件加到 html 中去。

同時我們需要使用 webpack 自帶的 DllReferencePlugin 插件對 mainfest.json 映射文件進行分析。

// config/webpack.common.js
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

// ...

const commonConfig = {
  // ...
  plugins: [
    // ...
    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
    }),
    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/react.dll.js')
    }),
    new webpack.DllReferencePlugin({
      manifest: require(path.resolve(__dirname, '../dll/vendors.dll.mainfest.json'))
    }),
    new webpack.DllReferencePlugin({
      manifest: require(path.resolve(__dirname, '../dll/react.dll.mainfest.json'))
    }),
  ],
  // ...
}
// ...

這里的代碼還可以優(yōu)化,具體大家可以參考筆者整理的筆記中 dll優(yōu)化 這一節(jié)。

我們進行一次打包,可以看到打包耗時為 1450ms 左右,同時可以看到庫文件打包到的 vendors.chunk.js1.22MB。

image

我們注釋掉對 dll 的引用分析之后,重新打包,打包耗時為 1950ms 左右,同時可以看到 vendors.chunk.js5.28MB。

image

緩存 Cache 相關(guān)

我們可以開啟相應(yīng) loader 或者 plugin 的緩存,來提升二次構(gòu)建的速度。一般我們可以通過下面幾項來完成:

如果項目中有緩存的話,在 node_modules 下會有相應(yīng)的 .cache 目錄來存放相應(yīng)的緩存。

babel-loader

首先我們開啟 babel-loader 的緩存,我們修改 babel-loader 的參數(shù),將參數(shù) cacheDirectory 設(shè)置為 true

// config/webpack.common.js
// ...
module: {
  rules: [
    { 
      test: /\.jsx?$/, 
      // exclude: /node_modules/,
      // include: path.resolve(__dirname, '../src'), 
      use: [
        {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          }
        },
      ]
    },
  ]
}         
// ...

首次打包時間為 8.5s 左右,打包完成之后,我們可以發(fā)現(xiàn)在 node_modules 下生成了一個 .cache 目錄,里面存放了 babel 的緩存文件:

image
image

我們重新打包一次,會發(fā)現(xiàn)時間變成了 6s 左右:

image
TerserPlugin

我們通過將 TerserPlugin 中的 cache 設(shè)為 true,就可以開啟緩存:

// config/webpack.common.js
const TerserPlugin = require('terser-webpack-plugin');
// ...
const commonConfig = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: 4, // 開啟幾個進程來處理壓縮,默認是 os.cpus().length - 1
        cache: true,
      }),
    ],
  },
  // ...
}

首次打包時間為 8-9s 左右,同時在 .cache 目錄下生成了 terser-webpack-plugin 緩存目錄:

image
image

我們重新打包一次,會發(fā)現(xiàn)時間變成了 5s 左右:

image
HardSourceWebpackPlugin

這個插件其實就是用于給模塊提供一個中間的緩存。

使用如下,我們直接在插件中引入就 ok 了:

// config/webpack.common.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
// ...
const plugins = [
  // ...
  new HardSourceWebpackPlugin(),
];
// ...

我們打包一下,可以看到在第一次打包的時候 HardSourceWebpackPlugin 就幫我們開始生成打包文件了,同時在 .cache 目錄生成了 hard-source 目錄,第一次打包耗時 6.6s 左右:

image
image

我們重新打包一次,會發(fā)現(xiàn)時間變成了 2.7s 左右:

image

合理使用 sourceMap

之前我們有講過,之前我們打包生成 sourceMap 的時候,如果信息越詳細,打包速度就會越慢

image

所以我們要在代碼打包過程中的時候,在對應(yīng)的環(huán)境使用對應(yīng)的 sourceMap 很重要。

?

其他

除了上述我們提到的一些常用方法,還有其他的一些方法,比如:

  • 使用 ES6 Modules 語法,以保證 Tree-Shaking 起作用

因為 tree-shaking 只對 ES6 Modules 靜態(tài)引入生效,對于類似于 CommonJs 的動態(tài)引入方式是無效的

  • 合理使用 Ployfill

如果我們對于引入的 polyfill 不做處理的話,Webpack 會把所有的 Polyfill 都加載進來,導(dǎo)致產(chǎn)出文件過大。推薦使用 @babel/preset-envuseBuiltIns='usage' 方案,此配置項會根據(jù)瀏覽器的兼容性幫助我們按需引入所需的墊片;此外我們也可以使用動態(tài) polyfill 服務(wù),每次根據(jù)瀏覽器的 User Agent,下發(fā)不同的 Polyfill,具體可以參考 polyfill.io。

  • 預(yù)加載資源 webpackPrefetch

使用 webpackPrefetch 來提前預(yù)加載一些資源,意思就是 將來可能需要一些模塊資源,在核心代碼加載完成之后帶寬空閑的時候再去加載需要用到的模塊代碼。

  • icon 類圖片使用 css Sprite 來合并圖片

如果 icon 類圖片太多的話,就使用雪碧圖合成一張圖片,減少網(wǎng)絡(luò)請求,或者使用字體文件。

  • html-webpack-externals-plugin

此插件可以將一些公用包提取出來使用 cdn 引入,不打入 bundle 中,從而減少打包文件大小,加快打包速度。

  • 合理配置 chunk 的哈希值

在生產(chǎn)環(huán)境打包,一定要配置文件的 hash,這樣有助于瀏覽器緩存我們的文件,當(dāng)我們的代碼文件沒變化的時候,用戶就只需要讀取瀏覽器緩存的文件即可。一般來說 javascript 文件使用 [chunkhash]css 文件使用 [contenthash]、其他資源(例如圖片、字體等)使用 [hash]。

更多性能優(yōu)化方法筆者就不再一一列舉了,因為關(guān)于 webpack 性能優(yōu)化的方法實在是太多了,大家可以根據(jù)實際遇到的問題制定相關(guān)的優(yōu)化方案。

?

小結(jié)

今天這篇文章介紹了 webpack 打包的一些優(yōu)化方案,從項目體積再到對項目速度,我們提出了一些優(yōu)化方案,大家可以在具體的項目中去進行實踐。

當(dāng)然我還要提一嘴,如果你的項目本身構(gòu)建就比較快,那么你其實就不需要使用文章中提到的方法去對項目進行優(yōu)化,可能效果會適得其反。

對于文章中有些一筆帶過的內(nèi)容,大家可以在我的 webpack 學(xué)習(xí)整理文檔 找到相應(yīng)的介紹。

實不相瞞,想要個贊!

image

?

相關(guān)鏈接

?

示例代碼

示例代碼可以看這里:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容