使用 HappyPack 和 DllPlugin 來提高你的 Webpack 構建速度

使用 HappyPack 和 DllPlugin 來提高你的 Webpack 構建速度

@(Blogs)[webpack, Front-End]javascript

本文原文發表在:medium.com/@Erichain/%…
本文采用的 Webpack 版本爲 2.0+
本文源代碼地址:github.com/Erichain/we…css

若是你問我對 Webpack 什麼印象的話,我只能告訴你,慢,真的慢。即便他的配置如文檔所說(固然,它的文檔也不是那麼好)很簡單,不像 Grunt 或者 Gulp 那樣須要一堆配置,只需那麼幾十行就可以配置一個構建系統,我依然以爲,這個構建工具很慢。或許,是從它的文檔開始,我就印象很差了?OK,這個話題到此爲止,咱們開始咱們的正題吧。java

本篇文章面向的不是 Webpack 新手,若是你對 Webpack 還不太熟悉的話,建議去閱讀它的官方文檔。固然,咱們確定也會涉及到一些基礎的東西。react

本文重點講解對生產環境的構建的性能的提高,若是須要對本地構建的性能進行提高的話,能夠在本文結束以後,本身尋找一下解決方案哦。固然,還有一點須要說明的是,本文中的代碼在本地不必定真正可以在瀏覽器中運行,有須要的能夠自行搭建本地的構建系統。webpack


一點基礎

使用過 Webpack 的朋友確定知道,Webpack 的最簡單的配置以下:git

module.exports = {
  entry: {
    app: './src/app.js'
  },

  output: {
    path: path.join(__dirname, 'dist-[hash]'),
    filename: '[name].[hash].js'
  }
};複製代碼

這樣的配置會將咱們的文件打包成爲一個 app.[hash].js 文件。這樣針對的通常是咱們的項目不算大的狀況,而且公用模塊比較少的狀況(固然,公用模塊較多的話,配置確定也不會這麼簡單了)。github

對於項目中有用到預處理器,ES2015+ 或者其他的須要編譯後在瀏覽器上運行的語言,咱們須要作的就是爲這些東西添加上對應的 loader,而後,Webpack 就會自動的幫咱們進行處理了(老實說,這一步仍是挺方便的)。web

一些 loader 配置示例以下:npm

rules: [
  {
    test: /\.jsx?$/,
    loader: ['babel-loader?presets[]=react,presets[]=latest&compact=false'],
  }, {
    test: /\.scss$/,
    loader: [
      'style-loader',
      'css-loader',
      'postcss-loader',
      'sass-loader'
    ],
  }, {
    test: /\.jpe?g|png|svg|gif/,
    loader: ['url-loader?limit=8192&name=assets/images/[name]-[hash].[ext]'],
  }
]複製代碼

另外,咱們還能夠經過一些插件來更多的定義 Webpack 的打包行爲。好比,若是咱們有不少第三方庫的引用,而且,多個地方都會引用到這些庫,咱們就可使用 Webpack 的 CommonsChunkPlugin 來將這些公用的代碼打包成一個文件(固然,至於速度嘛,咱們後面再說),而後,將咱們頁面的業務代碼打包成爲一個文件。json

Webpack 的主要配置就這幾項,其餘更多的更深刻的配置能夠查看 Webpack 的官方文檔

速度慢

儘管 Webpack 配置起來很方便,可是,按照通常的配置來的話,構建的速度真的是太慢了,每構建一次都會花掉至關長的時間,這對於開發者們來講簡直是噩夢。

但是,速度爲何會這麼慢呢?

以我所在的項目爲例,因爲咱們的項目存在多個 entries(大概四十多個),因此,咱們的 Webpack 採用的配置是將公用的第三方庫經過 CommonsChunkPlugin 來打包成爲一個 common.js

根據這個 common.js 的內容來看,這裏面存放的就是各個 entry 引用的公有的代碼,好比,咱們的不少組件都會用到 React 或者 Redux 這些第三方庫。經過將公有的代碼單獨打包成一個文件,而後再將業務代碼打包成一個文件,這樣一來,業務代碼模塊自己的體積就會減少不少,頁面的加載速度也可以獲得很大的提高。

雖然這樣打包的方式可以在必定程度上提高頁面的加載速度,可是,咱們簡單的想想也知道,CommonsChunkPlugin 會去將全部 entry 中的公有模塊遍歷出來再進行編譯壓縮混淆,這個過程是很是緩慢的(咱們的項目之前在使用這種方式的時候,在這一步會花上至少十二分鐘的時間,你能夠想象這個過程有多麼漫長)。

通過了幾個迭代的痛苦的打包上線的過程以後,咱們終於不能忍了,決定對這個構建系統進行改造。

