webpack構建速度優化


webpack打包速度優化

前言

Webpack打包優化並沒有什么固定的模式,一般我們常見的就是緩存多進程抽離拆分

一、分析打包速度

優化webpack構建速度的第一步就是知道時間花費在哪里,才可以集中的進行針對性的優化。

這邊我們用到speed-measure-webpack-plugin插件。

// 分析打包時間
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// ...
module.exports = smp.wrap(prodWebpackConfig)

二、開銷時間分析

如下是未優化前通過speed-measure-webpack-plugin測量插件生成的數據分析:

 SMP  ⏱  
General output time took 14 mins, 14.33 secs

 SMP  ⏱  Plugins
ExtractTextPlugin took 6 mins, 50.99 secs
PurifyPlugin took 6 mins, 11.47 secs
UglifyJsPlugin took 6 mins, 10.98 secs
NgcWebpackPlugin took 3 mins, 6.12 secs
LoaderOptionsPlugin took 35.55 secs
HtmlWebpackPlugin took 6.24 secs
ModuleConcatenationPlugin took 0.582 secs
HashedModuleIdsPlugin took 0.108 secs
CommonsChunkPlugin took 0.057 secs
Object took 0.031 secs
ScriptExtHtmlWebpackPlugin took 0.016 secs
HtmlElementsPlugin took 0.015 secs
DefinePlugin took 0.012 secs
InlineManifestPlugin took 0.01 secs
ProvidePlugin took 0.009 secs

 SMP  ⏱  Loaders
raw-loader took 6 mins, 17.56 secs
  module count = 1688
css-loader took 6 mins, 17.053 secs
  module count = 1291
css-loader, and 
sass-loader took 4 mins, 53.45 secs
  module count = 402
modules with no loaders took 3 mins, 20.57 secs
  module count = 1736
file-loader took 1 min, 12.41 secs
  module count = 13
@ngtools/webpack took 50.12 secs
  module count = 3487
to-string-loader, and 
css-loader took 14.66 secs
  module count = 1285
json-loader took 4.9 secs
  module count = 3
to-string-loader, and 
css-loader, and 
sass-loader took 3.83 secs
  module count = 402
extract-text-webpack-plugin, and 
style-loader, and 
css-loader took 0.54 secs
  module count = 4
html-webpack-plugin took 0.018 secs
  module count = 1

speed-measure-webpack-plugin插件打包的時間分析可知,花銷的總時間達到General output time took 14 mins, 14.33 secs14分鍾之多,主要開銷在loader的一些解析和編譯操作上。

三、緩存處理

1. 通過cache-loader進行緩存處理

從開銷時間來看,主要對開銷較大的loader進行緩存操作。這邊使用到cache-loader進行緩存操作。

⚠️ 請注意,保存和讀取這些緩存文件會有一些時間開銷,所以請只對性能開銷較大的 loader 使用此 loader。

module: {
	rules: [
		...ngcWebpackConfig.loaders,
		{
			test: /\.css$/,
			use: ['cache-loader', 'to-string-loader', 'css-loader'], // use loader是從右至左執行的,							因此必須放在執行的最后一個,也就是配置的第一個loader['cache-loader',...loaders]
			exclude: [helpers.root('src', 'styles')]
		},
		{
			test: /\.scss$/,
			use: ['cache-loader', 'to-string-loader', 'css-loader', 'sass-loader'],
			exclude: [helpers.root('src', 'styles')] // to-string-loader、raw-loader、file-loader進行緩存存在一些問題,實際中不適用
		},
		{
			test: /\.html$/,
			use: ['cache-loader', 'raw-loader'],
			exclude: [helpers.root('src/index.html')]
		},
		{
			test: /\.(jpg|png|gif)$/,
			use: ['cache-loader', 'file-loader']
		},
		{
			test: /\.(eot|woff2?|svg|ttf)([\?]?.*)$/,
			use: ['cache-loader', 'file-loader']
		}
	]
}

2. UglifyJsPlugin開啟壓縮JavaScript代碼

