Skip to content

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:

ts
// 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
<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:

ts
// 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:

ts
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:

ts
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:

ts
// 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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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.

Bun von www.bunjs.com.cn bearbeitet