效能優化篇---Webpack構建速度優化
如何輸出Webpack構建分析
- 輸出Webpack構建資訊的.json檔案:
webpack --profile --json > starts.json
--profile --json
-
web視覺化檢視構建分析:得到了webpack構建資訊檔案starts.json,如何進行很好的視覺化檢視?
- 方案一:通過視覺化分析工具 Webpack Analyse ,是個線上Web應用,上傳starts.json檔案就可以;不過好像需要翻牆;
- 方案二:安裝
webpack-bundle-analyzer
工具npm i -g webpack-bundle-analyzer
,生成starts.json後直接在其資料夾目錄執行webpack-bundle-analyzer
後,瀏覽器會開啟對應網頁並展示構建分析 文件地址webpack-bundle-analyzer - webpack-dashboard是一款統計和優化webpack日誌的工具,可以以表格形勢展示日誌資訊。其中包括構建過程和狀態、日誌以及涉及的模組列表
- 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在啟動時會從配置的Entry出發,解析出檔案中的匯入語句,再遞迴解析。
-
對於匯入語句Webpack會做出以下操作:
- 根據匯入語句尋找對應的要匯入的檔案;
- 在根據要匯入的檔案字尾,使用配置中的Loader去處理檔案(如使用ES6需要使用babel-loader處理)
- 針對這兩點可以優化查詢途徑
-
優化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目錄下的檔案 },
-
優化resolve.modules配置
-
resolve.modules
用於配置webpack去哪些目錄下尋找第三方模組,預設是['node_modules']
,但是,它會先去當前目錄的./node_modules
查詢,沒有的話再去../node_modules
最後到根目錄; - 所以當安裝的第三方模組都放在專案根目錄時,就沒有必要安預設的一層一層的查詢,直接指明存放的絕對位置
resolve: { modules: [path.resolve(__dirname, 'node_modules')], }
-
-
優化resolve.extensions配置
- 在匯入沒帶檔案字尾的路徑時,webpack會自動帶上字尾去嘗試詢問檔案是否存在,而
resolve.extensions
用於配置嘗試字尾列表;預設為extensions:['js','json']
; - 及當遇到
require('./data')
時webpack會先嚐試尋找data.js
,沒有再去找data.json
;如果列表越長,或者正確的字尾越往後,嘗試的次數就會越多; -
所以在配置時為提升構建優化需遵守:
- 頻率出現高的檔案字尾優先放在前面;
- 列表儘可能的小;
- 書寫匯入語句時,儘量寫上字尾名
- 因為專案中用的
jsx
較多,所以配置extensions: [".jsx",".js"],
- 在匯入沒帶檔案字尾的路徑時,webpack會自動帶上字尾去嘗試詢問檔案是否存在,而
- 基本配置後檢視構建速度:
Time: 10654ms
;配置前為Time: 11593ms
使用DllPlugin優化
- 在使用webpack進行打包時候,對於依賴的第三方庫,如react,react-dom等這些不會修改的依賴,可以讓它和業務程式碼分開打包;
- 只要不升級依賴庫版本,之後webpack就只需要打包專案業務程式碼,遇到需要匯入的模組在某個動態連結庫中時,就直接去其中獲取;而不用再去編譯第三方庫,這樣第三方庫就只需要打包一次。
-
接入需要完成的事:
- 將依賴的第三方模組抽離,打包到一個個單獨的動態連結庫中
- 當需要匯入的模組存在動態連結庫中時,讓其直接從連結庫中獲取
- 專案依賴的所有動態連結庫都需要被載入
-
接入工具(webpack已內建)
- DllPlugin外掛: 用於打包出一個個單獨的動態連結庫檔案;
- 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,將值作為從全域性變數中獲取動態連結庫內容時的全域性變數名 -
執行構建
webpack --progress --colors --config ./webpack.dll.config.js webpack --progress --colors --config ./webpack.prod.js
- html中引入dll.js檔案
- 構建時間對比:
["11593ms","10654ms","8334ms"]
HappyPack並行構建優化
- 核心原理: 將webpack中最耗時的loader檔案轉換操作任務,分解到多個程序中並行處理,從而減少構建時間。
- HappyPack
-
接入HappyPack
- 安裝:
npm i -D happypack
- 重新配置
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') ] }, }
-
引數:
threads verbose
程式碼壓縮用ParallelUglifyPlugin代替自帶的 UglifyJsPlugin外掛
- 自帶的JS壓縮外掛是單執行緒執行的,而 webpack-parallel-uglify-plugin 可以並行的執行
-
配置引數:
uglifyJS: {} test: /.js$/g include: [] exclude: [] cacheDir: '' workerCount: '' sourceMap: false
... 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
“積跬步、行千里” —— 持續更新中~,喜歡的話留下個贊和關注哦!