柚子快報激活碼778899分享:前端 Webpack學(xué)習(xí)記錄
柚子快報激活碼778899分享:前端 Webpack學(xué)習(xí)記錄
記錄學(xué)習(xí)筆記,歡迎指正
1.大型項(xiàng)目為什么需要打包
1.1 使用打包工具原因
編譯或轉(zhuǎn)譯文件:
項(xiàng)目中可能用到ES6語法,可能有瀏覽器不支持。需要打包工具將代碼編譯輸出為ES5語法的代碼。項(xiàng)目中可能使用Sass,Less等預(yù)處理器,瀏覽器本身并不支持。需要打包工具將代碼編譯輸出為CSS代碼。同編譯器的特點(diǎn),打包工具具有編譯器可以優(yōu)化代碼。
處理模塊:
大型項(xiàng)目可以劃分為若干模塊,模塊的開發(fā)需要依賴規(guī)范。例如UMD,AMD,CMD,ESM規(guī)范。瀏覽器默認(rèn)不支持除了ESM的規(guī)范,需要打包工具將項(xiàng)目打包成js文件,css文件后通過入口文件引入。
注意:編譯和轉(zhuǎn)譯的區(qū)別?
編譯是將高級程序語言的代碼轉(zhuǎn)為低級程序語言的代碼,例如匯編代碼,或者適合于某種虛擬機(jī)上運(yùn)行的代碼。轉(zhuǎn)譯是將高級程序語言的代碼轉(zhuǎn)為另一種高級程序語言的代碼。嚴(yán)格來說babel應(yīng)該稱之為轉(zhuǎn)譯器。
注意:webpack的能力包含編譯CSS,Typescript等文件嗎?
webpack本身功能比較局限只能編譯JavaScript和Json文件,將模塊進(jìn)行處理。編譯ES6,Sass,Less等需要webpack安裝其它的loader加載器。
注意:為什么使用ESM規(guī)范還需要打包?
避免頻繁網(wǎng)絡(luò)請求:
如果使用ESM規(guī)范但不打包,那么每使用一個模塊都要重新請求,頻繁請求會影響瀏覽器緩存效率,導(dǎo)致緩存命中率低。 如果使用ESM規(guī)范但打包,那么可以把模塊聚集在一個或屈指可數(shù)的幾個文件中,不會讓瀏覽器頻繁請求文件 提升兼容性:
大部分瀏覽器都支持ESM規(guī)范,但是考慮可能有低版本瀏覽器不支持,或支持度低。
2.包管理工具常識
2.1 開發(fā)環(huán)境和生產(chǎn)環(huán)境
開發(fā)環(huán)境:
項(xiàng)目未打包時運(yùn)行的環(huán)境。依賴于開發(fā)時依賴和生產(chǎn)時依賴的包。
生產(chǎn)時依賴的包可以流向開發(fā)時依賴,可以通過集合的包含關(guān)系來理解。如果想移動生產(chǎn)時依賴的包到開發(fā)時依賴,直接執(zhí)行 npm i sylvester -D類似的命令即可。 通常webpack配置全局變量process.env.NODE_ENV為development。
生產(chǎn)環(huán)境:
項(xiàng)目打包后運(yùn)行的環(huán)境。依賴于生產(chǎn)時依賴的包。
開發(fā)時依賴的包不能流向生產(chǎn)時依賴,可以通過集合的包含關(guān)系來理解。如果想移動包,那么只能刪除開發(fā)時依賴的指定包,之后再在生產(chǎn)時依賴中重新安裝。 通常webpack配置全局變量process.env.NODE_ENV為production。
舉例:
例如用于代理前綴的修改。通常為了便于做反向代理,會把所有JavaScript發(fā)起的請求的URL添加前綴做統(tǒng)一代理。如果不使用項(xiàng)目中統(tǒng)一封裝的網(wǎng)絡(luò)請求,例如使用window.location.href下載文件,并且生產(chǎn)環(huán)境下前后端都塞入tomcat,不存在跨域,那么可以通過process.env.NODE_ENV來判斷環(huán)境來決定是否添加前綴。
2.2 包的常用指令
npm install/add/i/ins…
空: 安裝開發(fā)時依賴和生產(chǎn)時依賴的所有包。 某個包: 默認(rèn)使用–save命令安裝這個包。 –save或-S + 某個包: 安裝生產(chǎn)時依賴,記錄到package.json的dependencies中。
舉例: react,ahooks,styled-components注意: 安裝包時默認(rèn)是–save指令是為了保證項(xiàng)目的正常運(yùn)行。如果默認(rèn)安裝到開發(fā)時環(huán)境或全局環(huán)境,那么如果交付遠(yuǎn)程服務(wù)器打包時,可能導(dǎo)致缺少必要的包。 –save-dev或-D + 某個包: 安裝開發(fā)時依賴,記錄到package.json的devDependencies中。
舉例: webpack,typescript,jest,eslint注意: 生產(chǎn)時依賴和開發(fā)時依賴不存在交集,因此無法同時將某個包安裝到兩個環(huán)境中,即 npm install sylvester -D -S不會同時安裝到兩個環(huán)境。 –global或-g + 某個包: 安裝到全局環(huán)境。
注意:
項(xiàng)目之間的依賴最好是獨(dú)立管控的,不推薦全局安裝。如果項(xiàng)目要使用遠(yuǎn)程服務(wù)器打包(打包后交付測試或部署上線)走自動化流程,那么遠(yuǎn)程服務(wù)器打包時找不到你在本地全局安裝的包,因?yàn)槟悴粫阉涗浀絧ackage.json中 –no-save + 某個包: 安裝依賴。但不記錄到開發(fā)時依賴和生產(chǎn)時依賴。
注意: 這通常用于測試某個第三方依賴,并且你的同事暫時不會安裝它。不過這并不是妥當(dāng)?shù)姆椒?,因?yàn)槟憧赡芫帉懸蕾囉谶@個第三方依賴的代碼,如果你提交代碼,會導(dǎo)致其他人報錯。推薦的方法是應(yīng)該新拉一個分支,或者新建一個項(xiàng)目來進(jìn)行測試。 –force或-f (+ 某個包): 強(qiáng)制安裝。 –production: 只安裝生產(chǎn)時依賴。
注意:
只是依據(jù)package.json安裝生產(chǎn)時依賴,安裝新依賴時使用該命令將被忽視。因此這個命令的表現(xiàn)和 npm update的表現(xiàn)類似,可以用來更新依賴。不論在什么環(huán)境下webpack打包都會依賴開發(fā)時依賴和生產(chǎn)時依賴,因此交付遠(yuǎn)程服務(wù)器打包時應(yīng)該使用 npm install而不是 npm install --production目前沒有只安裝開發(fā)時依賴的指令,因?yàn)殚_發(fā)環(huán)境依賴于開發(fā)時依賴和生產(chǎn)時依賴,只安裝開發(fā)時依賴沒有任何意義。
**npm update **
空: 忽略開發(fā)時依賴的更新,只更新生產(chǎn)時的依賴。其它與安裝時指令類似,如果想指定也更新開發(fā)時依賴,可以添加特殊指令。
npm uninstall/rm
與安裝時指令類似
2.3 包版本計算器
版本命名規(guī)范:
2.3.5-beta.3
major: 大版本minor: 小版本(可選)patch: 補(bǔ)丁版本(可選)prerelease_tag: 預(yù)發(fā)布標(biāo)簽(可選)
alpha: 內(nèi)測版本beta: 公測版本rc: 候選版本pre: 前面的三個數(shù)字類型的版本不足以標(biāo)識修改時使用pre prerelease_version: 預(yù)發(fā)布版本(可選)
版本前綴含義:
*: 允許升級到任何版本。^: 不超過左邊非零數(shù)字
舉例:
^2.3.1表示 2.3.1 <= version < 3.0.0^0.3.1表示 0.3.1 <= version < 0.4.0^0.0.1表示 0.0.1 <= version < 0.0.2 ~: 允許補(bǔ)丁版本升級到任意發(fā)布的版本。
舉例:
~2.3.1表示 2.3.1 <= version < 2.4.0~2.3表示 2.3.0 <= version < 2.4.0~2表示 2.0.0 <= version < 2.1.0 >,<,>=,<=: 按符號字面意思表示版本允許升級的范圍。
模糊版本更新機(jī)制:
npm install忽略模糊版本
npm update更新到最新模糊版本
2.4 包執(zhí)行者
npx + 包 + 包對應(yīng)的指令
npx是包執(zhí)行者,在項(xiàng)目里npx會去node_modules/.bin中尋找包的執(zhí)行程序執(zhí)行。例如我沒有全局安裝webpack,只是在項(xiàng)目中用npm安裝,并且我想執(zhí)行webpack的指令,那么我可以使用npx。
舉例:
無webpack.config.js:npx webpack ./src/main.js --mode=development 有webpack.config.js:npx webpack
3.Webpack核心配置點(diǎn)
3.1 概述
entry(入口): 打包入口,從一個或多個入口打包
output(輸出): 打包文件輸出位置
loader(加載器): webpack本身只能編譯JavaScript處理模塊化。編譯CSS等其它資源需要借助loader。
plugins(插件): 擴(kuò)展webpack的功能。
devServer(服務(wù)器): 開發(fā)模式下運(yùn)行項(xiàng)目并熱更新。
mode(模式): 開發(fā)模式development和生產(chǎn)模式production。
3.2 Output
3.2.1 配置入口文件輸出位置
Node全局變量和方法:
__dirname:__dirname返回當(dāng)前文件所在文件夾的絕對路徑 __filename: __filename返回當(dāng)前文件的絕對路徑 path.resolve: 負(fù)責(zé)解析路徑。
將若干個表示相對路徑的參數(shù)拼接到當(dāng)前文件所在的文件夾的絕對路徑上如果有參數(shù)是絕對路徑,那么忽略最后一個絕對路徑前的參數(shù)
module.exports = {
output: {
path: path.resovle(__dirname, "dist")
filename: "static/js/main.js"
}
}
3.2.2 配置自動清空上次打包效果
module.exports = {
output: {
clean: true
}
}
3.3 loader
3.3.1 配置樣式loader
解析順序: loader可以通過配置use屬性進(jìn)行鏈?zhǔn)秸{(diào)用,默認(rèn)規(guī)定鏈表頭是數(shù)組尾部,因此loader是從右向左解析。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
{
test: /\.s[ac]ss$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.styl$/,
use: ["style-loader", "css-loader", "stylus-loader"],
},
]
}
}
3.3.2 配置樣式兼容性loader
在package.json中配置需要兼容的瀏覽器,否則兼容性loader不會生效
// package.json中配置需要兼容的瀏覽器
{
"browserslist": {
"last 2 version" // 匹配的瀏覽器的最近的兩個版本
"> 1%" // 全球用戶使用率超過1%的瀏覽器
"not dead" // 不考慮官方聲明已經(jīng)不再維護(hù)的瀏覽器
}
}
配置css兼容性loader
樣式兼容性loader的位置: 在css-loader后面,在css預(yù)解析器loader的前面配置loader的方式: 在use中直接寫loader名稱表示使用默認(rèn)配置,使用對象方式寫loader和options可以自定義配置
const getStyleLoader = (pre) => {
return [
MiniCssExtractPlugin.loader,
"css-loader",
// 在use中直接寫loader的名稱表示使用默認(rèn)配置,下面是通過options自定義配置,配置依據(jù)來源于官網(wǎng)
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre,
].filter(Boolean);
}
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: getStyleLoader()
},
{
test: /\.less$/,
use: getStyleLoader("less-loader")
},
{
test: /\.s[ac]ss$/,
use: getStyleLoader("sass-loader")
},
{
test: /\.styl$/,
use: getStyleLoader("stylus-loader")
},
]
}
}
3.3.3 配置腳本兼容性loader
配置babel-loader??梢院蜆邮郊嫒菪詌oader類比:
都是處理兼容性的loader都用到了自定義配置自定義配置都可以提出來形成獨(dú)立的配置文件
{
test: /\.js$/,
// 不處理的文件
exclude: /node_modules|bower_components/,
use: {
loader: "babel-loader",
// 也可以寫?yīng)毩⑽募abel.config.js來配置下述內(nèi)容
options: {
presets: ["@babel/preset-env"],
},
},
}
3.3.4 配置資源loader
配置資源輸出路徑: generator中的filename指定了輸出的文件名或路徑。
有效配置: 只有設(shè)置了type為資源類型 asset, asset/resource, ...的文件配置輸出路徑才有效。無效配置: 如果給css或者css預(yù)解析文件配置輸出路徑是無效的,這些loader的行為是把css輸出到j(luò)s文件中,因此不會輸出css文件,因此不會有文件在期望的輸出路徑生成。但是你可以借助plugin擴(kuò)展loader的能力來完成這個行為。 資源打包成DataUrl形式: 資源打包成DataUrl可以避免發(fā)請求。
DataUrl形式:
module.exports = {
modules: {
rules: [
// 圖片文件
{
test: /\.(png|svg|jpg|jpeg|gif|webp)$/i,
type: 'asset',
parser: {
// 小于100KB的圖片解析為內(nèi)置資源dataUrl形式
dataUrlCondition: {
maxSize: 100 * 1024,
},
},
generator: {
// name: 文件名
// hash: 生成文件的唯一標(biāo)識
// 10: 限制hash長度最多為10
// ext: 擴(kuò)展名
// query: 查詢參數(shù)
filename: 'images/[name][hash:10][ext][query]',
},
},
// 字體文件
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset',
generator: {
filename: 'fonts/[name][hash][ext]',
},
},
]
}
}
3.4 plugin
3.4.1 配置開發(fā)規(guī)范plugin
webpack配置
const ESLintPlugin = require("eslint-webpack-plugin");
module.exports = {
plugins: [
new ESLintPlugin({
// eslint檢測的文件夾
context: path.resolve(__dirname, "../src"),
})
]
}
.eslintrc配置
注意: eslint配置文件.eslintrc.js一般放在根目錄下。配置完后立即生效,在開發(fā)代碼時會自動調(diào)用解析器檢查是否符合配置的規(guī)范。
配置說明:
extends: 配置要繼承的eslint配置文件。
舉例: 下面的配置繼承于 eslint:recommended,之后又添加了部分自定義配置。也可以安裝 eslint-config-react-app來繼承于 react-app。 env: 配置代碼運(yùn)行的環(huán)境。
舉例: 下面配置node環(huán)境和browser環(huán)境,那么代碼可能會運(yùn)行在這兩個環(huán)境上,那么eslint在做檢查時要支持對應(yīng)環(huán)境上的全局變量。 parser: 配置eslint調(diào)用的解析器。默認(rèn)解析器是Espree。
舉例: 即使配置parserOptions,Espree也會判定在函數(shù)中使用import動態(tài)加載模塊是錯誤的。那么這時可以更換parser,例如babel-eslint或@typescript-eslint/parser parserOptions: 修改parse的配置。
舉例: Espree默認(rèn)檢查es5語法,項(xiàng)目如果用es6規(guī)范的代碼,那么需要指定ecmaVersion支持對應(yīng)版本的語法。sourceType指定文件的模塊系統(tǒng),是module模塊還是單獨(dú)的script腳本。 rules: 修改eslint的檢查規(guī)則。核心配置內(nèi)容。
舉例:
每個配置項(xiàng)都對應(yīng)著值 0, 1, 2和off, warn, error。表示命中該規(guī)則時“在文件中的表現(xiàn)”和“本地服務(wù)器上的表現(xiàn)”,例如文件中顯示黃色波浪線,紅色波浪線,不阻塞服務(wù)器運(yùn)行,但是輸出提示,阻塞服務(wù)器運(yùn)行。下面的配置表示使用var聲明變量時會報警告。聲明變量后未使用會報警告。 plugins: 擴(kuò)展eslint能力。
舉例: 配置插件使eslint可以檢查JSX。這樣擴(kuò)展了rules內(nèi)容,eslint本身的所有rules可以在官網(wǎng)上查到配置,安裝插件可以提供新的校驗(yàn)?zāi)芰托r?yàn)選項(xiàng)。例如安裝eslint-plugin-react,提供JSX和react框架風(fēng)格的相關(guān)檢查。
module.exports = {
extends: ["eslint:recommended"],
env: {
node: true,
browser: true,
},
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2020,
sourceType: "module"
},
plugins: ["@typescript-eslint"],
rules: {
// 使用var做警告
"no-var": "warn",
// 未使用的變量做警告
"no-unused-vars": "warn",
},
};
3.4.2 配置文檔輸出plugin
配置輸出HTML的原因: 沒有配置前打包輸出沒有html文件,只是在public文件夾中的入口html文件中配置引入腳本的位置。如果打包后的腳本名稱發(fā)生變化的話又需要重新配置引入比較麻煩。
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [new HtmlWebpackPlugin({
// 不使用默認(rèn)位置,自定義輸出位置
template: path.resolve(__dirname, "../public/index.html")
})]
}
3.4.3 配置提取樣式plugin
提取CSS文件的原因: 如果使用style-loader來實(shí)現(xiàn)CSS-In-JS的方式,進(jìn)入頁面時會有閃動效果。先解析js,解析完成后再通過style標(biāo)簽插入樣式,由無樣式的效果到有樣式的效果導(dǎo)致了頁面閃動。注意事項(xiàng): 用插件的loader代替style-loader
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
modules: {
// 用MiniCssExtractPlugin.loader代替style-loader避免把CSS輸出到JS中
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
},
{
test: /\.s[ac]ss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
{
test: /\.styl$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "stylus-loader"],
},
]
}
plugins: [
new MiniCssExtractPlugin({
// 不用默認(rèn)輸出位置,指定輸出位置
filename: "static/css/main.css",
})
]
}
3.4.4 配置壓縮樣式plugin
是否要安裝壓縮其它資源的plugin: js壓縮在生產(chǎn)模式下打包webpack會默認(rèn)輸出壓縮后的js (使用的Terser),配置輸出html的插件并且在生產(chǎn)模式下打包webpack也會默認(rèn)輸出壓縮后的html,但是css不會默認(rèn)壓縮,需要安裝壓縮樣式的plugin
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
plugins: [new CssMinimizerPlugin()]
}
3.5 devServer
3.5.1 配置熱更新服務(wù)器
// 開發(fā)服務(wù)器:不會輸出打包內(nèi)容到dist
devServer: {
host: "localhost",
port: "3000",
// 是否自動打開瀏覽器
open: true,
// 是否只局部重新構(gòu)建修改的文件,如果為false,那么已修改就會重新打包所有內(nèi)容(默認(rèn)值就是true)
hot: true
}
3.6 mode
3.6.1 開發(fā)模式和生產(chǎn)模式
需要配置mode為development或production,這會影響打包結(jié)果是否壓縮
舉例: 網(wǎng)上說可以在根目錄新建一個config文件夾來保存生產(chǎn)和開發(fā)環(huán)境的webpack配置文件。個人覺得使用一份webpack配置文件即可,在內(nèi)部進(jìn)行環(huán)境判斷即可。 開發(fā)模式不需要指定output的輸出目錄,開發(fā)模式下一般采用webpack-dev-server服務(wù)器運(yùn)行,會將打包結(jié)果輸入內(nèi)存。
注意: 熱更新會導(dǎo)致頻繁打包來更新打包后的文件,webpack-dev-server將文件輸出到內(nèi)存可以提高IO讀寫效率
3.7 內(nèi)部過程
3.7.1 Module
Module是模塊,指的是文件,不是webpack運(yùn)行時內(nèi)部過程的概念。
模塊支持性: webpack支持多種模塊系統(tǒng),例如ESM,AMD,CommonJS等。
模塊類型:
模塊一定是文件。如果通過import從某個js文件中導(dǎo)入了一個函數(shù),那么函數(shù)不會被視為webpack模塊,函數(shù)所在的js文件將被視為模塊。 模塊不一定是js文件,在ESM系統(tǒng)中import可以導(dǎo)入各種資源,css文件,圖片等都可以被視為webpack模塊。
3.7.2 Chunk
Chunk定義:
Chunk是webpack打包的內(nèi)部過程中的概念,Chunk表示塊,是一種文件,產(chǎn)生在webpack的輸入到輸出過程中。
Chunk產(chǎn)生方式:
根據(jù)入口產(chǎn)生的依賴圖: 將文件系統(tǒng)看做圖,那么默認(rèn)情況下每個打包入口的所有可達(dá)節(jié)點(diǎn)的集合都會被裝載到一個chunk中。根據(jù)按需加載: 例如在ESM系統(tǒng)中每個 import()導(dǎo)入的模塊都會被裝載到一個新chunk中。這么做的意圖是將按需加載的模塊放到不同chunk中,然后輸出不同的bundle,最后在使用某個按需加載的模塊時就會發(fā)請求新的bundle文件,以此實(shí)現(xiàn)按需加載。
注意: 這是一個理想情況,chunk和bundle不一定是一一對應(yīng)的,默認(rèn)情況下它們是一對一,如果有特殊配置,例如code split,那么會有不同的對應(yīng)關(guān)系。因此真正按需加載時你想請求模塊A,可能也會同時請求到模塊B和模塊C,因?yàn)槟憧赡軐ε渲昧薱hunk組,使部分模塊打包到一個chunk中,最后使得按需加載的模塊都在一個bundle中。
舉例: chunk和bundle不一定是一一對應(yīng)的。下圖描述了一個chunk可以對應(yīng)多個最終輸出的bundle。如果使用了上文提到的提取樣式的插件就會有改效果。![外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳 根據(jù)代碼分割: 如果使用了下文提到的代碼分割來做優(yōu)化,那么將會按照配置規(guī)則來將模塊轉(zhuǎn)載到chunk中,可能導(dǎo)致一個模塊被裝到多個chunk的情況。
產(chǎn)生Chunk的時機(jī):
產(chǎn)生的chunk并不只是對代碼塊進(jìn)行組裝或拆分。產(chǎn)生chunk時,loader和plugin的大部分工作已經(jīng)完成了。chunk到bundle的過程主要是optimization配置和部分plugin配置在發(fā)揮作用,例如MiniCssExtractPlugin將css提取成單獨(dú)文件。
注意: code split只是控制生成chunk的規(guī)則,并不控制chunk到bundle的生成規(guī)則。
3.7.3 Bundle
bundle是文件,表示webpack最終輸出的文件。bundle不是webpack運(yùn)行時的內(nèi)部概念。
3.7.4 Chunk和Bundle的對應(yīng)關(guān)系
參考Chunk中的描述
4.Webpack優(yōu)化
4.1 開發(fā)體驗(yàn)優(yōu)化
使用 devtool中不同 source-map報錯信息和源代碼進(jìn)行對應(yīng),否則報錯信息會直接指向打包后的代碼。不同的 source-map記錄的報錯信息精度不同,因此打包時編譯的速度會受到影響。
開發(fā)模式: cheap-module-source-map
錯誤信息精確到行打包速度快 生產(chǎn)模式: source-map
錯誤信息精確到行和列打包速度比上面的慢
4.1.1 cheap-module-source-map
報錯信息精確到行
下圖中顯然是args.reduce調(diào)用時出錯,但是波浪線從第二行開頭開始。不能精確到從args開始。
報錯信息精確到行不代表不會顯示列信息,下圖顯示了錯誤在源代碼第二行第一列。只是這個列不夠精確,默認(rèn)就是第一列。
映射建立方式
打包后文件片段
對應(yīng)的源文件片段
打包后會生成json格式的.map映射文件描述映射關(guān)系。其中mappings描述了映射關(guān)系。指的是打包后文件到源文件的映射。
這里翻譯成js形式可以解讀為:
return args.reduce(function (sum, item) {映射到 args.reduce((sum, item) => (isNaN(item) ? sum : sum + item), 0)(); return isNaN(item) ? sum : sum + item;映射到 args.reduce((sum, item) => (isNaN(item) ? sum : sum + item), 0)(); }, 0)();映射到 args.reduce((sum, item) => (isNaN(item) ? sum : sum + item), 0)();
從編譯原理的角度理解:
詞法分析階段只記錄源代碼的行信息到詞素(token)上。行信息傳遞到語法分析階段生成的抽象語法樹上。依據(jù)抽象語法樹生成中間代碼時,中間代碼的每個詞素都能記憶源代碼的行信息。并且保證中間代碼某一行的詞素都對應(yīng)源代碼的同一行,否則換行。編譯完成后根據(jù)輸出代碼每行詞素記憶的行信息,建立.map映射文件,方便代碼出錯時快速定位到源文件。
4.1.2 source-map
報錯信息精確到行和列
與source-map相比,波浪線可以精確到調(diào)用語句??刂婆_報錯信息可以精確到調(diào)用語句所在的列。
映射建立方式
和cheap-module-source-map一樣都會建立.map映射文件,效果和上面一樣。
翻譯一下就是建立了詞素到詞素的映射關(guān)系,詞素到詞素的映射細(xì)粒度比行到行的映射細(xì)粒度小很多,所以生成的map文件也會大一些。
為什么生產(chǎn)模式下不能用cheap-module-source-map?
如果生成模式下使用cheap-module-source-map會導(dǎo)致無法精確定位錯誤信息到源文件,并且也不會生成.map映射文件。生產(chǎn)模式下js代碼是壓縮的,只有一行。從編譯原理的角度來講,即使代碼只有一行那也不影響我做映射,因?yàn)樵~法分析階段記錄的映射是精確到詞素(token)級別的。但是問題出在cheap-module-source-map工具上,它只會根據(jù)詞素記錄的行信息來建立行到行映射,即建立細(xì)粒度更大更模糊的映射。壓縮后的代碼可能只有一行,所以映射只會有一條,所以無法精確定位。并且不論詞法分析階段記錄再多信息也沒用,因?yàn)樽詈笾粫∫恍〔糠中畔⒆瞿:挠成洹?/p>
4.2 提升打包構(gòu)建速度
4.2.1 模塊熱更新—HotModuleReplacement
配置devServer
設(shè)置hot屬性為true,當(dāng)文件修改時只局部重新構(gòu)建。設(shè)置為false時,文件一旦修改就會對整個項(xiàng)目重新構(gòu)建。
hot屬性無法針對js文件。如果修改js可以局部重新構(gòu)建,那么可以考慮安裝vue-hot-loader或react-hot-laoder。
4.2.2 匹配唯一Loader一oneOf
配置loader
生產(chǎn)環(huán)境和開發(fā)環(huán)境都適用。如果不配置oneOf,那么處理css文件時能命中第一個loader,但是命中完成后還會向后遍歷看是否能命中其它loader。如果文件很多時,打包的時間復(fù)雜度的常數(shù)會很大。oneOf是一種優(yōu)化常數(shù)的方法。
rules: [
{
oneOf: [
{
test: /\.css$/,
use: getStyleLoader(),
},
{
test: /\.less$/,
// 類比上述過程,那就是先把less轉(zhuǎn)為css-loader,然后重復(fù)上述過程
use: getStyleLoader("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoader("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoader("stylus-loader"),
},
// 激活內(nèi)置的loader(見文檔外部資源模塊)
{
test: /\.(png|jpe?g|gif|webp)$/,
// 大圖盡量不轉(zhuǎn)base64,base64增長率太大。小圖片轉(zhuǎn)base64,base64增長率比較小,可以接受。
type: "asset",
parser: {
dataUrlCondition: {
// 小于300KB的圖片會轉(zhuǎn)base64以節(jié)省請求數(shù)量。(300KB有點(diǎn)大,是因?yàn)楸镜貨]有小圖)。
// 并且重新打包不會刪除之前資源,所以刪除dist后重新打包即可。
maxSize: 10 * 1024,
},
},
generator: {
// 輸出圖片名稱(也可以是路徑)
// hash: 圖片名稱(唯一的)。下面的是10表示只取前十位
// ext: 文件擴(kuò)展名
// query: url查詢參數(shù)
filename: "static/images/[hash:10][ext][query]",
},
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
generator: {
filename: "static/media/[hash:10][ext][query]",
},
},
// babel
{
test: /\.js$/,
// 不處理的文件
exclude: /node_modules|bower_components/,
use: {
loader: "babel-loader",
// 也可以寫?yīng)毩⑽募abel.config.js
// options: {
// presets: ["@babel/preset-env"],
// },
},
},
],
},
],
4.2.3 包含和排除—Include/Exclude
一般只針對js文件做處理,例如babel和eslint。并且要注意include和exclude只能存在一個。
// babel
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// 不處理的文件
exclude: /node_modules|bower_components/,
// 處理的文件(exclude和include只能使用一種)
// include: path.resolve(__dirname, '../src')
use: ["babel-loader"],
}
]
}
}
// eslint
module.exports = {
plugins: [
new ESLintPlugin({
// 檢測的文件夾
context: path.resolve(__dirname, "../src"),
// 不處理的文件
exclude: 'node_modules'
})
]
}
4.2.4 緩存—Cache
使用緩存原因
打包文件中往往都是js占比最大,并且每個js都會經(jīng)過Eslint插件和Babel加載器編譯。因此在重新構(gòu)建時可以緩存上次編譯結(jié)果,為重復(fù)構(gòu)建提速。
module.exports = {
module: {
rules: [
{
oneOf: [
// babel
{
test: /\.js$/,
exclude: /node_modules|bower_components/,
use: {
loader: "babel-loader",
options: {
// 開啟babel緩存
cacheDirectory: true,
// 不壓縮緩存文件(壓縮會影響速度,壓縮只會減少緩存占用的存儲空間)
cacheCompression: false,
},
},
}
]
}
]
},
plugins: [
// eslint
new ESLintPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
// 開啟緩存
cache: true,
// 緩存位置
cacheLocation: path.resolve(__dirname, "../node__modules/.cache/eslintcache")
})
]
}
4.2.5 多進(jìn)程—Thread
對JS處理的loader或plugin進(jìn)行多線程優(yōu)化
下面對:(1)babel(2)eslint(3)terser(4)cssMinimizer做了多進(jìn)程優(yōu)化
注意: 關(guān)于優(yōu)化的插件可以放到optimization屬性的配置中。例如代碼壓縮,代碼分割,tree shaking等等。
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const threads = 4
module.exports = {
// babel
module: {
oneOf: [
{
test: /\.js$/,
exclude: /node_modules|bower_components/,
use: [
{
// 安裝thread-loaders,開啟多進(jìn)程對babel做處理
loader: "thread-loader",
options: {
// 進(jìn)程數(shù)量
works: threads,
},
},
{
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
],
},
]
},
plugins: [
new ESLintPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/eslintcache"
),
// 開啟多進(jìn)程
threads,
}),
],
optimization: {
minimizer: [
// 壓縮css
new CssMinimizerPlugin(),
// 壓縮js
new TerserWebpackPlugin({
// 開啟多進(jìn)程對JavaScript壓縮優(yōu)化
parallel: threads,
}),
]
}
}
4.3 減少代碼體積
4.3.1 移除未使用代碼—Tree Shaking
tree shaking是webpack的默認(rèn)行為。如果一個模塊導(dǎo)出了若干內(nèi)容,但是其中有某些內(nèi)容沒有被其余模塊使用,那么不會被打包最后的文件。
舉例: 假設(shè)main.js是項(xiàng)目的入口文件,在其中引用了add函數(shù),但是從main開始打包,沒有任何其余文件引用util中的mul函數(shù),那么最后只會有add函數(shù)被打進(jìn)包。
util.js
export const add = (a, b) => a + b
export const mul = (a, b) => a * b
main.js
import { add } from './util'
console.log(add(1, 2))
4.3.2 禁止引入的方法直接注入—babel
普通babel導(dǎo)致的問題: 正常情況下,假設(shè)項(xiàng)目中多個文件引入了lodash的debounce做防抖,按之前的打包配置,babel會把debounce方法實(shí)現(xiàn)的代碼直接注入到每個引用它的模塊中,這導(dǎo)致了debounce的代碼重復(fù)出現(xiàn)了很多次。解決方案: 安裝插件@babel/plugin-transform-runtime在babel-loader中使用,可以把注入的行為轉(zhuǎn)為引用,實(shí)際上公共代碼就出現(xiàn)一次。注意(代碼復(fù)用針對對象問題):這個babel插件只是針對輔助函數(shù),例如Object.assign,Promise等等。并不能處理我們自定義的函數(shù)或組件,復(fù)用這些自定義代碼塊需要通過配置splitchunk實(shí)現(xiàn)。
module.exports = {
module: {
oneOf: [
// babel
{
test: /\.js$/,
exclude: /node_modules|bower_components/,
use: [
{
loader: "thread-loader",
options: {
works: threads,
},
},
{
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
// 減少代碼體積
plugins: ["@babel/plugin-transform-runtime"],
},
},
],
}
]
}
}
4.3.3 壓縮圖片—Image Minimizer
可以配置Image Minimizer在打包時對圖片進(jìn)行壓縮。這個優(yōu)化操作配置比較麻煩。需要安裝該插件,image-minimizer-webpack-plugin,可以在webpack官網(wǎng)上查看相關(guān)配置。
4.4 優(yōu)化代碼運(yùn)行性能
4.4.1 代碼分割—Code Split
Code Split定義:
回顧一下上文提到的Chunk的概念,而Code Split主要用于控制生成Chunk,那么Code Split主要應(yīng)用于
多入口: 打包設(shè)置多個入口分割插件: 根據(jù)Code Split配置分割代碼生成新Chunk按需加載: 遇到按需加載直接分割代碼生成新Chunk。注(產(chǎn)生過多小chunk問題):大型項(xiàng)目中路由很多,動態(tài)加載的代碼很多,可能導(dǎo)致分割出很多小chunk,這通過splitchunk插件無法控制,需要使用webpack內(nèi)置的limitchunk插件控制數(shù)量
Code Split通信:
背景:
瀏覽器不能直接支持AMD,CMD等模塊系統(tǒng)。假如代碼分割導(dǎo)致打包后產(chǎn)生了A和B兩個js文件,并且A文件需要引用B文件的某些代碼塊,這會怎么實(shí)現(xiàn)?
原理:
查看打包后文件(例如要求輸出UMD格式)發(fā)現(xiàn)每個文件都是一個封閉的作用域,無法直接通信。那么A要引用B中代碼時,先請求B的資源,請求完后放入頁面執(zhí)行B的腳本,B會把自己導(dǎo)出的內(nèi)容掛載到全局對象上。不同模塊引用代碼塊通過全局對象進(jìn)行通信。
Code Split默認(rèn)配置:
需要注意,在生產(chǎn)模式和開發(fā)模式下默認(rèn)值會有些出入
字段取值默認(rèn)值含義chunks‘a(chǎn)sync’|‘a(chǎn)ll’| ‘initial’async代碼分割應(yīng)用的場景: 表示代碼分割默認(rèn)應(yīng)用場景。默認(rèn)為async,只對按需加載生效。all表示對所有模塊生效。initial表示對入口和按需加載生效。minSizenumber20000分割時,chunk的最小大?。?表示當(dāng)前chunk超過該大小后會自動分割出新chunk。單位是byte,默認(rèn)在20kb左右。minRemainingSizenumber0分割后,chunk的最小大?。?表示分割后不能生成過小的chunk。默認(rèn)為0,表示分割后的chunk大小不做限制。minChunksnumber1分割后,chunk的最小引用次數(shù): 表示分割后的chunk被其它c(diǎn)hunk引用的最小次數(shù)。默認(rèn)是1,該條件默認(rèn)生效,表示默認(rèn)可分割。注(無法復(fù)用問題):如果SPA應(yīng)用只打出一個chunk,chunk中有很多復(fù)用代碼塊,如果設(shè)置minChunks大于1不會觸發(fā)公共代碼塊分割maxAsyncRequestsnumber30分割后,產(chǎn)生的chunk的最大數(shù)量: 指的是按需加載文件生成的chunk過大,需要進(jìn)一步拆分成多個chunk,限制進(jìn)一步拆分chunk的數(shù)量。maxInitialRequestsnumber30分割后,產(chǎn)生的chunk的最大數(shù)量: 指的是入口文件生成的chunk過大,需要進(jìn)一步拆分成多個chunk。顯然這對異步加載模塊無效,因?yàn)楫惒郊虞d模塊已經(jīng)分到另一個chunk里面了,使用的是maxAsyncRequests配置。enforceSizeThresholdnumber50000cacheGroups配置哪些代碼可以打包到一個組里
module.exports = {
optimization: {
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
}
}
4.4.2 提取公共模塊—代碼分割—Code Split
回顧一下上文提到的Chunk的概念,Code Split主要用于控制生成Chunk,那么Code Split主要方式是
多入口: 打包設(shè)置多個入口分割插件: 根據(jù)Code Split配置分割代碼生成新Chunk按需加載: 遇到按需加載直接分割代碼生成新Chunk。
除此之外Code Split可以用于指定哪些代碼需要打包到一個Chunk中,可以借助這個能力來復(fù)用代碼塊(4.3.2中提到)。
module.exports = {
splitChunks: {
cacheGroups: {
// node_modules中代碼都打包到vendors塊中
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'all',
name: 'vendors'
},
// common中代碼都打包到common塊中
common: {
test: /[\\/]packages\/common[\\/]/,
priority: -10,
chunks: 'all',
name: 'common'
},
// sfa中代碼都打包到sfa塊中
sfa: {
test: /[\\/]packages\/sfa[\\/]/,
priority: -10,
chunks: 'all',
name: 'sfa'
},
// 其它代碼打包規(guī)則
// reuseExistingChunk: true 表示開啟打包時代碼復(fù)用
default: {
// minChunks為1表示SPA中只要存在代碼復(fù)用就可以分割出去
minChunks: 1,
priority: -20,
reuseExistingChunk: true
}
}
},
},
}
4.4.3 緩存優(yōu)化—runtimeChunk
背景:
在代碼分割后,我們某次迭代可能只修改部分文件,這樣導(dǎo)致打包后的對應(yīng)chunk發(fā)生變化,并且文件hash值變化。但是實(shí)際不是這樣,這會導(dǎo)致入口chunk的緩存失效。
運(yùn)行時:
導(dǎo)致這個問題的原因是每個入口chunk中都會保存webpack運(yùn)行時的配置,比如模塊加載邏輯。如果代碼觸發(fā)了Code Split,那么這部分代碼將描述每個模塊對應(yīng)的hash值文件名。如果其中某個模塊的hash值改變就會導(dǎo)致這部分代碼刷新,從而導(dǎo)致入口chunk被修改,最后導(dǎo)致緩存無法復(fù)用。
解決方案:
配置runtimeChunk,指定從入口chunk提取出運(yùn)行時文件。
module.exports = {
optimization: {
runtimeChunk: {
name: (entryPoint) => `runtime~${entryPoint.name}.js`
}
}
}
代碼中有app和main兩個打包入口,配置runtimeChunk后生成了兩個runtime文件。這樣修改52,743,847這幾個chunk時就不會導(dǎo)致main或app的chunk緩存失效了。
優(yōu)點(diǎn):
多入口: 多入口時可以設(shè)置值runtimeChunk為single,把所有運(yùn)行時代碼提取到一個runtime文件中。如果多個入口中的依賴圖中存在相同節(jié)點(diǎn),那么將會復(fù)用運(yùn)行時配置,不需要在每個入口chunk中指明依賴圖中的chunk名稱,減少代碼量。緩存值: 不論是多入口還是單入口,抽取運(yùn)行時配置都可以避免修改chunk時使入口chunk的緩存失效。
柚子快報激活碼778899分享:前端 Webpack學(xué)習(xí)記錄
推薦鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。