uglifyjs-webpack-plugin插件也是我們常用用來壓縮JavaScript代碼的,在這個插件下也有一個參數用來配置緩存處理,也可以在壓縮代碼的時候生成緩存,以便在下次壓縮代碼的時候直接在緩存中取而不必重新壓縮。

new UglifyJsPlugin({
	cache: true // 開啟壓縮js代碼緩存
})

⚠️ 請注意使用cache-loader之后,我們再一次打包看看效果如何。因為第一次是沒有生成緩存文件的,因此使用cache-loader要在打包之后緩存過后的第一次打包,也就是加了cache-loader之后的第二次打包才會生成緩存文件。

// 加了cache-loade, cache: true之后的第一次打包(生成緩存文件,但是第一次不走緩存,開銷變大,因為加了一些cache-loader的解析和編譯) 由原來的14分鍾上升到17分鍾。
 SMP  ⏱  
General output time took 17 mins, 15.61 secs

 SMP  ⏱  Plugins
ExtractTextPlugin took 9 mins, 21.44 secs
PurifyPlugin took 8 mins, 39.02 secs
UglifyJsPlugin took 8 mins, 38.5 secs
NgcWebpackPlugin took 3 mins, 20.037 secs
LoaderOptionsPlugin took 38.75 secs
HtmlWebpackPlugin took 6.41 secs
ModuleConcatenationPlugin took 0.655 secs
HashedModuleIdsPlugin took 0.107 secs
CommonsChunkPlugin took 0.059 secs
Object took 0.032 secs
HtmlElementsPlugin took 0.016 secs
ScriptExtHtmlWebpackPlugin took 0.016 secs
InlineManifestPlugin took 0.011 secs
ProvidePlugin took 0.01 secs
DefinePlugin took 0.008 secs

 SMP  ⏱  Loaders
cache-loader, and 
raw-loader took 6 mins, 33.38 secs
  module count = 1688
cache-loader, and 
to-string-loader, and 
css-loader took 6 mins, 20.15 secs
  module count = 1285
css-loader took 6 mins, 16.26 secs
  module count = 1291
css-loader, and 
sass-loader took 4 mins, 49.26 secs
  module count = 402
modules with no loaders took 4 mins, 25.23 secs
  module count = 1736
cache-loader, and 
to-string-loader, and 
css-loader, and 
sass-loader took 4 mins, 6.97 secs
  module count = 402
@ngtools/webpack took 48.6 secs
  module count = 3487
cache-loader, and 
file-loader took 14.78 secs
  module count = 13
json-loader took 7.36 secs
  module count = 3
extract-text-webpack-plugin, and 
style-loader, and 
css-loader took 0.528 secs
  module count = 4
html-webpack-plugin took 0.019 secs
  module count = 1
// 第二次構建,走緩存。花銷時間為8分鍾40s,可以看到是有很大的提升的。
 SMP  ⏱  
General output time took 8 mins, 40.22 secs

 SMP  ⏱  Plugins
NgcWebpackPlugin took 3 mins, 20.82 secs
ExtractTextPlugin took 42.63 secs
LoaderOptionsPlugin took 38.24 secs
HtmlWebpackPlugin took 11.55 secs
UglifyJsPlugin took 1.74 secs
PurifyPlugin took 1.012 secs
ModuleConcatenationPlugin took 0.637 secs
HashedModuleIdsPlugin took 0.1 secs
CommonsChunkPlugin took 0.045 secs
Object took 0.034 secs
HtmlElementsPlugin took 0.015 secs
ScriptExtHtmlWebpackPlugin took 0.013 secs
InlineManifestPlugin took 0.01 secs
ProvidePlugin took 0.005 secs
DefinePlugin took 0.005 secs

 SMP  ⏱  Loaders
cache-loader, and 
raw-loader took 6 mins, 31.28 secs
  module count = 1688
cache-loader, and 
to-string-loader, and 
css-loader took 6 mins, 25.46 secs
  module count = 1285
css-loader took 6 mins, 19.084 secs
  module count = 1291
css-loader, and 
sass-loader took 4 mins, 46.74 secs
  module count = 402
