Skip to content

HTMLRewriter vous permet d'utiliser des sélecteurs CSS pour transformer des documents HTML. Il fonctionne avec Request, Response, ainsi que string. L'implémentation de Bun est basée sur lol-html de Cloudflare.


Utilisation

Un cas d'utilisation courant est la réécriture d'URL dans du contenu HTML. Voici un exemple qui réécrit les sources d'images et les URL de liens pour utiliser un domaine CDN :

ts
// Remplacer toutes les images par un rickroll
const rewriter = new HTMLRewriter().on("img", {
  element(img) {
    // Miniature de la célèbre vidéo de rickroll
    img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");

    // Envelopper l'image dans un lien vers la vidéo
    img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
      html: true,
    });
    img.after("</a>", { html: true });

    // Ajouter un texte alternatif amusant
    img.setAttribute("alt", "Definitely not a rickroll");
  },
});

// Un document HTML exemple
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);

Cela remplace toutes les images par une miniature de Rick Astley et enveloppe chaque <img> dans un lien, produisant un diff comme ceci :

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>

Maintenant, chaque image de la page sera remplacée par une miniature de Rick Astley, et cliquer sur n'importe quelle image mènera à une vidéo très célèbre.

Types d'entrée

HTMLRewriter peut transformer du HTML provenant de diverses sources. L'entrée est automatiquement gérée en fonction de son type :

ts
// Depuis Response
rewriter.transform(new Response("<div>content</div>"));

// Depuis string
rewriter.transform("<div>content</div>");

// Depuis ArrayBuffer
rewriter.transform(new TextEncoder().encode("<div>content</div>").buffer);

// Depuis Blob
rewriter.transform(new Blob(["<div>content</div>"]));

// Depuis File
rewriter.transform(Bun.file("index.html"));

Notez que l'implémentation HTMLRewriter de Cloudflare Workers ne prend en charge que les objets Response.

Gestionnaires d'éléments

La méthode on(selector, handlers) vous permet d'enregistrer des gestionnaires pour les éléments HTML qui correspondent à un sélecteur CSS. Les gestionnaires sont appelés pour chaque élément correspondant pendant l'analyse :

ts
rewriter.on("div.content", {
  // Gérer les éléments
  element(element) {
    element.setAttribute("class", "new-content");
    element.append("<p>Nouveau contenu</p>", { html: true });
  },
  // Gérer les nœuds texte
  text(text) {
    text.replace("nouveau texte");
  },
  // Gérer les commentaires
  comments(comment) {
    comment.remove();
  },
});

Les gestionnaires peuvent être asynchrones et retourner une Promise. Notez que les opérations asynchrones bloqueront la transformation jusqu'à ce qu'elles soient terminées :

ts
rewriter.on("div", {
  async element(element) {
    await Bun.sleep(1000);
    element.setInnerContent("<span>remplacer</span>", { html: true });
  },
});

Prise en charge des sélecteurs CSS

La méthode on() prend en charge un large éventail de sélecteurs CSS :

ts
// Sélecteurs de balises
rewriter.on("p", handler);

// Sélecteurs de classe
rewriter.on("p.red", handler);

// Sélecteurs d'ID
rewriter.on("h1#header", handler);

// Sélecteurs d'attributs
rewriter.on("p[data-test]", handler); // A l'attribut
rewriter.on('p[data-test="one"]', handler); // Correspondance exacte
rewriter.on('p[data-test="one" i]', handler); // Insensible à la casse
rewriter.on('p[data-test="one" s]', handler); // Sensible à la casse
rewriter.on('p[data-test~="two"]', handler); // Correspondance de mot
rewriter.on('p[data-test^="a"]', handler); // Commence par
rewriter.on('p[data-test$="1"]', handler); // Se termine par
rewriter.on('p[data-test*="b"]', handler); // Contient
rewriter.on('p[data-test|="a"]', handler); // Séparé par tiret

// Combinateurs
rewriter.on("div span", handler); // Descendant
rewriter.on("div > span", handler); // Enfant direct

// Pseudo-classes
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);

// Sélecteur universel
rewriter.on("*", handler);

Opérations sur les éléments

Les éléments fournissent diverses méthodes de manipulation. Toutes les méthodes de modification retournent l'instance d'élément pour le chaînage :

