HTMLRewriter ermöglicht die Verwendung von CSS-Selektoren zur Transformation von HTML-Dokumenten. Es funktioniert mit Request, Response sowie string. Bun's Implementierung basiert auf Cloudflare's lol-html.
Verwendung
Ein häufiger Anwendungsfall ist das Umschreiben von URLs in HTML-Inhalten. Hier ist ein Beispiel, das Bildquellen und Link-URLs so umschreibt, dass sie eine CDN-Domain verwenden:
// Alle Bilder durch einen Rickroll ersetzen
const rewriter = new HTMLRewriter().on("img", {
element(img) {
// Berühmtes Rickroll-Video-Thumbnail
img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");
// Das Bild in einen Link zum Video einwickeln
img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
html: true,
});
img.after("</a>", { html: true });
// Einen lustigen Alt-Text hinzufügen
img.setAttribute("alt", "Definitely not a rickroll");
},
});
// Ein Beispiel-HTML-Dokument
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);Dies ersetzt alle Bilder durch ein Thumbnail von Rick Astley und wickelt jedes <img> in einen Link ein, was einen Diff wie diesen erzeugt:
<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>Jetzt wird jedes Bild auf der Seite durch ein Thumbnail von Rick Astley ersetzt, und ein Klick auf ein beliebiges Bild führt zu einem sehr berühmten Video.
Eingabetypen
HTMLRewriter kann HTML aus verschiedenen Quellen transformieren. Die Eingabe wird automatisch basierend auf ihrem Typ behandelt:
// Von Response
rewriter.transform(new Response("<div>content</div>"));
// Von String
rewriter.transform("<div>content</div>");
// Von ArrayBuffer
rewriter.transform(new TextEncoder().encode("<div>content</div>").buffer);
// Von Blob
rewriter.transform(new Blob(["<div>content</div>"]));
// Von File
rewriter.transform(Bun.file("index.html"));Beachten Sie, dass die Cloudflare Workers-Implementierung von HTMLRewriter nur Response-Objekte unterstützt.
Element-Handler
Die on(selector, handlers)-Methode ermöglicht es, Handler für HTML-Elemente zu registrieren, die mit einem CSS-Selektor übereinstimmen. Die Handler werden für jedes übereinstimmende Element während des Parsens aufgerufen:
rewriter.on("div.content", {
// Elemente verarbeiten
element(element) {
element.setAttribute("class", "new-content");
element.append("<p>New content</p>", { html: true });
},
// Textknoten verarbeiten
text(text) {
text.replace("new text");
},
// Kommentare verarbeiten
comments(comment) {
comment.remove();
},
});Die Handler können asynchron sein und ein Promise zurückgeben. Beachten Sie, dass asynchrone Operationen die Transformation blockieren, bis sie abgeschlossen sind:
rewriter.on("div", {
async element(element) {
await Bun.sleep(1000);
element.setInnerContent("<span>replace</span>", { html: true });
},
});CSS-Selektor-Unterstützung
Die on()-Methode unterstützt eine breite Palette von CSS-Selektoren:
// Tag-Selektoren
rewriter.on("p", handler);
// Klassen-Selektoren
rewriter.on("p.red", handler);
// ID-Selektoren
rewriter.on("h1#header", handler);
// Attribut-Selektoren
rewriter.on("p[data-test]", handler); // Hat Attribut
rewriter.on('p[data-test="one"]', handler); // Exakte Übereinstimmung
rewriter.on('p[data-test="one" i]', handler); // Groß-/Kleinschreibung ignorieren
rewriter.on('p[data-test="one" s]', handler); // Groß-/Kleinschreibung beachten
rewriter.on('p[data-test~="two"]', handler); // Wortübereinstimmung
rewriter.on('p[data-test^="a"]', handler); // Beginnt mit
rewriter.on('p[data-test$="1"]', handler); // Endet mit
rewriter.on('p[data-test*="b"]', handler); // Enthält
rewriter.on('p[data-test|="a"]', handler); // Durch Bindestrich getrennt
// Kombinatoren
rewriter.on("div span", handler); // Nachfahre
rewriter.on("div > span", handler); // Direktes Kind
// Pseudo-Klassen
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);
// Universeller Selektor
rewriter.on("*", handler);Element-Operationen
Elemente bieten verschiedene Methoden zur Manipulation. Alle Änderungsmethoden geben die Elementinstanz zur Verkettung zurück:
rewriter.on("div", {
element(el) {
// Attribute
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");
// Inhaltsmanipulation
el.setInnerContent("New content"); // Maskiert HTML standardmäßig
el.setInnerContent("<p>HTML content</p>", { html: true }); // Parst HTML
el.setInnerContent(""); // Inhalt löschen
// Positionsmanipulation
el.before("Content before").after("Content after").prepend("First child").append("Last child");
// HTML-Inhalt einfügen
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 });
// Entfernen
el.remove(); // Element und Inhalt entfernen
el.removeAndKeepContent(); // Nur die Element-Tags entfernen
// Eigenschaften
console.log(el.tagName); // Tag-Name in Kleinbuchstaben
console.log(el.namespaceURI); // Namespace-URI des Elements
console.log(el.selfClosing); // Ob das Element selbstschließend ist (z.B. <div />)
console.log(el.canHaveContent); // Ob das Element Inhalt enthalten kann (false für leere Elemente wie <br>)
console.log(el.removed); // Ob das Element entfernt wurde
// Attribute-Iteration
for (const [name, value] of el.attributes) {
console.log(name, value);
}
// End-Tag-Behandlung
el.onEndTag(endTag => {
endTag.before("Before end tag");
endTag.after("After end tag");
endTag.remove(); // Das End-Tag entfernen
console.log(endTag.name); // Tag-Name in Kleinbuchstaben
});
},
});Text-Operationen
Text-Handler bieten Methoden zur Textmanipulation. Text-Chunks repräsentieren Teile von Textinhalten und bieten Informationen über ihre Position im Textknoten:
rewriter.on("p", {
text(text) {
// Inhalt
console.log(text.text); // Textinhalt
console.log(text.lastInTextNode); // Ob dies der letzte Chunk ist
console.log(text.removed); // Ob Text entfernt wurde
// Manipulation
text.before("Before text").after("After text").replace("New text").remove();
// HTML-Inhalt einfügen
text
.before("<span>before</span>", { html: true })
.after("<span>after</span>", { html: true })
.replace("<span>replace</span>", { html: true });
},
});Kommentar-Operationen
Kommentar-Handler ermöglichen die Kommentarmanipulation mit ähnlichen Methoden wie Textknoten:
rewriter.on("*", {
comments(comment) {
// Inhalt
console.log(comment.text); // Kommentartext
comment.text = "New comment text"; // Kommentartext setzen
console.log(comment.removed); // Ob Kommentar entfernt wurde
// Manipulation
comment.before("Before comment").after("After comment").replace("New comment").remove();
// HTML-Inhalt einfügen
comment
.before("<span>before</span>", { html: true })
.after("<span>after</span>", { html: true })
.replace("<span>replace</span>", { html: true });
},
});Dokument-Handler
Die onDocument(handlers)-Methode ermöglicht die Behandlung von Dokument-Ereignissen. Diese Handler werden für Ereignisse aufgerufen, die auf Dokumentebene auftreten, anstatt innerhalb spezifischer Elemente:
rewriter.onDocument({
// Doctype verarbeiten
doctype(doctype) {
console.log(doctype.name); // "html"
console.log(doctype.publicId); // Öffentliche Kennung falls vorhanden
console.log(doctype.systemId); // Systemkennung falls vorhanden
},
// Textknoten verarbeiten
text(text) {
console.log(text.text);
},
// Kommentare verarbeiten
comments(comment) {
console.log(comment.text);
},
// Dokumentende verarbeiten
end(end) {
end.append("<!-- Footer -->", { html: true });
},
});Response-Behandlung
Bei der Transformation einer Response:
- Der Statuscode, Header und andere Response-Eigenschaften bleiben erhalten
- Der Body wird transformiert, wobei die Streaming-Fähigkeiten erhalten bleiben
- Content-Encoding (wie gzip) wird automatisch behandelt
- Der ursprüngliche Response-Body wird nach der Transformation als verwendet markiert
- Header werden in die neue Response kopiert
Fehlerbehandlung
HTMLRewriter-Operationen können in mehreren Fällen Fehler werfen:
- Ungültige Selektorsyntax in der
on()-Methode - Ungültiger HTML-Inhalt in Transformationsmethoden
- Stream-Fehler bei der Verarbeitung von Response-Bodys
- Speicherzuweisungsfehler
- Ungültige Eingabetypen (z.B. Symbol übergeben)
- Body bereits verwendet Fehler
Fehler sollten angemessen abgefangen und behandelt werden:
try {
const result = rewriter.transform(input);
// Ergebnis verarbeiten
} catch (error) {
console.error("HTMLRewriter-Fehler:", error);
}Siehe auch
Sie können auch die Cloudflare-Dokumentation lesen, mit der diese API kompatibel sein soll.