改造的過程

說實話,一開始我實際上是沒有任何頭緒的,我只知道這個構建的過程慢,可是,並不清楚應該從何處開始進行改造。

與同事們進行了一些商討以後,我準備從如下幾個方面入手:

  • 減小構建的文件,減少文件大小:咱們的項目中存在太多的無用的文件和代碼,我決定先刪除這些無用的東西
  • 移除 CommonsChunkPlugin
  • Search with Google

第一步的做用其實並不明顯,我刪除了很大一部分的無用的圖片和代碼,可是,構建速度並無明顯的提高。

第二步,簡單的移除掉 CommonsChunkPlugin 的話,構建速度確實會快不少,可是,這樣打包出來的項目就不可以運行了,因此,還須要結合第三步(必需要感謝這個世界存在 Google)。

我在網上找到了許多相關的問題,關鍵性的建議有如下幾個:

  • css-loader 的版本回溯到 0.15 及其之前的版本
  • 使用 HappyPack
  • 使用 DllPlugin

首先,第一點,下降 css-loader 的版本。

在 GitHub 上有這樣一個 issue:0.15.0+ makes Webpack load slowly。按照 issue 中你們的討論,我將咱們項目中的 css-loader 的版本降到了 0.14.5。滿懷期待的覺得這樣就可以提高一部分速度,可是,結果是使人失望的——構建的速度並無明顯的改變。我試着構建了好幾遍,速度依然沒有提高,因此,第一個方法失敗,我將 css-loader 的版本恢復了回來。

那麼,繼續嘗試第二個方法,也是本文將要重點說明的方法之一,那就是使用 HappyPack。

使用 HappyPack

HappyPack 容許 Webpack 使用 Node 多線程進行構建來提高構建的速度。

使用的方法與在 Webpack 中定義 loader 的方法相似,只是說,咱們把構建須要的 loader 放到了 HappyPack 中,讓 HappyPack 來爲咱們進行相應的操做,咱們只須要在 Webpack 的配置中引入 HappyPack 的 loader 的配置就行了。

好比,咱們編譯 .jsx 文件的 loader 就能夠這樣寫:

new HappyPack({
  id: 'jsx',
  threads: 4,
  loaders: ['babel-loader?presets[]=react,presets[]=latest&compact=false'],
})複製代碼

其中,threads 指明 HappyPack 使用多少子進程來進行編譯,通常設置爲 4 爲最佳。

編譯 .scss 文件的 loader 這樣寫:

new HappyPack({
  id: 'scss',
  threads: 4,
  loaders: [
    'style-loader',
    'css-loader',
    'postcss-loader',
    'sass-loader',
  ],
})複製代碼

其中,須要注意的一點就是,在使用 HappyPack 的狀況下,咱們須要單首創建一個 postcss.config.js 文件,否則,在編譯的時候,就會報錯。

因爲 HappyPack 對 url-loaderfile-loader 的支持度的問題,因此,咱們此處,打包圖片文件的時候,並無使用 HappyPack。

postcss.config.js 的配置就像下面這樣(根據你的需求,定製你本身的配置):

module.exports = {
  autoprefixer: {
    browsers: ['last 3 versions'],
  }
};複製代碼

定義好了咱們 HappyPack 的 loader 以後,咱們直接在咱們的 Webpack 的配置的 plugins 一項中,引入就行了。

那麼,咱們在編譯的時候,就會看到下面的輸出:

@HappyPack 輸出|center
@HappyPack 輸出|center

這就是 HappyPack 在編譯的時候的輸出內容。

可是,咱們的關注點不是它輸出了什麼,而是說,咱們的構建速度有沒有提高。

固然,結果是使人失望的,咱們單獨使用 HappyPack 的狀況下,構建速度並無明顯的提高(固然,或許有所提高可是我沒有發現也有可能)。

因此,爲了進一步的提高咱們的構建速度,咱們將採起第三種方案,那就是 DllPlugin。

使用 DllPlugin

仔細閱讀過 Webpack 文檔的朋友確定對這個插件會有印象,或者說知道這個插件是幹嗎用的。其實,咱們此處也是基於 Webpack 的文檔的一些說明,而後,結合我在項目中的實踐來爲你們講解這個插件。

在 Webpack 中,DllPlugin 並非單獨的使用的,而是須要與一個名爲 DllReferencePlugin 的插件結合起來使用的。

熟悉 Windows 的朋友就應該知道,DLL 所表明的含義。在 Windows 中,有大量的 .dll 文件,稱爲動態連接庫。

MSDN 上,微軟是這樣解釋動態連接庫的:

A dynamic-link library (DLL) is a module that contains functions and data that can be used by another module (application or DLL).