ts
rewriter.on("div", {
  element(el) {
    // Attributs
    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");

    // Manipulation de contenu
    el.setInnerContent("Nouveau contenu"); // Échappe le HTML par défaut
    el.setInnerContent("<p>Contenu HTML</p>", { html: true }); // Analyse le HTML
    el.setInnerContent(""); // Effacer le contenu

    // Manipulation de position
    el.before("Contenu avant").after("Contenu après").prepend("Premier enfant").append("Dernier enfant");

    // Insertion de contenu HTML
    el.before("<span>avant</span>", { html: true })
      .after("<span>après</span>", { html: true })
      .prepend("<span>premier</span>", { html: true })
      .append("<span>dernier</span>", { html: true });

    // Suppression
    el.remove(); // Supprimer l'élément et son contenu
    el.removeAndKeepContent(); // Supprimer uniquement les balises de l'élément

    // Propriétés
    console.log(el.tagName); // Nom de balise en minuscules
    console.log(el.namespaceURI); // URI d'espace de noms de l'élément
    console.log(el.selfClosing); // Si l'élément est auto-fermant (ex. <div />)
    console.log(el.canHaveContent); // Si l'élément peut contenir du contenu (faux pour les éléments vides comme <br>)
    console.log(el.removed); // Si l'élément a été supprimé

    // Itération des attributs
    for (const [name, value] of el.attributes) {
      console.log(name, value);
    }

    // Gestion des balises de fin
    el.onEndTag(endTag => {
      endTag.before("Avant la balise de fin");
      endTag.after("Après la balise de fin");
      endTag.remove(); // Supprimer la balise de fin
      console.log(endTag.name); // Nom de balise en minuscules
    });
  },
});

Opérations sur le texte

Les gestionnaires de texte fournissent des méthodes de manipulation de texte. Les chunks de texte représentent des portions de contenu texte et fournissent des informations sur leur position dans le nœud texte :

ts
rewriter.on("p", {
  text(text) {
    // Contenu
    console.log(text.text); // Contenu texte
    console.log(text.lastInTextNode); // Si c'est le dernier chunk
    console.log(text.removed); // Si le texte a été supprimé

    // Manipulation
    text.before("Texte avant").after("Texte après").replace("Nouveau texte").remove();

    // Insertion de contenu HTML
    text
      .before("<span>avant</span>", { html: true })
      .after("<span>après</span>", { html: true })
      .replace("<span>remplacer</span>", { html: true });
  },
});

Opérations sur les commentaires

Les gestionnaires de commentaires permettent la manipulation de commentaires avec des méthodes similaires aux nœuds texte :

ts
rewriter.on("*", {
  comments(comment) {
    // Contenu
    console.log(comment.text); // Texte du commentaire
    comment.text = "Nouveau texte de commentaire"; // Définir le texte du commentaire
    console.log(comment.removed); // Si le commentaire a été supprimé

    // Manipulation
    comment.before("Avant le commentaire").after("Après le commentaire").replace("Nouveau commentaire").remove();

    // Insertion de contenu HTML
    comment
      .before("<span>avant</span>", { html: true })
      .after("<span>après</span>", { html: true })
      .replace("<span>remplacer</span>", { html: true });
  },
});

Gestionnaires de document

La méthode onDocument(handlers) vous permet de gérer des événements au niveau du document. Ces gestionnaires sont appelés pour des événements qui se produisent au niveau du document plutôt qu'à l'intérieur d'éléments spécifiques :

ts
rewriter.onDocument({
  // Gérer le doctype
  doctype(doctype) {
    console.log(doctype.name); // "html"
    console.log(doctype.publicId); // identifiant public si présent
    console.log(doctype.systemId); // identifiant système si présent
  },
  // Gérer les nœuds texte
  text(text) {
    console.log(text.text);
  },
  // Gérer les commentaires
  comments(comment) {
    console.log(comment.text);
  },
  // Gérer la fin du document
  end(end) {
    end.append("<!-- Pied de page -->", { html: true });
  },
});

Gestion des Response

Lors de la transformation d'une Response :

  • Le code de statut, les en-têtes et autres propriétés de réponse sont conservés
  • Le corps est transformé tout en conservant les capacités de streaming
  • Le codage de contenu (comme gzip) est géré automatiquement
  • Le corps de réponse original est marqué comme utilisé après la transformation
  • Les en-têtes sont clonés vers la nouvelle réponse

Gestion des erreurs

Les opérations HTMLRewriter peuvent lancer des erreurs dans plusieurs cas :

  • Syntaxe de sélecteur invalide dans la méthode on()
  • Contenu HTML invalide dans les méthodes de transformation
  • Erreurs de flux lors du traitement des corps de Response
  • Échecs d'allocation mémoire
  • Types d'entrée invalides (ex. passer Symbol)
  • Erreurs de corps déjà utilisé

Les erreurs doivent être attrapées et gérées de manière appropriée :

ts
try {
  const result = rewriter.transform(input);
  // Traiter le résultat
} catch (error) {
  console.error("Erreur HTMLRewriter :", error);
}

Voir aussi

Vous pouvez également lire la documentation Cloudflare, avec laquelle cette API est conçue pour être compatible.

Bun édité par www.bunjs.com.cn