1 webpack的原理
1. 原理
webpack整體是一個事件驅動架構,所有的功能都以Plugin(插件)的方式集成在構建流程中, 通過發佈訂閱事件來觸發各個插件執行。webpack核心使用tapable來實現Plugin(插件)的註冊和調用,Tapable是一個事件發佈(tap)訂閱(call)庫。
2. webpack的流程
- 初始化參數:從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數;
- 開始編譯:用上一步得到的參數初始化 Compiler 對象,加載所有配置的插件,執行對象的 run 方法開始執行編譯;
- 編譯模塊:從入口文件出發,調用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經過了本步驟的處理;
- 完成模塊編譯:在經過第3步使用 Loader 翻譯完所有模塊後,得到了每個模塊被翻譯後的最終內容以及它們之間的依賴關係;
- 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk 轉換成一個單獨的文件加入到輸出列表,在確定好輸出內容後,根據配置確定輸出的路徑和文件名,把文件內容寫入到文件系統。
3. webpack的優化
webpack是我們在日常開發中使用的構建工具,它的打包速度直接影響開發效率,因此優化webpack的打包是很重要的,下面使用我們的一個比較大型的項目來做實驗。
上圖是採用常規webpack配置,並未採用優化手段打包,打包時間100s…
採用webpack4.0提供的優化手段:
- 分包,使用 SplitChunks 抽取公有代碼,重點是緩存組cacheGroups的配置,主要是用於過濾模塊的test的設置,test可以是直接的正則表達式,也可以是過濾函數,可以使用test來配置一些公共代碼的抽取,這裏我們對node_modules下的npm包和src下的shared/compentes一些公用部分打成一個基礎包。
optimization.splitChunks = {
cacheGroups: {
vendor: {
test(mod, chunks) {
return (
(mod.context.includes("node_modules") ||
/src[\\/]\w+[\\/](shared|components)|src[\\/]shared/.test(mod.context)
)
},
chunks: "all",
name: "vendor"
}
}
}
增加公共代碼的抽取主要是對代碼進行分塊打包,並不能對打包速度有特別大的提升,還是在90—100之間。
- happypack,多線程編譯,加快編譯速度(主要是加快loader的編譯速度,此外,babel的配置文件關於env的配置也需要使用babel/preset-env進行優化,具體可參見前端項目工程化工具webpack,gulp,babel的使用和原理中babel部分),解決webpck單線程的弊端,項目中配置了一個happypack函數對babel-loader速度進行優化:
const happypack = (webpackConfig) =>{
const HappyPack = require("happypack")
const os = require("os")
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
let babelOptions = {}
let rules = webpackConfig.module.rules.reduce((rs, rule) => {
if (rule.loader === "babel-loader") {
babelOptions = rule.options
rs.push({
test: /\.(js|mjs|jsx|ts|tsx)$/,
exclude: /(node_modules|bower_components)/,
loader: "happypack/loader?id=happyBabel"
})
} else {
rs.push(rule)
}
return rs
}, [])
webpackConfig.module.rules = rules
webpackConfig.plugins.push(
new HappyPack({
// id 標識符,要和 rules 中指定的 id 對應起來
id: "happyBabel",
threadPool: happyThreadPool,
// 需要使用的 loader,用法和 rules 中 Loader 配置一樣
// 可以直接是字符串,也可以是對象形式
loaders: [{ loader: "babel-loader", options: babelOptions }]
})
)
}
增加了happypack之後,打包速度快了點,已經從100秒降到70秒了。。
- babel-loader配置的優化
在babel-loader轉譯文件時候非常耗費時間,我們需要對這個過程進行優化,參考官網(https://webpack.docschina.org/loaders/babel-loader/),主要對options進行設置,經過測試,設置cacheDirectory(當有設置時,指定的目錄將用來緩存 loader 的執行結果。之後的 webpack 構建,將會嘗試讀取緩存,來避免在每次執行時,可能產生的、高性能消耗的 Babel 重新編譯過程(recompilation process)。如果設置了一個空值 (loader: ‘babel-loader?cacheDirectory’) 或者 true (loader: ‘babel-loader?cacheDirectory=true’),loader 將使用默認的緩存目錄 node_modules/.cache/babel-loader,如果在任何根目錄下都沒有找到 node_modules 目錄,將會降級回退到操作系統默認的臨時文件目錄。)
在剛剛的happypack函數中增加cacheDirectory:
const happypack = (webpackConfig) =>{
const HappyPack = require("happypack")
const os = require("os")
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
let babelOptions = {
cacheDirectory: true
}
let rules = webpackConfig.module.rules.reduce((rs, rule) => {
if (rule.loader === "babel-loader") {
babelOptions = rule.options
rs.push({
test: /\.(js|mjs|jsx|ts|tsx)$/,
exclude: /(node_modules|bower_components)/,
loader: "happypack/loader?id=happyBabel"
})
} else {
rs.push(rule)
}
return rs
}, [])
webpackConfig.module.rules = rules
webpackConfig.plugins.push(
new HappyPack({
/*
* 必須配置
*/
// id 標識符,要和 rules 中指定的 id 對應起來
id: "happyBabel",
threadPool: happyThreadPool,
// 需要使用的 loader,用法和 rules 中 Loader 配置一樣
// 可以直接是字符串,也可以是對象形式
loaders: [{ loader: "babel-loader", options: babelOptions }]
})
)
}
打包時間瞬間降到了29秒,感動。。。
- DllPlugin,DllReferencePlugin:動態鏈接庫
先運行一個dll配置文件,預先把一些固定模塊編譯,它會在第一次編譯的時候將配置好的需要預先編譯的模塊編譯在緩存中,第二次編譯的時候,解析到這些模塊就直接使用緩存,而不是去編譯這些模塊(DllPlugin);將預先編譯好的模塊關聯到當前編譯中,當 webpack 解析到這些模塊時,會直接使用預先編譯好的模塊(DllReferencePlugin),,這塊暫時沒在項目使用。。。
參考文獻:
1.https://lequ7.com/2019/04/26/javascript/webpack-yuan-li-shu-li/
2.https://juejin.im/post/5c763885e51d457380771ab0
3. https://juejin.im/entry/5b0e3eba5188251534379615