當前位置:
首頁 > 知識 > Webpack構建速度優化

Webpack構建速度優化

如何輸出Webpack構建分析

  • 輸出Webpack構建信息的.json文件:webpack --profile --json > starts.json
  1. --profile:記錄構建中的耗時信息
  2. --json:以json格式輸出構建結果,最後只輸出一個json文件(包含所有的構建信息)
  • web可視化查看構建分析:得到了webpack構建信息文件starts.json,如何進行很好的可視化查看?

1. 方案一:通過可視化分析工具Webpack Analyse,是個在線Web應用,上傳starts.json文件就可以;不過好像需要翻牆;

2. 方案二:安裝webpack-bundle-analyzer工具npm i -g webpack-bundle-analyzer,生成starts.json後直接在其文件夾目錄執行webpack-bundle-analyzer後,瀏覽器會打開對應網頁並展示構建分析文檔地址webpack-bundle-analyzer

3. webpack-dashboard是一款統計和優化webpack日誌的工具,可以以表格形勢展示日誌信息。其中包括構建過程和狀態、日誌以及涉及的模塊列表

4. jarvis是一款基於webapck-dashboard的webpack性能分析插件,性能分析的結果在瀏覽器顯示,比webpack-bundler-anazlyer更美觀清晰GitHub文檔地址

  • npm i -D webpack-jarvis
  • webpack.config.js配置:

const Jarvis = require("webpack-jarvis");
plugins: [
new Jarvis({
watchOnly: false,
port: 3001 // optional: set a port
})
];

  • port:監聽的埠,默認1337,監聽面板將監聽這個埠,通常像http://localhost:port/
  • host:域名,默認localhost,不限制域名。
  • watchOnly:僅僅監聽編譯階段。默認為true,如果高為false,jarvis不僅僅運行在編譯階段,在編譯完成後還保持運行狀態。
  • 界面:看到構建時間為:Time: 11593ms(作為優化時間對比)

Webpack構建速度優化

webpack配置優化

  • webpack在啟動時會從配置的Entry出發,解析出文件中的導入語句,再遞歸解析。
  • 對於導入語句Webpack會做出以下操作:

1. 根據導入語句尋找對應的要導入的文件;

2. 在根據要導入的文件後綴,使用配置中的Loader去處理文件(如使用ES6需要使用babel-loader處理)

  • 針對這兩點可以優化查找途徑

1. 優化Loader配置

  • Loader處理文件的轉換操作是很耗時的,所以需要讓儘可能少的文件被Loader處理

{
test: /.js$/,
use: [
"babel-loader?cacheDirectory",//開啟轉換結果緩存
],
include: path.resolve(__dirname, "src"),//只對src目錄中文件採用babel-loader
exclude: path.resolve(__dirname," ./node_modules"),//排除node_modules目錄下的文件
},

2. 優化resolve.modules配置

  • resolve.modules用於配置webpack去哪些目錄下尋找第三方模塊,默認是["node_modules"],但是,它會先去當前目錄的./node_modules查找,沒有的話再去../node_modules最後到根目錄;
  • 所以當安裝的第三方模塊都放在項目根目錄時,就沒有必要安默認的一層一層的查找,直接指明存放的絕對位置

resolve: {
modules: [path.resolve(__dirname, "node_modules")],
}

3. 優化resolve.extensions配置

  • 在導入沒帶文件後綴的路徑時,webpack會自動帶上後綴去嘗試詢問文件是否存在,而resolve.extensions用於配置嘗試後綴列表;默認為extensions:["js","json"];
  • 及當遇到require("./data")時webpack會先嘗試尋找data.js,沒有再去找data.json;如果列表越長,或者正確的後綴越往後,嘗試的次數就會越多
  • 所以在配置時為提升構建優化需遵守:

1. 頻率出現高的文件後綴優先放在前面;

2. 列表儘可能的小;

