HTMLRewriter 讓你可以使用 CSS 選擇器轉換 HTML 文檔。它適用於 Request、Response 以及 string。Bun 的實現基於 Cloudflare 的 lol-html。
用法
一個常見的用例是重寫 HTML 內容中的 URL。以下示例將圖像源和鏈接 URL 重寫為使用 CDN 域名:
// 將所有圖像替換為 rickroll
const rewriter = new HTMLRewriter().on("img", {
element(img) {
// 著名的 rickroll 視頻縮略圖
img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");
// 將圖像包裝在指向視頻的鏈接中
img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
html: true,
});
img.after("</a>", { html: true });
// 添加一些有趣的替代文本
img.setAttribute("alt", "Definitely not a rickroll");
},
});
// 示例 HTML 文檔
const html = `
<html>
<body>
<img src="/cat.jpg">
<img src="dog.png">
<img src="https://example.com/bird.webp">
</body>
</html>
`;
const result = rewriter.transform(html);
console.log(result);這將把所有圖像替換為 Rick Astley 的縮略圖,並將每個 <img> 包裝在鏈接中,產生如下差異:
<html>
<body>
<img src="/cat.jpg" />
<img src="dog.png" />
<img src="https://example.com/bird.webp" />
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" />
</a>
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" />
</a>
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" />
</a>
</body>
</html>現在頁面上的每個圖像都將被替換為 Rick Astley 的縮略圖,點擊任何圖像都會跳轉到 一個非常著名的視頻。
輸入類型
HTMLRewriter 可以轉換來自各種來源的 HTML。輸入根據其類型自動處理:
// 來自 Response
rewriter.transform(new Response("<div>content</div>"));
// 來自字符串
rewriter.transform("<div>content</div>");
// 來自 ArrayBuffer
rewriter.transform(new TextEncoder().encode("<div>content</div>").buffer);
// 來自 Blob
rewriter.transform(new Blob(["<div>content</div>"]));
// 來自 File
rewriter.transform(Bun.file("index.html"));請注意,Cloudflare Workers 的 HTMLRewriter 實現僅支持 Response 對象。
元素處理器
on(selector, handlers) 方法允許你為匹配 CSS 選擇器的 HTML 元素注冊處理器。在解析期間,每個匹配的元素都會調用這些處理器:
rewriter.on("div.content", {
// 處理元素
element(element) {
element.setAttribute("class", "new-content");
element.append("<p>New content</p>", { html: true });
},
// 處理文本節點
text(text) {
text.replace("new text");
},
// 處理注釋
comments(comment) {
comment.remove();
},
});處理器可以是異步的並返回 Promise。請注意,異步操作將阻塞轉換直到完成:
rewriter.on("div", {
async element(element) {
await Bun.sleep(1000);
element.setInnerContent("<span>replace</span>", { html: true });
},
});CSS 選擇器支持
on() 方法支持廣泛的 CSS 選擇器:
// 標簽選擇器
rewriter.on("p", handler);
// 類選擇器
rewriter.on("p.red", handler);
// ID 選擇器
rewriter.on("h1#header", handler);
// 屬性選擇器
rewriter.on("p[data-test]", handler); // 具有屬性
rewriter.on('p[data-test="one"]', handler); // 精確匹配
rewriter.on('p[data-test="one" i]', handler); // 不區分大小寫
rewriter.on('p[data-test="one" s]', handler); // 區分大小寫
rewriter.on('p[data-test~="two"]', handler); // 單詞匹配
rewriter.on('p[data-test^="a"]', handler); // 以...開頭
rewriter.on('p[data-test$="1"]', handler); // 以...結尾
rewriter.on('p[data-test*="b"]', handler); // 包含
rewriter.on('p[data-test|="a"]', handler); // 短橫線分隔
// 組合器
rewriter.on("div span", handler); // 後代
rewriter.on("div > span", handler); // 直接子級
// 偽類
rewriter.on("p:nth-child(2)", handler);
rewriter.on("p:first-child", handler);
rewriter.on("p:nth-of-type(2)", handler);
rewriter.on("p:first-of-type", handler);
rewriter.on("p:not(:first-child)", handler);
// 通用選擇器
rewriter.on("*", handler);元素操作
元素提供多種操作方法。所有修改方法都返回元素實例以便鏈式調用:
rewriter.on("div", {
element(el) {
// 屬性
el.setAttribute("class", "new-class").setAttribute("data-id", "123");
const classAttr = el.getAttribute("class"); // "new-class"
const hasId = el.hasAttribute("id"); // boolean
el.removeAttribute("class");
// 內容操作
el.setInnerContent("New content"); // 默認轉義 HTML
el.setInnerContent("<p>HTML content</p>", { html: true }); // 解析 HTML
el.setInnerContent(""); // 清除內容
// 位置操作
el.before("Content before").after("Content after").prepend("First child").append("Last child");
// HTML 內容插入
el.before("<span>before</span>", { html: true })
.after("<span>after</span>", { html: true })
.prepend("<span>first</span>", { html: true })
.append("<span>last</span>", { html: true });
// 刪除
el.remove(); // 刪除元素及其內容
el.removeAndKeepContent(); // 僅刪除元素標簽
// 屬性
console.log(el.tagName); // 小寫標簽名
console.log(el.namespaceURI); // 元素的命名空間 URI
console.log(el.selfClosing); // 元素是否自關閉(例如 <div />)
console.log(el.canHaveContent); // 元素是否可以包含內容(對於 <br> 等空元素為 false)
console.log(el.removed); // 元素是否被刪除
// 屬性迭代
for (const [name, value] of el.attributes) {
console.log(name, value);
}
// 結束標簽處理
el.onEndTag(endTag => {
endTag.before("Before end tag");
endTag.after("After end tag");
endTag.remove(); // 刪除結束標簽
console.log(endTag.name); // 小寫標簽名
});
},
});文本操作
文本處理器提供文本操作方法。文本塊表示文本內容的部分,並提供有關其在文本節點中位置的信息:
rewriter.on("p", {
text(text) {
// 內容
console.log(text.text); // 文本內容
console.log(text.lastInTextNode); // 是否是文本節點的最後一塊
console.log(text.removed); // 文本是否被刪除
// 操作
text.before("Before text").after("After text").replace("New text").remove();
// HTML 內容插入
text
.before("<span>before</span>", { html: true })
.after("<span>after</span>", { html: true })
.replace("<span>replace</span>", { html: true });
},
});注釋操作
注釋處理器允許注釋操作,方法與文本節點類似:
rewriter.on("*", {
comments(comment) {
// 內容
console.log(comment.text); // 注釋文本
comment.text = "New comment text"; // 設置注釋文本
console.log(comment.removed); // 注釋是否被刪除
// 操作
comment.before("Before comment").after("After comment").replace("New comment").remove();
// HTML 內容插入
comment
.before("<span>before</span>", { html: true })
.after("<span>after</span>", { html: true })
.replace("<span>replace</span>", { html: true });
},
});文檔處理器
onDocument(handlers) 方法允許你處理文檔級事件。這些處理器在文檔級別發生的事件上調用,而不是在特定元素內:
rewriter.onDocument({
// 處理 doctype
doctype(doctype) {
console.log(doctype.name); // "html"
console.log(doctype.publicId); // 如果存在則為公共標識符
console.log(doctype.systemId); // 如果存在則為系統標識符
},
// 處理文本節點
text(text) {
console.log(text.text);
},
// 處理注釋
comments(comment) {
console.log(comment.text);
},
// 處理文檔結束
end(end) {
end.append("<!-- Footer -->", { html: true });
},
});Response 處理
轉換 Response 時:
- 狀態碼、頭信息和其他響應屬性會被保留
- 主體會被轉換,同時保持流式傳輸功能
- 內容編碼(如 gzip)會自動處理
- 原始響應主體在轉換後會被標記為已使用
- 頭信息會被克隆到新響應中
錯誤處理
HTMLRewriter 操作在以下幾種情況下可能會拋出錯誤:
on()方法中的選擇器語法無效- 轉換方法中的 HTML 內容無效
- 處理 Response 主體時的流錯誤
- 內存分配失敗
- 無效的輸入類型(例如傳遞 Symbol)
- 主體已使用的錯誤
應捕獲並適當處理錯誤:
try {
const result = rewriter.transform(input);
// 處理結果
} catch (error) {
console.error("HTMLRewriter error:", error);
}另請參閱
你還可以閱讀 Cloudflare 文檔,此 API 旨在與其兼容。