webpack4.x 效能優化
webpack可以說是當下最流行的打包庫,當webpack處理應用程式時,它會遞迴地構建一個依賴關係圖,其中包含應用程式需要的每一個模組,然後將所有這些模組打包成一個或多個bundle。這篇文章將介紹webpack非常重要的一部分——效能優化。文章的程式碼的在此:github.com/USTB-musion…
本文將從以下幾部分進行總結:
- noParse
- ignorePlugin
- dllPlugin
- happypack
- Tree-Shaking
- 抽離公共程式碼
- 懶載入
- 熱更新
noParse
- noParse 配置項可以讓 Webpack 忽略對部分沒采用模組化的檔案的遞迴解析和處理,這樣做的好處是能提高構建效能。 原因是一些庫例如 jQuery 、ChartJS 它們龐大又沒有采用模組化標準,讓 Webpack 去解析這些檔案耗時又沒有意義。
啟用noParse:
module: { // 不去解析jquery的依賴關係 noParse: /jquery/ }, 複製程式碼
ignorePlugin
- moment 2.18 會將所有本地化內容和核心功能一起打包)。可以使用 IgnorePlugin 在打包時忽略本地化內容,經過實驗,使用 ignorePlugin 之後 :package: 之後的體積由 1.2M 降低至 800K
ignorePlugin啟用方法:
// 用法: new webpack.IgnorePlugin(requestRegExp, [contextRegExp]); //eg. plugins: [new webpack.IgnorePlugin(/\.\/local/, /moment/)]; 複製程式碼
DllPlugin
- DllPlugin 是基於 Windows 動態連結庫(dll)的思想被創作出來的。這個外掛會把第三方庫單獨打包到一個檔案中,這個檔案就是一個單純的依賴庫。這個依賴庫不會跟著你的業務程式碼一起被重新打包,只有當依賴自身發生版本變化時才會重新打包。
用 DllPlugin 處理檔案,要分兩步走:
- 基於 dll 專屬的配置檔案,打包 dll 庫
let path = require("path"); let webpack = require("webpack"); module.exports = { mode: "development", entry: { react: ["react", "react-dom"] }, output: { filename: "_dll_[name].js", // 產生的檔名 path: path.resolve(__dirname, "dist"), library: "_dll_[name]" }, plugins: [ // name要等於library裡的name new webpack.DllPlugin({ name: "_dll_[name]", path: path.resolve(__dirname, "dist", "manifest.json") }) ] }; 複製程式碼
- 基於 webpack.config.js 檔案,打包業務程式碼
let path = require("path"); let HtmlWebpackPlugin = require("html-webpack-plugin"); let webpack = require("webpack"); module.exports = { mode: "development", // 多入口 entry: { home: "./src/index.js" }, devServer: { port: 3000, open: true, contentBase: "./dist" }, module: { // 不去解析jquery的依賴關係 noParse: /jquery/, rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"] }, { test: /\.js$/, exclude: /node_modules/, include: path.resolve("src"), use: { loader: "babel-loader", options: { presets: ["@babel/preset-env", "@babel/preset-react"] } } } ] }, output: { // name -> home a filename: "[name].js", path: path.resolve(__dirname, "dist") }, plugins: [ new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, "dist", "manifest.json") }), new webpack.IgnorePlugin(/\.\/local/, /moment/), new HtmlWebpackPlugin({ template: "./src/index.html", filename: "index.html" }), new webpack.DefinePlugin({ DEV: JSON.stringify("production") }) ] }; 複製程式碼
Happypack——將 loader 由單程序轉為多程序
- 大家知道,webpack 是單執行緒的,就算此刻存在多個任務,你也只能排隊一個接一個地等待處理。這是 webpack 的缺點,好在我們的 CPU 是多核的,Happypack 會充分釋放 CPU 在多核併發方面的優勢,幫我們把任務分解給多個子程序去併發執行,大大提升打包效率。
happypack的使用方法:
將loader中的配置轉移到happypack中就好:
let path = require("path"); let HtmlWebpackPlugin = require("html-webpack-plugin"); let webpack = require("webpack"); // 模組 happypack 可以實現多執行緒:package: let Happypack = require("happypack"); module.exports = { mode: "development", // 多入口 entry: { home: "./src/index.js" }, devServer: { port: 3000, open: true, contentBase: "./dist" }, module: { // 不去解析jquery的依賴關係 noParse: /jquery/, rules: [ { test: /\.css$/, use: "Happypack/loader?id=css" }, { test: /\.js$/, exclude: /node_modules/, include: path.resolve("src"), use: "Happypack/loader?id=js" } ] }, output: { // name -> home a filename: "[name].js", path: path.resolve(__dirname, "dist") }, plugins: [ new Happypack({ id: "css", use: ["style-loader", "css-loader"] }), new Happypack({ id: "js", use: [ { loader: "babel-loader", options: { presets: ["@babel/preset-env", "@babel/preset-react"] } } ] }), new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, "dist", "manifest.json") }), new webpack.IgnorePlugin(/\.\/local/, /moment/), new HtmlWebpackPlugin({ template: "./src/index.html", filename: "index.html" }), new webpack.DefinePlugin({ DEV: JSON.stringify("production") }) ] }; 複製程式碼
Tree-Shaking
- 基於 import/export 語法,Tree-Shaking 可以在編譯的過程中獲悉哪些模組並沒有真正被使用,這些沒用的程式碼,在最後打包的時候會被去除。適合於處理模組級別的程式碼,所以儘量使用es6的import/export語法。
抽離公共程式碼
把公共程式碼抽離出來的好處:
- 減少網路傳輸流量,降低伺服器成本;
- 雖然使用者第一次開啟網站的速度得不到優化,但之後訪問其它頁面的速度將大大提升。
啟用抽離程式碼:
// webpack 4.x版本之前的commonChunkPlugins optimization: { // 分割程式碼塊 splitChunks: { // 快取組 cacheGroups: { // 公共模組 common: { chunks: "initial", minSize: 0, // 最小公用模組次數 minChunks: 2 }, vendor: { priority: 1, // 抽離出來 test: /node_modules/, chunks: "initial", minSize: 0, minChunks: 2 } } } } 複製程式碼
按需載入
按需載入的思想
- 一次不載入完所有的檔案內容,只加載此刻需要用到的那部分(會提前做拆分)
- 當需要更多內容時,再對用到的內容進行即時載入
通過 es6 的 import 實現按需載入,在使用 import() 分割程式碼後,你的瀏覽器並且要支援 Promise API 才能讓程式碼正常執行, 因為 import() 返回一個 Promise,它依賴 Promise。對於不原生支援 Promise 的瀏覽器,你可以注入 Promise polyfill。
let button = document.createElement("button"); button.innerHTML = "musion"; // vue,react的懶載入原理也是如此 button.addEventListener("click", function() { // es6草案中的語法, jsonp實現動態載入檔案 import("./source.js").then(data => { console.log(data.default); }); console.log("click"); }); document.body.appendChild(button); 複製程式碼
熱更新
模組熱替換(HMR - Hot Module Replacement)是 webpack 提供的最有用的功能之一。它允許在執行時替換,新增,刪除各種模組,而無需進行完全重新整理重新載入整個頁面,其思路主要有以下幾個方面:
- 保留在完全重新載入頁面時丟失的應用程式的狀態
- 只更新改變的內容,以節省開發時間
- 調整樣式更加快速,幾乎等同於就在瀏覽器偵錯程式中更改樣式
啟用HRM
- 引入了webpack庫
- 使用了new webpack.HotModuleReplacementPlugin()
- 設定devServer選項中的hot欄位為true
let path = require("path"); let HtmlWebpackPlugin = require("html-webpack-plugin"); let webpack = require("webpack"); module.exports = { mode: "production", // 多入口 entry: { index: "./src/index.js", other: "./src/other.js" }, devServer: { // 啟用熱更新 hot: true, port: 3000, open: true, contentBase: "./dist" }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, include: path.resolve("src"), use: { loader: "babel-loader", options: { presets: ["@babel/preset-env", "@babel/preset-react"], plugins: ["@babel/plugin-syntax-dynamic-import"] } } } ] }, output: { // name -> home a filename: "[name].js", path: path.resolve(__dirname, "dist") }, plugins: [ new webpack.NamedModulesPlugin(), // 列印更新的模組路徑 new webpack.HotModuleReplacementPlugin() // 熱更新外掛 ] }; 複製程式碼