大概的意思就是說,動態連接庫包含的是,能夠在其餘模塊中進行調用的函數和數據。

文檔裏面還有一句話是這樣說的:

DLLs provide a way to modularize applications so that their functionality can be updated and reused more easily.

動態連接庫提供了將應用模塊化的方式,應用的功能能夠在此基礎上更容易被複用。

回到咱們的項目中,相似的,咱們其實要作的也是將各個模塊中公用的部分給打包成爲一個公用的模塊。這個模塊就包含了咱們的其餘模塊中須要的函數和數據(好比,其餘組件所需的 React 庫)。

使用 DllPlugin 的時候,會生成一個 manifest.json 這個文件,所存儲的就是各個模塊和所需公用模塊的對應關係。

說了這麼多,咱們不如直接來看看這個插件究竟是怎麼使用的:

首先,咱們須要一個文件,這個文件包含全部的第三方或者公用的模塊和庫,咱們在此將其命名爲 vendor.js,文件的內容以下:

import 'react';
import 'react-dom';複製代碼

因爲咱們的示例項目中只用到了這兩個公用的第三方庫,因此,咱們此處只須要引入這兩個庫就好了。

在打包的時候,咱們將這些公用的模塊單獨打包成一個文件,而後,經過生成的 manifest.json 文件對應過去。因此,咱們須要單首創建一個 webpack.config.vendor.js

文件內容其實很簡單:

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

module.exports = {
  entry: {
    vendor: [path.join(__dirname, 'src', 'vendor.js')],
  },

  output: {
    path: path.join(__dirname, 'dist-[hash]'),
    filename: '[name].js',
    library: '[name]',
  },

  plugins: [
    new webpack.DllPlugin({
      path: path.join(__dirname, 'dll', '[name]-manifest.json'),
      filename: '[name].js',
      name: '[name]',
    }),
  ]
};複製代碼

能夠看到,咱們主要的操做是在 plugins 配置中,生成的文件名就是咱們所定義的 entry 的名稱,JSON 文件名能夠根據本身的須要來命名。像上面這樣,咱們就能夠將咱們的一些公用模塊打包出來了。

運行如下命令:

webpack -p --progress --config webpack.config.vendor.js複製代碼

咱們就能夠看到這樣的輸出:

@DllPlugin 打包輸出|center
@DllPlugin 打包輸出|center

這樣,咱們就完成了構建的第一步。下一步,咱們須要在構建應用的配置文件中,加入咱們的 DllPlugin 的配置。

這時候,咱們就須要用到 DllReferencePlugin 了。

在咱們的主要配置文件中,加入如下的配置:

const manifest = require('./dll/vendor-manifest.json');

// ... 其餘完美的配置

plugins: [
  new webpack.DllReferencePlugin({
    manifest,
  }),
],複製代碼

就這樣,咱們的全部工做就完成了,咱們只須要運行一條命令,就可以看到構建速度的巨大提高。

固然,爲了更完美,咱們能夠將 DllPlugin 和 HappyPack 結合起來使用,效果會更好。具體的代碼細節,此處不予展現,朋友們能夠直接去 GitHub 上查看。

爲了方便構建,咱們能夠寫一個腳本將構建過程簡單化。在個人 GitHub 項目裏面有相關的腳本,包含了一些基礎的操做,有須要的朋友能夠去查看。此處,咱們就認爲咱們的命令能夠直接構建了。

爲了體現出構建速度的區別,咱們先運行 npm run build,這是採用普通方式進行構建的命令。

@採用普通構建方式的構建時間|center
@採用普通構建方式的構建時間|center

能夠看到,構建時間爲 20353ms,換算下來爲 20s 左右。

接下來,咱們運行 npm run build dll,經過 DllPlugin 和 HappyPack 進行構建。

@構建 vendor.js 文件的時間|center
@構建 vendor.js 文件的時間|center

@構建 app.js 文件的時間|center
@構建 app.js 文件的時間|center

咱們將兩個時間加起來,總共爲 12184ms,換算下來爲 12s 左右。快了將近一倍的時間!這還只是文件少的狀況。在咱們的實際項目中,構建時間提高了 3 倍多,因此,能夠看到 DllPlugin 的強大之處。

一點總結

本文只是尋找了這樣幾種可以提高構建速度的解決方案,我相信,方法確定不止這些,必定還有更多的解決方案等待咱們去發現。因此,但願各位朋友可以對本文中不足的地方提出建議,但願與你們共同窗習,共同進步。


References

OPTIMIZING WEBPACK FOR FASTER REACT BUILDS

Optimizing Webpack build times and improving caching with DLL bundles

Dynamic-Link Libraries

相關文章
相關標籤/搜索