3. 書寫導入語句時,盡量寫上後綴名

  • 因為項目中用的jsx較多,所以配置extensions: [".jsx",".js"],
  • 基本配置後查看構建速度:Time: 10654ms;配置前為Time: 11593ms

使用DllPlugin優化

  • 在使用webpack進行打包時候,對於依賴的第三方庫,如react,react-dom等這些不會修改的依賴,可以讓它和業務代碼分開打包;
  • 只要不升級依賴庫版本,之後webpack就只需要打包項目業務代碼,遇到需要導入的模塊在某個動態鏈接庫中時,就直接去其中獲取;而不用再去編譯第三方庫,這樣第三方庫就只需要打包一次。
  • 接入需要完成的事:

1. 將依賴的第三方模塊抽離,打包到一個個單獨的動態鏈接庫中

2. 當需要導入的模塊存在動態鏈接庫中時,讓其直接從鏈接庫中獲取

3. 項目依賴的所有動態鏈接庫都需要被載入

  • 接入工具(webpack已內置)

1. DllPlugin插件:用於打包出一個個單獨的動態鏈接庫文件;

2. DllReferencePlugin:用於在主要的配置文件中引入DllPlugin插件打包好的動態鏈接庫文件

  • 配置webpack_dll.config.js構建動態鏈接庫

const path = require("path");
const DllPlugin = require("webpack/lib/DllPlugin");
module.exports = {
mode: "production",
entry: {
// 將React相關模塊放入一個動態鏈接庫
react: ["react","react-dom","react-router-dom","react-loadable"],
librarys: ["wangeditor"],
utils: ["axios","js-cookie"]
},
output: {
filename: "[name]-dll.js",
path: path.resolve(__dirname, "dll"),
// 存放動態鏈接庫的全局變數名,加上_dll_防止全局變數衝突
library: "_dll_[name]"
},
// 動態鏈接庫的全局變數名稱,需要可output.library中保持一致,也是輸出的manifest.json文件中name的欄位值
// 如react.manifest.json欄位中存在"name":"_dll_react"
plugins: [
new DllPlugin({
name: "_dll_[name]",
path: path.join(__dirname, "dll", "[name].manifest.json")
})
]
}

  • webpack.pro.config.js中使用

const DllReferencePlugin = require("webpack/lib/DllReferencePlugin");
...
plugins: [
// 告訴webpack使用了哪些動態鏈接庫
new DllReferencePlugin({
manifest: require("./dll/react.manifest.json")
}),
new DllReferencePlugin({
manifest: require("./dll/librarys.manifest.json")
}),
new DllReferencePlugin({
manifest: require("./dll/utils.manifest.json")
}),
]

  • 注意:

    在webpack_dll.config.js文件中,DllPlugin中的name參數必須和output.library中的一致;因為DllPlugin的name參數影響輸出的manifest.json的name;而webpack.pro.config.js中的DllReferencePlugin會讀取manifest.json的name,將值作為從全局變數中獲取動態鏈接庫內容時的全局變數名
  • 執行構建

1. webpack --progress --colors --config ./webpack.dll.config.js

2. webpack --progress --colors --config ./webpack.prod.js

  • html中引入dll.js文件
  • 構建時間對比:["11593ms","10654ms","8334ms"]

HappyPack並行構建優化

  • 核心原理:將webpack中最耗時的loader文件轉換操作任務,分解到多個進程中並行處理,從而減少構建時間。
  • HappyPack
  • 接入HappyPack

1. 安裝:npm i -D happypack

2. 重新配置rules部分,將loader交給happypack來分配:

