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