柚子快報(bào)邀請(qǐng)碼778899分享:webpack學(xué)習(xí)
webpack學(xué)習(xí)筆記
參考文檔: Webpack官網(wǎng)中文文檔學(xué)習(xí)視頻: Webpack原理與實(shí)踐
溫故webpack打包知識(shí),忘記了就回頭看看~ 根據(jù)學(xué)習(xí)視頻學(xué)習(xí)整理,不為一一對(duì)應(yīng)筆記,主要是根據(jù)對(duì)學(xué)習(xí)視頻知識(shí)的梳理~ PS:上學(xué)習(xí)視頻是基于webpack4的,我這邊運(yùn)行時(shí)基于webpack5的,不對(duì)的地方已經(jīng)按照官方文檔進(jìn)行定義配置
初始化項(xiàng)目
環(huán)境要求:node+npm+webpack
初始化 npm 項(xiàng)目
npm init -y
安裝 webpack 和 webpack-cli:
npm install --save-dev webpack webpack-cli
創(chuàng)建項(xiàng)目
建立webpack.config.js文件新建src文件夾,新建打包入口文件main.js webpack.config.js
const path = require('path');
module.exports = {
//定義入口文件
entry: './src/main.js',
// 定義輸出文件名
output:{
//定義輸出名稱
filename:'bundle.js',
//定義輸出路徑
path: path.join(__dirname, "/dist"),
}
}
main.js
alert('Hello world!')
定義打包命令package.json中script
運(yùn)行npm run build 則打包文件,默認(rèn)根據(jù)webpack.config.js配置打包
"scripts": {
"build": "webpack"
}
在根目錄建立index.html文件,通過(guò)script標(biāo)簽引入打包的文件(后面通過(guò)插件htlm-webpack-plugin自動(dòng)生成,則刪除此文件,直接自動(dòng)打包到dist文件夾中,并會(huì)自動(dòng)引入打包.js文件)
安裝server運(yùn)行靜態(tài)服務(wù)器
運(yùn)行serve則打開(kāi)當(dāng)前目錄,若有index.html默認(rèn)運(yùn)行index.html文件
npm install -g serve
運(yùn)行
當(dāng)前開(kāi)始時(shí)運(yùn)行的自定義的index.html看效果,
serve .
后續(xù)運(yùn)行dist文件夾的自動(dòng)生成的index.html看效果
serve dist
模塊化標(biāo)準(zhǔn)規(guī)范
瀏覽器:ES Module (主流的打包方案)Nodejs: Commonjs(內(nèi)置的環(huán)境系統(tǒng))
Entry(入口):
Webpack 的打包過(guò)程從入口文件開(kāi)始。入口文件可以是一個(gè)或多個(gè),指定了應(yīng)用程序的起點(diǎn)。
// webpack.config.js
module.exports = {
entry: './src/index.js'
};
Output(輸出):
配置打包后的文件輸出位置和文件名。
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
}
};
Loaders(加載器):
Loaders 讓 Webpack 能夠處理非 JavaScript 文件!!! 如 CSS、圖片、字體等。通過(guò)配置 loaders,Webpack 可以將這些文件轉(zhuǎn)換為可以被應(yīng)用程序使用的模塊。 具體每個(gè)loader作用以及更多l(xiāng)oader查看官網(wǎng),在這里配置最常見(jiàn)的loader
常見(jiàn)loader如下:
style-loader & css-loaderfile-loader & url-loaderhtml-loaderbabel-loader自制loader…
安裝依賴
npm i style-loader css-loader file-loader url-loader html-loader babel-loader
配置應(yīng)用
const webpack = require('webpack');
const path = require("path");
module.exports = {
mode: "none",
entry: "./src/main.js",
output: {
filename: "[name]-[chunkhash:8]-bundle.js",
path: path.join(__dirname, "/dist"),
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
//將ES Modules轉(zhuǎn)換為CommonJS
presets: [
['@babel/preset-env', {
//默認(rèn)是auto自動(dòng),寫了commonjs是強(qiáng)制轉(zhuǎn)換為commonjs,此時(shí)usedExports無(wú)法生效,即tree shaking無(wú)法生效
modules: 'commonjs'
}]
],
}
}
},
{
test: /\.html$/,
use: 'html-loader',
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader']
},
{
test: /\.png$/,
use: {
loader: "url-loader",
options: {
name: '[name].[hash:8].[ext]',
outputPath: 'images/',
publicPath: 'images/',
limit: 10 * 1024,
}
}
}
]
}
}
自制loader詳解
自制markdown-loader為了解析引入.md文件
安裝markdown-it(webpack5用這個(gè)解析.md文件)
npm i markdown-it
新建markdown-loader.js
const markdownIt = require('markdown-it')();
module.exports = function markdownLoader(source) {
const html = markdownIt.render(source);
return `module.exports = ${JSON.stringify(html)}`;
};
配置使用
const webpack = require('webpack');
const path = require("path");
module.exports = {
mode: "none",
entry: "./src/main.js",
output: {
filename: "[name]-[chunkhash:8]-bundle.js",
path: path.join(__dirname, "/dist"),
},
module: {
rules: [
{
test: /\.md$/,
use: './markdown-loader'
}
]
}
}
style-loader 和 css-loader
這兩個(gè) Loaders 通常一起使用,用于處理 CSS 文件并將其引入到 JavaScript 模塊中。
css-loader:
解析 CSS 文件中的 @import 和 url() 語(yǔ)法,并將 CSS 轉(zhuǎn)換為 JavaScript 模塊。 允許你在 JavaScript 文件中通過(guò) import 或 require 引入 CSS 文件。
style-loader:
將 CSS 以
示例配置:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
file-loader 和 url-loader
這兩個(gè) Loaders 用于處理文件資源,如圖片、字體等。
file-loader:
將文件解析為 import 或 require 語(yǔ)句,并返回一個(gè)相應(yīng)的 URL。 將文件復(fù)制到輸出目錄,并根據(jù)配置返回相對(duì) URL 或絕對(duì) URL。
url-loader:
功能類似于 file-loader,但如果文件小于設(shè)定的閾值(以字節(jié)為單位),它會(huì)將文件內(nèi)容轉(zhuǎn)換為 Base64 編碼的 Data URL,嵌入到生成的 JavaScript 文件中。 超過(guò)閾值的文件仍然會(huì)使用 file-loader 處理
示例配置:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, // 8KB 以下的文件會(huì)被轉(zhuǎn)為 Base64
name: '[name].[hash:8].[ext]'
}
}
]
}
]
}
};
html-loader
html-loader:
解析 HTML 文件中的 標(biāo)簽、 標(biāo)簽和其他資源引用,將它們轉(zhuǎn)換為 import 或 require 語(yǔ)句,從而使得這些資源能夠被 Webpack 打包。 處理 HTML 文件中的資源依賴,生成正確的資源路徑。 示例配置
module.exports = {
module: {
rules: [
{
test: /\.html$/,
use: 'html-loader'
}
]
}
};
babel-loader
babel-loader: 使用 Babel 將 ES6/ES7/ES8 等現(xiàn)代 JavaScript 語(yǔ)法轉(zhuǎn)換為 ES5,從而兼容更多的瀏覽器環(huán)境。 支持 Babel 插件和預(yù)設(shè)(presets),如 @babel/preset-env,以便使用最新的 JavaScript 特性和語(yǔ)法。
示例配置:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
};
總結(jié)
style-loader & css-loader:處理 CSS 文件,解析并注入到 HTML 中。file-loader & url-loader:處理文件資源(如圖片、字體),將文件復(fù)制到輸出目錄并生成相應(yīng)的 URL,url-loader 可以將小文件內(nèi)聯(lián)到 JavaScript 中。html-loader:解析 HTML 文件中的資源引用,生成正確的資源路徑。babel-loader:使用 Babel 將現(xiàn)代 JavaScript 語(yǔ)法轉(zhuǎn)換為 ES5,以兼容更多瀏覽器環(huán)境。 通過(guò)使用這些 Loaders,Webpack 可以處理多種類型的文件資源,并將它們整合到打包輸出中,提高開(kāi)發(fā)效率和代碼兼容性。
Plugins(插件):
插件用于執(zhí)行更廣泛的任務(wù),如優(yōu)化打包結(jié)果、資源管理和環(huán)境變量注入。插件比 loaders 更強(qiáng)大,提供了更多功能。 具體每個(gè)plugins作用以及更多plugins查看官網(wǎng),在這里配置最常見(jiàn)的plugins
常見(jiàn)plugins如下:
clean-webpack-pluginhtml-webpack-plugincopy-webpack-pluginmini-css-extract-plugincss-minimizer-webpack-pluginterser-webpack-plugin自制plugins…更多plugins詳見(jiàn)官網(wǎng)
安裝依賴
npm i --save-dev clean-webpack-plugin html-webpack-plugin copy-webpack-plugin mini-css-extract-plugin css-minimizer-webpack-plugin terser-webpack-plugin
配置應(yīng)用
// clean-webpack-plugin 清除上次生成的dist目錄 避免遺留文件
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
// html-webpack-plugin 通過(guò)webpack輸出html文件 index.html中的script路徑引用 是否正常
const HTMLWebpackPlugin = require("html-webpack-plugin");
// copy-webpack-plugin 拷貝不需要打包的目錄
const CopyWebpackPlugin = require("copy-webpack-plugin");
//提取css文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 壓縮css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//js壓縮插件
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const path = require("path");
module.exports = {
mode: "none",
entry: "./src/main.js",
output: {
filename: "[name]-[chunkhash:8]-bundle.js",
path: path.join(__dirname, "/dist"),
},
//相當(dāng)于tree shaking:集中配置webpack當(dāng)中優(yōu)化功能
optimization: {
// 壓縮代碼 負(fù)責(zé)【搖掉】他們 開(kāi)發(fā)環(huán)境一般不壓縮
minimize: false,
//minimizer默認(rèn)壓縮,配置為數(shù)組方式則為自定義配置打包壓縮,CssMinimizerPlugin壓縮css,TerserPlugin壓縮js
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin()
]
},
plugins: [
new CleanWebpackPlugin(),
// 用于生成index.html
new HTMLWebpackPlugin({
title: "Webpack Plugin Demo",
meta: {
viewport: "width=device-width, initial-scale=1"
},
// 按照模版輸出
// template: "./src/index.html",
}),
//用于熱更新
new webpack.HotModuleReplacementPlugin(),
//按需加載css文件
new MiniCssExtractPlugin({
// :8指定長(zhǎng)度 推薦使用chunkhash
// * hash:項(xiàng)目中任何一個(gè)地方改動(dòng),打包都會(huì)造成全部文件變化
// * chunkhash推薦: 根據(jù)修改代碼處,打包只變化修改的代碼處文件名
// * contenthash:根據(jù)代碼修改代碼處,修改只變化內(nèi)容的文件名
// filename:'[name]-[hash:8].bundle.css',
filename: '[name]-[chunkhash:8].bundle.css',
// filename:'[name]-[contenthash].bundle.css',
}),
// 壓縮css
new CssMinimizerPlugin()
]
}
自制plugins詳解
自制my-plugins插件,用于清除js文件中的注釋!
通在webpack生命周期的鉤子中掛載函數(shù)實(shí)現(xiàn)擴(kuò)展 !?。?類似事件 webpack給每個(gè)環(huán)節(jié)埋下鉤子 掛載不同任務(wù) 就可擴(kuò)展webpack能力。webpack要求我們鉤子必須是一個(gè)函數(shù)或者是一個(gè)包含apply方法的對(duì)象
配置如下:
const path = require("path");
// 自制插件 plugin 通在webpack生命周期的鉤子中掛載函數(shù)實(shí)現(xiàn)擴(kuò)展 !??! 類似事件 webpack給每個(gè)環(huán)節(jié)埋下鉤子 掛載不同任務(wù) 就可擴(kuò)展webpack能力
//webpack要求我們鉤子必須是一個(gè)函數(shù)或者是一個(gè)包含apply方法的對(duì)象
//編寫一個(gè)插件 去除js文件中的注釋
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tap('MyPlugin', compilation => {
//compilation 理解為此次打包的上下文
for (const name in compilation.assets) {
console.log(`Processing asset: ${name}`);
if (name.endsWith(".js")) {
const content = compilation.assets[name].source();
console.log(`Original content: \n${content}`);
const withoutComments = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
console.log(`Content without comments: \n${withoutComments}`);
compilation.assets[name] = {
source: () => withoutComments,
//必須的方法
size: () => withoutComments.length,
};
}
}
});
}
}
module.exports = {
mode: "none",
entry: "./src/main.js",
output: {
filename: "[name]-[chunkhash:8]-bundle.js",
path: path.join(__dirname, "/dist"),
},
plugins: [
// 自制插件 用于清除js文件中的注釋
new MyPlugin(),
]
}
配置使用
const webpack = require('webpack');
const path = require("path");
module.exports = {
mode: "none",
entry: "./src/main.js",
output: {
filename: "[name]-[chunkhash:8]-bundle.js",
path: path.join(__dirname, "/dist"),
},
module: {
rules: [
{
test: /\.md$/,
use: './markdown-loader'
}
]
}
}
clean-webpack-plugin
作用:在每次構(gòu)建之前清理 /dist 文件夾,確保輸出目錄中只有構(gòu)建過(guò)程中生成的文件。主要功能:
刪除舊的文件,防止冗余文件堆積,保持輸出目錄整潔。 安裝依賴
npm i --save-dev clean-webpack-plugin
配置
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
};
html-webpack-plugin
作用:生成 HTML 文件,并自動(dòng)注入打包生成的 JavaScript 和 CSS 文件。 主要功能:
根據(jù)模板或默認(rèn)生成 HTML 文件。自動(dòng)注入所有打包生成的資源(例如 JS、CSS 文件)。支持設(shè)置模板、文件名、標(biāo)題等選項(xiàng)。 安裝依賴
npm i --save-dev html-webpack-plugin
配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
title: 'My App'
})
]
};
copy-webpack-plugin
作用:將文件或文件夾從一個(gè)位置復(fù)制到另一個(gè)位置(通常是復(fù)制靜態(tài)資源到輸出目錄)。 主要功能:
復(fù)制不需要進(jìn)行編譯處理的文件,如圖片、字體、靜態(tài) HTML 等。 安裝依賴
npm i --save-dev copy-webpack-plugin
配置
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: 'public', to: 'dist' }
]
})
]
};
mini-css-extract-plugin
通過(guò)MiniCssExtractPlugin插件實(shí)現(xiàn)css文件的按需加載。建議css文件超過(guò)150kb才考慮是否提取到文件當(dāng)中,會(huì)單獨(dú)生成一個(gè)文件。
作用:將 CSS 提取到單獨(dú)的文件,而不是嵌入到 JavaScript 中。 主要功能:
提高 CSS 加載速度,減少 JavaScript 文件的體積。支持按需加載 CSS。 安裝依賴
npm i --save-dev mini-css-extract-plugin
配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
]
};
css-minimizer-webpack-plugin
optimize-css-assets-webpack-plugincss-minimizer-webpack-plugin
PS:由于 optimize-css-assets-webpack-plugin 版本與 webpack 版本之間的兼容性問(wèn)題。 具體來(lái)說(shuō),optimize-css-assets-webpack-plugin@6.0.1 需要 webpack@^4.0.0,而我當(dāng)前的項(xiàng)目中使用的是 webpack@5.91.0。 則不適用安裝此,我將安裝css-minimizer-webpack-plugin代替。 當(dāng)然,你也可以使用 --legacy-peer-deps 參數(shù)來(lái)忽略兼容性檢查,但這可能會(huì)導(dǎo)致其他問(wèn)題。
作用:壓縮和優(yōu)化 CSS 文件,減少文件體積,提高加載速度。主要功能:
使用 cssnano 庫(kù)進(jìn)行 CSS 的壓縮優(yōu)化。通常與 mini-css-extract-plugin 配合使用。通常與 terser-webpack-plugin配合使用,壓縮js文件。
安裝依賴
壓縮css
npm i --save-dev css-minimizer-webpack-plugin
壓縮js
為了區(qū)分環(huán)境,使用optimization中minimizer進(jìn)行,一般默認(rèn)壓縮,配置為數(shù)組方式則為自定義配置打包壓縮,CssMinimizerPlugin壓縮css,則需要安裝TerserPlugin壓縮js)
npm install --save-dev terser-webpack-plugin
引入并使用
// 引入依賴壓縮css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//js壓縮插件
const TerserPlugin = require('terser-webpack-plugin');
module.exports={
// 壓縮建議在生產(chǎn)環(huán)境才輸出,建議放在optimization中minimize統(tǒng)一配置
optimization:{
//可以由這里控制是否壓縮,開(kāi)發(fā)環(huán)境設(shè)置為false則不壓縮,生產(chǎn)環(huán)境設(shè)置為true開(kāi)啟壓縮
minimize:true,
//minimizer默認(rèn)壓縮,配置為數(shù)組方式則為自定義配置打包壓縮,CssMinimizerPlugin壓縮css,TerserPlugin壓縮js
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin()
]
},
//這里配置直接壓縮不區(qū)分環(huán)境
// plugins:[
// // 壓縮css
// new CssMinimizerPlugin()
// ]
}
terser-webpack-plugin
作用:壓縮和優(yōu)化 JavaScript 文件,減少文件體積,提高加載速度。主要功能:
使用 Terser 庫(kù)進(jìn)行 JavaScript 的壓縮優(yōu)化。替代 Webpack 4 之前的 UglifyJS 插件。
安裝依賴
npm i --save-dev terser-webpack-plugin
配置
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()]
}
};
總結(jié)
clean-webpack-plugin:在每次構(gòu)建之前清理輸出目錄,保持目錄整潔。html-webpack-plugin:生成 HTML 文件并自動(dòng)注入打包生成的資源。copy-webpack-plugin:復(fù)制文件或文件夾到輸出目錄。mini-css-extract-plugin:將 CSS 提取到單獨(dú)的文件,提高加載速度。css-minimizer-webpack-plugin:壓縮和優(yōu)化 CSS 文件,減少文件體積。terser-webpack-plugin:壓縮和優(yōu)化 JavaScript 文件,減少文件體積。 通過(guò)使用這些插件,Webpack 可以更高效地處理和優(yōu)化各種資源,提高構(gòu)建和加載性能。
webpack-dev-server
自動(dòng)刷新功能:將打包結(jié)果暫時(shí)存放在內(nèi)存當(dāng)中,沒(méi)有進(jìn)入磁盤讀寫,webpack-dev-server從內(nèi)存當(dāng)中將變化讀取出來(lái),發(fā)送到瀏覽器,添加–open默認(rèn)自動(dòng)打開(kāi)我們的瀏覽器,這里添加到script命令里,我配置到dev,運(yùn)行一次就可以看到一邊編碼一邊預(yù)覽的環(huán)境
安裝依賴
npm install --save-dev webpack-dev-server
配置運(yùn)行命令在package.json中
"scripts": {
"dev": "webpack-dev-server --open"
}
運(yùn)行
npm run dev
運(yùn)行之后,在文件中修改瀏覽器內(nèi)容在保存后便會(huì)直接變化,無(wú)需刷新方可更新
devtools(source-map)
設(shè)置屬性 devtool: ‘source-map’ 一般效果最好的生成的最慢,約為12種,每種方式效率和效果不同
開(kāi)發(fā)環(huán)境偏向選擇cheap-module-source-map 轉(zhuǎn)換過(guò)后差異過(guò)大 首次打包慢無(wú)所謂 重寫打包就快了生產(chǎn)環(huán)境 選擇none 為了不暴露源代碼 隱患 調(diào)試是開(kāi)發(fā)階段的事情 或者nosources-source-map
module.exports={
devtool: 'cheap-module-source-map'
}
點(diǎn)擊查看更多source-map
HMR熱更新
Hot Module Replacement 模塊熱替換或者叫模塊熱更新。 Webpack 的熱更新(Hot Module Replacement, HMR)是一種允許在運(yùn)行時(shí)替換、添加或刪除模塊,而無(wú)需重新加載整個(gè)頁(yè)面的技術(shù)。HMR 的核心原理包括模塊熱替換、依賴管理以及狀態(tài)保持。以下是 Webpack 熱更新的詳細(xì)實(shí)現(xiàn)原理
Webpack 熱更新的工作流程:
檢測(cè)變更:
Webpack 使用文件系統(tǒng)監(jiān)視工具(如 chokidar)監(jiān)視源代碼文件的變更。當(dāng)開(kāi)發(fā)者修改文件并保存時(shí),Webpack 偵測(cè)到這些變化。
編譯模塊:
一旦檢測(cè)到變更,Webpack 重新編譯受影響的模塊(模塊包括 JavaScript、CSS、模板文件等)。這一步只重新編譯變更的部分,而不是整個(gè)應(yīng)用。
生成補(bǔ)?。?/p>
編譯完成后,Webpack 生成更新的模塊,并將這些模塊打包成補(bǔ)丁(Hot Update Chunk),包含變更的模塊和相應(yīng)的依賴關(guān)系。
通知客戶端: Webpack 開(kāi)發(fā)服務(wù)器(Webpack Dev Server)通過(guò) WebSocket 向客戶端(瀏覽器)發(fā)送更新通知。通知內(nèi)容包括更新的模塊 ID 和哈希值等信息。應(yīng)用更新:
客戶端收到更新通知后,通過(guò) WebSocket 從開(kāi)發(fā)服務(wù)器請(qǐng)求補(bǔ)丁數(shù)據(jù)。客戶端運(yùn)行 HMR 運(yùn)行時(shí)邏輯,動(dòng)態(tài)地將新的模塊替換進(jìn)運(yùn)行中的應(yīng)用。
Webpack HMR 關(guān)鍵組件
Webpack Dev Server Webpack Dev Server 是一個(gè)開(kāi)發(fā)服務(wù)器,提供靜態(tài)文件服務(wù)、實(shí)時(shí)重載以及 HMR 支持。它在內(nèi)存中保存最新的編譯結(jié)果,減少文件系統(tǒng) I/O 操作,提高性能。 HMR Runtime HMR 運(yùn)行時(shí)是注入到打包輸出中的一段 JavaScript 代碼,負(fù)責(zé)處理模塊更新。它通過(guò) WebSocket 與 Webpack Dev Server 通信,接收更新通知并應(yīng)用補(bǔ)丁。
熱更新的應(yīng)用過(guò)程:
更新檢查:
HMR Runtime 通過(guò) WebSocket 接收來(lái)自 Webpack Dev Server 的更新通知。
請(qǐng)求更新模塊:
HMR Runtime 發(fā)送請(qǐng)求獲取更新模塊的補(bǔ)丁數(shù)據(jù)(包含新的模塊代碼和更新的哈希值)。
模塊熱替換:
HMR Runtime 加載新的模塊代碼,調(diào)用模塊的 accept 或 dispose 鉤子函數(shù),執(zhí)行模塊熱替換邏輯。 accept 鉤子函數(shù)允許模塊在更新時(shí)執(zhí)行自定義邏輯,例如重新渲染組件。 dispose 鉤子函數(shù)允許模塊在被替換前執(zhí)行清理工作,例如移除事件監(jiān)聽(tīng)器或保存狀態(tài)。
例子:當(dāng)然也可在本main.js中體會(huì)
if (module.hot) {
module.hot.accept('./moduleA.js', function() {
console.log('moduleA updated');
// 處理更新后的邏輯
render();
});
module.hot.dispose(function() {
console.log('Cleaning up before module is replaced');
// 清理邏輯,例如移除事件監(jiān)聽(tīng)器
});
}
function render() {
const content = require('./moduleA.js');
document.getElementById('app').innerHTML = content;
}
render();
在這個(gè)示例中,當(dāng) moduleA.js 被更新時(shí),HMR Runtime 會(huì)調(diào)用 accept 鉤子函數(shù)重新渲染內(nèi)容,并在模塊被替換前調(diào)用 dispose 鉤子函數(shù)進(jìn)行清理工作。
總結(jié)
Webpack 的熱更新機(jī)制通過(guò)監(jiān)聽(tīng)文件變化、重新編譯受影響模塊、生成并應(yīng)用補(bǔ)丁來(lái)實(shí)現(xiàn)模塊的熱替換。 關(guān)鍵組件包括 Webpack Dev Server 和 HMR Runtime,它們共同配合,使得開(kāi)發(fā)者可以在不刷新整個(gè)頁(yè)面的情況下高效地進(jìn)行模塊更新。 通過(guò)使用 HMR,開(kāi)發(fā)者可以顯著提升開(kāi)發(fā)體驗(yàn)和效率。
開(kāi)啟熱更新,配置package.json文件 配置–hot
"scripts": {
"dev": "webpack-dev-server --open --hot"
}
不同環(huán)境中的配置
通過(guò)運(yùn)行命令env區(qū)別: 打包命令 配置在script上
webpack --env production
實(shí)際操作 webpack.common.js 根據(jù)env判斷環(huán)境做相應(yīng)處理
const config = {
}
//生產(chǎn)環(huán)境下的配置
if(env==='production'){
config.mode = 'prodution'
config.devtool = false
config.plugin =[
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
}
}
大型項(xiàng)目建議
不同環(huán)境對(duì)應(yīng)不同配置文件
webpack.common.js 通用配置webpack.dev.js 開(kāi)發(fā)環(huán)境配置webpack.prod.js 生產(chǎn)環(huán)境配置
webpack-merge合并邏輯
安裝依賴
npm i --save-dev webpack-merge
配置使用,比如在生產(chǎn)環(huán)境配置 webpack.prod.js中:
//通用配置
const common = require('./webpack.common');
//合并配置
const merge = require('webpack-merge');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = merge(common,{
mode:'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
打包命令構(gòu)建,配置在package.json中的script命令
webpack --config webpack.prod.js
definePlugin注入變量
通常用于不同環(huán)境的區(qū)別
在webpack.prod.js定義
const webpack = require('webpack');
module.exports={
mode:'none',
entry: "./src/main.js",
output:{
filename: 'bundle.js',
},
plugins:[
new webpack.DefinePlugin({
API_BASE_URL:`"https:''api/example.com"`
})
]
}
在main.js測(cè)試
//可以看到結(jié)果https:''api/example.com
console.log(API_BASE_URL);
驗(yàn)證:執(zhí)行打包npm run build:prod可以看到bundle.js有https://api.example.com
Tree Shaking 搖樹
tree shaking:將枯樹葉搖落:即意思為自動(dòng)檢測(cè)出代碼中未引用的代碼,移除未用的多余代碼。 生產(chǎn)環(huán)境默認(rèn)開(kāi)啟!生產(chǎn)模式默認(rèn)開(kāi)啟tree shaking功能! 運(yùn)用
自動(dòng)開(kāi)啟
這里我在editor.js定義了一個(gè)未被引用的console.log
開(kāi)發(fā)環(huán)境:運(yùn)行npm run build:dev打包就打印出來(lái)了,未開(kāi)啟tree shaking功能生產(chǎn)環(huán)境:運(yùn)行npm run build:prod打包就沒(méi)有打印出來(lái),生產(chǎn)模式默認(rèn)開(kāi)啟tree shaking功能
1-指定生產(chǎn)環(huán)境
或打包時(shí)候指定為生產(chǎn)環(huán)境:
package.json script配置命令
webpack --mode prodution
或者配置中指定mode
module.exports = {
mode:'prodution'
}
運(yùn)行命令
npm run build:prod
2-手動(dòng)開(kāi)啟tree shaking
主要通過(guò)配置 optimization 比如在開(kāi)發(fā)環(huán)境下配置
// 開(kāi)發(fā)環(huán)境打包配置
const common = require('./webpack.common');
// v5之前
// const merge = require('webpack-merge');
//v5版本之后 webpack-merge v5 之后的導(dǎo)入方式有所不同,你需要從 webpack-merge 包中導(dǎo)入 merge 函數(shù)。
const {merge} = require('webpack-merge');
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-module-source-map',
//相當(dāng)于tree shaking:集中配置webpack當(dāng)中優(yōu)化功能,下面二者缺一不可
optimization: {
//只導(dǎo)出使用的模塊 負(fù)責(zé)標(biāo)記【枯樹葉】
usedExports: true,
//壓縮代碼 負(fù)責(zé)【搖掉】他們
minimize: true,
}
})
運(yùn)行命令
npm run build:dev
合并模塊ConcatenateModules
主要通過(guò)在optimization配置concatenateModules:true,同時(shí)為了更好地看到效果,關(guān)閉掉minimizee:true 作用:盡可能將所有模塊合并輸出到一個(gè)函數(shù)中,就不是一個(gè)模塊對(duì)應(yīng)一個(gè)函數(shù)了。又名 Scope Hoisting 作用域提升
// 開(kāi)發(fā)環(huán)境打包配置
const common = require('./webpack.common');
// v5之前
// const merge = require('webpack-merge');
//v5版本之后 webpack-merge v5 之后的導(dǎo)入方式有所不同,你需要從 webpack-merge 包中導(dǎo)入 merge 函數(shù)。
const {merge} = require('webpack-merge');
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-module-source-map',
//相當(dāng)于tree shaking:集中配置webpack當(dāng)中優(yōu)化功能
optimization: {
//只導(dǎo)出使用的模塊 負(fù)責(zé)標(biāo)記【枯樹葉】
usedExports: true,
//壓縮代碼 負(fù)責(zé)【搖掉】他們
// minimize: true,
//這里:合并模塊!!
concatenateModules: true,
}
})
運(yùn)行查看
npm run buil
Tree Shaking & Babel
webpack發(fā)展非???,有人提出使用了babel-loader,tree shaking就會(huì)失效。 原因是 tree shaking使用的前提是ES Modules.交給Webpack打包的代碼必須使用ES Modules.而babel-loader是將ES Modules轉(zhuǎn)換成CommonJS。
情況一:babel-loader 會(huì)判斷是否usedExport,如果有,就禁用ES Module的轉(zhuǎn)換。tree shaking生效情況二:babel-loader在presets里面配置了modules為commonjs則為強(qiáng)制轉(zhuǎn)換,tree shaking不生效 具體如下代碼:
const webpack = require('webpack');
const path = require("path");
module.exports = {
mode: "none",
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.join(__dirname, "/dist"),
},
//生產(chǎn)環(huán)境 選擇none 為了不暴露源代碼 隱患 調(diào)試是開(kāi)發(fā)階段的事情 或者nosources-source-map
devtool: 'cheap-module-source-map',
optimization: {
//?。?!只導(dǎo)出使用的模塊 負(fù)責(zé)標(biāo)記【枯樹葉】 但是使用了babel-loader就看具體情況,強(qiáng)制轉(zhuǎn)換commonjs這次就失效
usedExports: true,
//壓縮代碼 負(fù)責(zé)【搖掉】他們
minimize: true,
//這里:合并模塊!!
concatenateModules: true,
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
//將ES Modules轉(zhuǎn)換為CommonJS
presets: [
['@babel/preset-env', {
//默認(rèn)是auto自動(dòng),寫了commonjs是強(qiáng)制轉(zhuǎn)換為commonjs,此時(shí)usedExports無(wú)法生效,即tree shaking無(wú)法生效
modules: 'commonjs'
}]
],
}
}
}
]
}
}
SideEffects副作用特性
適用前提:確保你的代碼沒(méi)有副作用!! sideEffects一般用于NPM包標(biāo)記是否有副作用,生產(chǎn)環(huán)境下默認(rèn)開(kāi)啟,開(kāi)啟了之后沒(méi)有用到的模塊就不再會(huì)打包。 在optimization中配置
標(biāo)識(shí)是否有副作用
在 Webpack 中,“副作用”(side effects)這個(gè)術(shù)語(yǔ)也有特定的意義,通常用于描述模塊是否對(duì)導(dǎo)入它的代碼產(chǎn)生額外影響。具體來(lái)說(shuō),Webpack 會(huì)使用 sideEffects 字段來(lái)標(biāo)記一個(gè)模塊或包是否包含副作用,以便進(jìn)行更好的優(yōu)化,例如 Tree Shaking(移除未使用的代碼)。
package.json中
{
"name": "my-library",
"version": "1.0.0",
"sideEffects": false
}
在這個(gè)例子中,設(shè)置 “sideEffects”: false 表示模塊沒(méi)有副作用,Webpack 可以安全地移除未使用的代碼部分。如果某些文件確實(shí)有副作用,可以指定它們:
{
"name": "my-library",
"version": "1.0.0",
"sideEffects": ["*.css", "*.scss"]
}
這告訴 Webpack 除了 .css 和 .scss 文件外,其他文件都沒(méi)有副作用。
開(kāi)啟副作用功能
在編程中,“副作用”(side effect)指的是函數(shù)或表達(dá)式在計(jì)算結(jié)果之外,還會(huì)對(duì)程序的其他部分產(chǎn)生影響的情況。通俗一點(diǎn)說(shuō),副作用是指除了返回一個(gè)值以外,函數(shù)或表達(dá)式還做了其他事情,這些事情會(huì)影響到外部的狀態(tài)或環(huán)境。比如說(shuō)有個(gè)方法Number.propotype.pad = function(){}通過(guò)原型改變Number方法這也是副作用,開(kāi)啟了副作用將不再打包這段函數(shù)
webpack.dev.js:
optimization:{
//開(kāi)啟副作用,生產(chǎn)環(huán)境默認(rèn)開(kāi)啟,沒(méi)用到的模塊不再打包
sideEffects: true
}
多入門打包
entry定義為一個(gè)對(duì)象,一個(gè)屬性就是打包的一個(gè)路徑,多個(gè)入口打包出多個(gè)結(jié)果,filename不直接定義為一個(gè)固定名稱。 但是若是希望打包出來(lái)也為多個(gè)index.html就需要獨(dú)立配置,都則將會(huì)打包到一個(gè)index.html中引入。則需在HtmlWebpackPlugin中定義chunks:[‘index’] 實(shí)現(xiàn)步驟
定義entry多個(gè)入口 對(duì)象方式修改輸出多個(gè)文件名稱區(qū)分定義輸出多個(gè)不用名稱index.html,多個(gè)引入代替一個(gè)index.html引入多個(gè)
// 開(kāi)發(fā)環(huán)境打包配置
const common = require('./webpack.common');
const HTMLWebpackPlugin = require('html-webpack-plugin');
// v5之前
// const merge = require('webpack-merge');
//v5版本之后 webpack-merge v5 之后的導(dǎo)入方式有所不同,你需要從 webpack-merge 包中導(dǎo)入 merge 函數(shù)。
const {merge} = require('webpack-merge');
const path = require("node:path");
module.exports = merge(common, {
// 1-定義多個(gè)入口
entry:{
main: './src/main.js',
hello:'./src/hello.js'
},
//2-修改輸出名稱
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, "/dist"),
},
//3-定義輸出多個(gè)不同名稱index.html
plugins: [
new HTMLWebpackPlugin({
title: 'webpack main',
filename: 'main.html',
//定義chunks連接
chunks:['main']
}),
new HTMLWebpackPlugin({
title: 'webpack hello',
filename: 'hello.html',
//定義chunks連接
chunks:['hello']
})
],
mode: 'development',
devtool: 'cheap-module-source-map',
//相當(dāng)于tree shaking:集中配置webpack當(dāng)中優(yōu)化功能
optimization: {
//只導(dǎo)出使用的模塊 負(fù)責(zé)標(biāo)記【枯樹葉】
usedExports: true,
//壓縮代碼 負(fù)責(zé)【搖掉】他們 僅僅對(duì)于js文件生效 css不生效
minimize: true,
//這里:合并模塊!!
concatenateModules: true,
//副作用,開(kāi)啟了之后沒(méi)有用到的模塊就不再會(huì)打包。
sideEffects: true,
}
})
不同打包之提取公共模塊
代碼中會(huì)有相同的共用部分造成代碼復(fù)用率低。 利用optimization中配置splitChunks代碼分割為chunks:'all’開(kāi)啟提取打包公用模塊
//相當(dāng)于tree shaking:集中配置webpack當(dāng)中優(yōu)化功能
optimization: {
//提取公共部分
splitChunks:{
chunks: 'all'
}
}
打包查看
npm run build:dev
可看到dist文件中生成了多余的js文件名為二者合一公用模塊
動(dòng)態(tài)導(dǎo)入
動(dòng)態(tài)導(dǎo)入按需加載極大節(jié)省我們的帶寬以及流量,提高響應(yīng)速度。 事例情況:就是比如點(diǎn)擊菜單A顯示A內(nèi)容,菜單B顯示B內(nèi)容。而不是一開(kāi)始就加載全部AB內(nèi)容,而是做到按需加載。 具體原理:按照監(jiān)聽(tīng)菜單瞄點(diǎn),是什么瞄點(diǎn)加載什么內(nèi)容
具體原理實(shí)現(xiàn)代碼:
const render = ()=>{
const hash = window.location.hash || "#posts";
const mainElements = document.querySelector('.main');
mainElements.innerHTML = '';
if(hash==='#posts'){
import('./posts/post').then(({default:posts})=>{
mainElements.appendChild(posts());
})
}else if(hash==='#album'){
import('./posts/album').then(({default:album})=>{
mainElements.appendChild(album());
})
}
}
render();
//監(jiān)聽(tīng)改變
window.addEventListener('hashchange', render);
魔法注釋
通過(guò)分包時(shí)候添加注釋,相同的chunks name就會(huì)打包到一起
實(shí)現(xiàn)如下:
const render = () => {
const hash = window.location.hash || "#posts";
const mainElements = document.querySelector('.main');
mainElements.innerHTML = '';
if (hash === '#posts') {
//魔法注釋
import(/* webpackChunkname:'posts' */ './posts/post').then(({default: posts}) => {
mainElements.appendChild(posts());
})
} else if (hash === '#album') {
//魔法注釋
import(/* webpackChunkname:'album' */ './posts/album').then(({default: album}) => {
mainElements.appendChild(album());
})
}
}
render();
//監(jiān)聽(tīng)改變
window.addEventListener('hashchange', render);
輸出文件名 Hash
啟用靜態(tài)資源,用戶則不需要重復(fù)請(qǐng)求加載得到這些資源,整體響應(yīng)速度就會(huì)有一個(gè)大幅度提升。 為了解決:
緩存失效時(shí)間設(shè)置的過(guò)短,效果則不明顯緩存失效時(shí)間設(shè)置的過(guò)長(zhǎng),沒(méi)辦法及時(shí)更新到客戶端
生產(chǎn)模式下,文件名使用Hash哈希值,一旦資源文件發(fā)生改變,則會(huì)一起改變。 對(duì)于客戶端而言,全新的文件名就是全新的請(qǐng)求,則沒(méi)有緩存的問(wèn)題。那緩存時(shí)間設(shè)置的非常長(zhǎng),也不擔(dān)心更新問(wèn)題。
hash模式
為了減少靜態(tài)資源請(qǐng)求,設(shè)置緩存,則減少服務(wù)器的請(qǐng)求。
緩存時(shí)間設(shè)置的短,請(qǐng)求頻繁。緩存時(shí)間設(shè)置的長(zhǎng),用戶不能及時(shí)更新
解決方式:設(shè)置hash哈希值
hash:項(xiàng)目中任何一個(gè)地方改動(dòng),打包都會(huì)造成全部文件變化chunkhash推薦: 根據(jù)修改代碼處,打包只變化修改的代碼處文件名(推薦使用)contenthash:根據(jù)代碼修改代碼處,修改只變化內(nèi)容的文件名
通過(guò):length指定哈希長(zhǎng)度,默認(rèn)是20位
//提取css文件
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports={
output:{
filename:'[name]-[chunkhash:8].bundle.js',
path: path.join(__dirname, "/dist"),
},
plugins:[
new MiniCssExtractPlugin({
filename:'[name]-[chunkhash:8].bundle.css'
})
]
}
柚子快報(bào)邀請(qǐng)碼778899分享:webpack學(xué)習(xí)
參考閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。