cache-loader, and 
to-string-loader, and 
css-loader, and 
sass-loader took 4 mins, 14.074 secs
  module count = 402
modules with no loaders took 4 mins, 0.806 secs
  module count = 1736
@ngtools/webpack took 49.95 secs
  module count = 3487
cache-loader, and 
file-loader took 18.81 secs
  module count = 13
json-loader took 5.15 secs
  module count = 3
extract-text-webpack-plugin, and 
style-loader, and 
css-loader took 0.411 secs
  module count = 4
html-webpack-plugin took 0.016 secs
  module count = 1

四、開啟多進程

1. 使用happypack開啟多進程解析和處理。

在使用 Webpack 對項目進行構建時,會對大量文件進行解析和處理。當文件數量變多之后,Webpack 構件速度就會變慢。由於運行在 Node.js 之上的 Webpack 是單線程模型的,所以 Webpack 需要處理的任務要一個一個進行操作。

而 Happypack 的作用就是將文件解析任務分解成多個子進程並發執行。子進程處理完任務后再將結果發送給主進程。所以可以大大提升 Webpack 的項目構件速度

cnpm i happypack --save-dev // 安裝happypack

// webpack.config.js
const HappyPack = require('happypack'); // 通過node CommonJs模塊導入
const os = require('os');

plugins: [
  new HappyPack({
    id: 'css',
    threads: os.cpus().length,
    loaders: ['to-string-loader', 'css-loader']
  }),
	new HappyPack({
    id: 'scss', // 定義id為需開啟happypack解析的loader進行標記
    threads: os.cpus().length, // 開啟操作系統cpu的最大核心數
    loaders: ['to-string-loader', 'css-loader', 'sass-loader']
  })
],
module: {
  rules: [
    {
      test: /\.css$/,
      use: 'happypack/loader?id=css' // id=css就是上述happypack定義的id
    },
    {
      test: /\.scss$/,
      use: 'happypack/loader?id=scss' 
    }
  ]
}

2. 同時使用cache-loader和happypack進行打包優化。

// 要同時使用cache-loader和happypack,只需要在happypack插件中的loaders配置把cache-loader加載配置最前頭就可以,如下。
plugins: [
	new HappyPack({
    id: 'scss', // 定義id為需開啟happypack解析的loader進行標記
    threads: os.cpus().length, // 開啟操作系統cpu的最大核心數
    loaders: ['cache-loader', 'style-loader', 'css-loader', 'sass-loader'] // cache-loader最前
  })
],
module: {
  rules: [
    {
      test: /\.scss$/,
      use: 'happypack/loader?id=scss'
    }
  ]
}

3. UglifyJsPlugin開啟多進程壓縮JavaScript代碼

new UglifyJsPlugin({
	cache: true,
	parallel: true // 開啟多進程壓縮js代碼
})
// 同時使用緩存和多進程,可以看到進一步的提升
 SMP  ⏱  
General output time took 7 mins, 35.03 secs

 SMP  ⏱  Plugins
NgcWebpackPlugin took 3 mins, 17.25 secs
ExtractTextPlugin took 43.79 secs
LoaderOptionsPlugin took 39.64 secs
HtmlWebpackPlugin took 4.98 secs
UglifyJsPlugin took 1.83 secs
PurifyPlugin took 1.018 secs
ModuleConcatenationPlugin took 0.717 secs
HappyPlugin took 0.364 secs
Object took 0.117 secs
HashedModuleIdsPlugin took 0.108 secs
CommonsChunkPlugin took 0.051 secs
HtmlElementsPlugin took 0.016 secs
ScriptExtHtmlWebpackPlugin took 0.015 secs
InlineManifestPlugin took 0.012 secs
ProvidePlugin took 0.008 secs
DefinePlugin took 0.008 secs

 SMP  ⏱  Loaders
happypack took 5 mins, 30.95 secs
  module count = 1687
cache-loader, and 
raw-loader took 6 mins, 28.28 secs
  module count = 1688
modules with no loaders took 4 mins, 15.065 secs
  module count = 1736
