文章首發於:github.com/USTB-musion…
寫在前面
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 之後 ? 之後的體積由 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 可以實現多執行緒?
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() // 熱更新外掛
]
};
複製程式碼