【手寫loader】- 在webpack打包階段檢測失效圖片資源並替換
ead>theme: smartblue highlight: a11y-dark
「這是我參與2022首次更文挑戰的第4天,活動詳情檢視:2022首次更文挑戰」
首發於我的公眾號「前端面壁者」,歡迎關注。
loader
本質上是匯出為函式的JavaScript
模組。loader runner
中包含實用的方法this.async()
可以使loader
呼叫方式變為非同步。
一、環境搭建
1. 專案初始化
或許你期望按照官方給出的指南來起步
新建目錄並初始化專案
js
mkdir webpack-loader
cd webpack-loader
npm init
安裝webpack
與cli
js
npm i -D webpack webpack-cli
安裝html-webpack-plugin
與clean-webpack-plugin
js
npm i -D html-webpack-plugin
npm i -D clean-webpack-plugin
- html-webpack-plugin 會將 webpack 打包出來的 js 檔案自動引入到 html 中
- 每次執行 npm run build 會發現 dist 資料夾裡會殘留上次打包的檔案,clean-webpack-plugin 在打包輸出前會清空資料夾
2. 專案配置(模擬使用場景)
新建一個資料夾 src,然後新建一個檔案 index.js,編寫一段程式碼模擬使用場景
js
const checkUrlList = [
"http://d-dev.zhgcloud.com/image/2021/6/2/60b75680287c1.png",
"http://d-dev.zhgcloud.com/image/2021/6/2/60b73e58b38d0.jpg",
"http://d-dev.zhgcloud.com/image/2021/6/2/60b75758164c6.png",
"http://d-dev.zhgcloud.com/image/2021/6/2/60b752cd01b1c.png",
"http://webpack.docschina.org/site-logo.1fcab817090e78435061.svg",
];
for (let i = 0; i < 5; i++) {
console.log(i);
var img = document.createElement("img");
img.setAttribute("src", checkUrlList[i]);
document.getElementById("body").appendChild(img);
}
- 前四張圖片地址是失效的,第五張可訪問
新建一個資料夾 public,然後新建一個檔案 index.html,用於在瀏覽器除錯
```html
- 給
body
設定一個id="body"
3. webpack 配置
新建一個 build 資料夾,裡面新建一個 webpack.config.js,進行如下配置
```js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); module.exports = { mode: "development", // 開發模式 entry: { index: path.resolve(__dirname, "../src/index.js"), }, // 入口檔案 output: { filename: "[name].[chunkhash:8].js", // 打包後的檔名稱 path: path.resolve(__dirname, "../dist"), // 打包後的目錄 },
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
filename: "index.html",
chunks: ["index"], // 與入口檔案對應的模組名
}),
],
}; ```
在package.json
新增打包命令
Tips:需要注意的是webpack
是執行在node
環境中的,謹記這一點下面會用到
4. 使用 loader 前打包
執行打包命令
在瀏覽器檢視未使用 loader
打包的 index.js
- 頁面按序載入了四張失效圖和一張有效圖
二、手寫 loader
1. 自定義 replace-legal-img
新建一個 lib
資料夾,裡面新建一個 replace-legal-img.js
,編寫如下程式碼
js
const https = require("https"); // 載入待檢測的 圖片`url`,根據其返回值決定是否替換成合法圖片`url`
const parser = require("@babel/parser"); // 會將原始碼解析成 `AST`
const traverse = require("@babel/traverse").default; // 對 `AST` 節點進行遞迴遍歷,生成一個便於操作、轉換的 `path` 物件
const generator = require("@babel/generator").default; // 將 `AST` 解碼生成 `js` 程式碼
module.exports = function (source) {
const ast = parser.parse(source, { sourceType: "module" });
var callback = this.async();
let count = 0;
let promiseAll = [];
traverse(ast, {
enter(path) {
if (
path.node.type === "StringLiteral" &&
/^http[s]{0,1}:\/\/([\w.]+\/?)\S*[png|jpg|image|svg]$/.test(
path.node.value
)
) {
promiseAll[`${count}`] = new Promise((resolve, reject) => {
https.get(path.node.value, function (res) {
if (res.statusCode === 404) {
path.node.value =
"http://p26-passport.byteacctimg.com/img/mosaic-legacy/3793/3114521287~300x300.image";
resolve(path.node.value);
} else if (res.statusCode === 200) {
resolve(path.node.value);
}
});
});
count++;
}
},
});
Promise.all(promiseAll)
.then((result) => {
output = generator(ast, {}, source);
callback(null, output.code);
})
.catch((error) => {
console.log(error);
});
};
- 由於
webpack
基於node
,所以使用https
發起網路請求 - 圖片
url
是String
型別的字面量即StringLiteral
,配合正則表示式可以排出非圖片url
型別的StringLiteral
- 由於網路請求是非同步的,需要配合
this.async()
告知loader runner
等待非同步結果返回 source
若包含多個圖片URL
會發起多個網路請求,這時需要使用promiseAll
組織所有promise
的返回,使用count
計數- 當
source
中所有圖片URL
的請求全部執行完畢時Promise.all(promiseAll)
給出返回值,這裡再使用generator
將檢測替換新value
後的AST
轉換成js
,並使用callback
告知loader runner
執行完畢
2. webpack配置自定義loader
在 webpack.config.js 中引入這個 loader
```js module.exports = { ...
module: {
rules: [
{
test: /\.js$/,
use: path.resolve(__dirname, "../lib/replace-legal-img.js"),
},
],
},
...
}; ```
3. 效果展示
在瀏覽器檢視未使用 loader
打包的 index.js
- 頁面按序載入了四張被替換的圖片和一張有效圖
三、總結
注意到掘金和CSDN等平臺有許多手寫loader的文,蒐羅下來感覺值得借鑑的並不多,大多都是一些很簡單的示例,真正有生產意義的很少。所以對於新手來說,如何寫一個有效的loader,或者說實現有效loader的難點吧,個人感覺難點有兩個,一是邁出第一步,二是知識儲備。
我自己的實踐方式是多看多寫,看一是看文件,二是看成熟專案的原始碼和優秀的blog,寫就儘可能的放到一個具體的場景裡,這樣寫出來的東西多少優點意義。比如在上面的loader中只是實現了最簡單的圖片資源檢測,且同意替換成固定的資源連結。如果source裡有來自多個圖床地址,替換的資源支援自定義,都可以在這個基礎上擴充套件。
四、參考
1. 2020 年了,再不會 webpack 敲得程式碼就不香了(近萬字實戰)