css-loader, and 
sass-loader took 2 mins, 31.25 secs
  module count = 402
@ngtools/webpack took 49.56 secs
  module count = 3487
css-loader took 28.089 secs
  module count = 1291
cache-loader, and 
file-loader took 17.45 secs
  module count = 13
json-loader took 6.14 secs
  module count = 3
extract-text-webpack-plugin, and 
style-loader, and 
css-loader took 0.509 secs
  module count = 4

五、抽離和外部引用Externals

項目中的抽離一般把不常改變的第三方依賴通過動態鏈接庫抽離出來,再去引用,這樣可以解決從動態鏈接庫直接引用,減少了第三方包的打包時間,從而提高打包速度。

這邊用到的插件是DllPluginDLLReferencePlugin

  1. 使用DllPlugin需要先創建一個webpack.dll.config.js
  2. 文件寫入需要抽離的第三方包,如下的vendor: []
// webpack.dll.config.js
module.exports = {
  entry: {
    vendor: ["immutable", "ng-drag-drop", "ng-zorro-antd", "ngx-amap", "ngx-clipboard", "ngx-color-picker", "ngx-echarts", "ngx-infinite-scroll", "ngx-ueditor", "ngx-umeditor"]
  },
  output: {
    path: path.resolve(__dirname, '../dll'),
    filename: '[name].dll.js',
    library: '[name]_[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.resolve(__dirname, '../dll', 'manifeset.json'),
      name: '[name]_[hash]'
    })
  ]
}

// package.json中配置
{
	"scripts": {
  	"build:dll": "npm run webpack -- --config config/webpack.dll.config.js --progress",
  }
}
  
// 執行npm run build:dll生成vendor.dll.js和manifeset.json文件
  1. 在webpack.prod.js生產配置文件中通過DLLReferencePlugin去引用對應的生成的manifeset.json文件
// webpack.prod.js
plugins: [
  new webpack.DllReferencePlugin({
    manifest: require('../dll/manifeset.json'),
  }) 
]
  1. 也可以使用externals外部擴展,來從輸出的bundle中排除依賴。然后在index.html通過script標簽引用。這個不僅可以提升打包的速度,也可以縮小bundle的體積,從而提升http請求的速度來提高首屏渲染速度。
// webpack.prod.js
module.exports = {
  externals: {
    jquery: 'jQuery',
    ...obj
  }
}
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>externals</title>
  <script src="https://code.jquery.com/jquery-3.1.0.js"></script>
</head>
<body>
  
</body>
</html>
// index.js

import $ from 'jquery';

console.log($); // 輸出成功,代表配置成功
  • ⚠️但是要注意的是,在使用Externals的時候,如果我們更新了我們的代碼依賴包的話,比如說通過npm undate <package name>更新的話,我們還要把我們的第三方依賴包更新一下版本在通過script去引入,比較麻煩。可以通過一些webpack插件去自動生成CDN Link Script標簽。(有興趣的話可以繼續去了解)。
  • ⚠️還有一點是使用DllPlugin生成動態鏈接庫的時候,在我們更新第三方依賴的時候,如果涉及到我們抽離出來的第三方包的話,我們首先要執行npm run build:dll重新生成manifest.json,才不會出錯。

六、微前端實踐

管理后台項目較大,每次打包的時候都需要把管理后台重新打包,時間話費巨大。

因此可以把每一個子模塊都被拆分成了一個單獨的repo,通過微前端架構實踐通過portal配置不同的模塊入口。來把不同模塊的分開打包,最后生成一個整體的代碼結構。有興趣的可以去看看。

需要考慮的是:

  • 如何解決路由的問題
  • 公共模塊如何維護的問題
  • ......等一些問題

七、總結

綜上所述,一般構建優化優化措施的兩個大方向:緩存(cache-loader)、多線程(happy-pack)。緩存是為了讓二次構建時,不再去做重復的工作;而多核,是利用了硬件本身的優勢,讓我們充分發揮出cpu的性能。當然隨着webpack的發展,會涌現出更多的優化插件以及方向。


免責聲明!

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



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