const HappyPack = require("happypack");
const happyThreadPool = HappyPack.ThreadPool({size: 5}); //構建共享進程池,包含5個進程
...
plugins: [
// happypack並行處理
new HappyPack({
// 用唯一ID來代表當前HappyPack是用來處理一類特定文件的,與rules中的use對應
id: "babel",
loaders: ["babel-loader?cacheDirectory"],//默認設置loader處理
threadPool: happyThreadPool,//使用共享池處理
}),
new HappyPack({
// 用唯一ID來代表當前HappyPack是用來處理一類特定文件的,與rules中的use對應
id: "css",
loaders: [
"css-loader",
"postcss-loader",
"sass-loader"],
threadPool: happyThreadPool
})
],
module: {
rules: [
{
test: /.(js|jsx)$/,
use: ["happypack/loader?id=babel"],
exclude: path.resolve(__dirname," ./node_modules"),
},
{
test: /.(scss|css)$/,
//使用的mini-css-extract-plugin提取css此處,如果放在上面會出錯
use: [MiniCssExtractPlugin.loader,"happypack/loader?id=css"],
include:[
path.resolve(__dirname,"src"),
path.join(__dirname, "./node_modules/antd")
]
},
}

  • 參數:

1. threads:代表開啟幾個子進程去處理這一類文件,默認是3個;

2. verbose:是否運行HappyPack輸出日誌,默認true;

3. threadPool:代表共享進程池,即多個HappyPack示例使用一個共享進程池中的子進程去處理任務,以防資源佔有過多

代碼壓縮用ParallelUglifyPlugin代替自帶的 UglifyJsPlugin插件

  • 自帶的JS壓縮插件是單線程執行的,而webpack-parallel-uglify-plugin可以並行的執行
  • 配置參數:

1. uglifyJS: {}:用於壓縮 ES5 代碼時的配置,Object 類型

2. test: /.js$/g:使用正則去匹配哪些文件需要被 ParallelUglifyPlugin 壓縮,默認是 /.js$/

3. include: []:使用正則去包含被壓縮的文件,默認為 [].

4. exclude: []: 使用正則去包含不被壓縮的文件,默認為 []

5. cacheDir: "":緩存壓縮後的結果,下次遇到一樣的輸入時直接從緩存中獲取壓縮後的結果並返回,默認不會緩存,開啟緩存設置一個目錄路徑

6. workerCount: "":開啟幾個子進程去並發的執行壓縮。默認是當前運行電腦的 CPU 核數減去1

7. sourceMap: false:是否為壓縮後的代碼生成對應的Source Map, 默認不生成

...
minimizer: [
// webpack:production模式默認有配有js壓縮,但是如果這裡設置了css壓縮,js壓縮也要重新設置,因為使用minimizer會自動取消webpack的默認配置
new optimizeCssPlugin({
assetNameRegExp: /.css$/g,
cssProcessor: require("cssnano"),
cssProcessorOptions: { discardComments: { removeAll: true } },
canPrint: true
}),
new ParallelUglifyPlugin({
cacheDir: ".cache/",
uglifyJS:{
output: {
// 是否輸出可讀性較強的代碼,即會保留空格和製表符,默認為輸出,為了達到更好的壓縮效果,可以設置為false
beautify: false,
//是否保留代碼中的注釋,默認為保留,為了達到更好的壓縮效果,可以設置為false
comments: false
},
compress: {
//是否在UglifyJS刪除沒有用到的代碼時輸出警告信息,默認為輸出
warnings: false,
//是否刪除代碼中所有的console語句,默認為不刪除,開啟後,會刪除所有的console語句
drop_console: true,
//是否內嵌雖然已經定義了,但是只用到一次的變數,比如將 var x = 1; y = x, 轉換成 y = 1, 默認為否
collapse_vars: true,
}
}
}),
]

  • 構建結果對比:["11593ms","10654ms","8334ms","7734ms"]
  • 整體構建速度從12000ms降到現在的8000ms

「積跬步、行千里」—— 持續更新中~,喜歡的話留下個贊和關注哦!

  • 往期經典好文:
  • 你不知道的CORS跨域資源共享
  • Koa日誌中間件封裝開發(log4js)
  • 團隊合作必備的Git操作
  • 使用pm2部署node生產環境

作者:keywords

原文:http://developer.51cto.com/art/201903/593428.htm

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

核心交易鏈路架構設計與演進
幽默:當你想取一個有意義的變數名稱時

TAG:程序員小新人學習 |