HTMLRewriter permite que você use seletores CSS para transformar documentos HTML. Funciona com Request, Response, bem como string. A implementação do Bun é baseada no lol-html da Cloudflare.
Uso
Um caso de uso comum é reescrever URLs em conteúdo HTML. Aqui está um exemplo que reescreve fontes de imagens e URLs de links para usar um domínio CDN:
// Substitui todas as imagens por um rickroll
const rewriter = new HTMLRewriter().on("img", {
element(img) {
// Famosa thumbnail do vídeo rickroll
img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");
// Envolve a imagem em um link para o vídeo
img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
html: true,
});
img.after("</a>", { html: true });
// Adiciona um texto alt divertido
img.setAttribute("alt", "Definitely not a rickroll");
},
});
// Um documento HTML de exemplo
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);Isso substitui todas as imagens por uma thumbnail de Rick Astley e envolve cada <img> em um link, produzindo um diff como este:
<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>Agora todas as imagens na página serão substituídas por uma thumbnail de Rick Astley, e clicar em qualquer imagem levará a um vídeo muito famoso.
Tipos de entrada
HTMLRewriter pode transformar HTML de várias fontes. A entrada é automaticamente tratada com base em seu tipo:
// De Response
rewriter.transform(new Response("<div>content</div>"));
// De string
rewriter.transform("<div>content</div>");
// De ArrayBuffer
rewriter.transform(new TextEncoder().encode("<div>content</div>").buffer);
// De Blob
rewriter.transform(new Blob(["<div>content</div>"]));
// De File
rewriter.transform(Bun.file("index.html"));Note que a implementação do HTMLRewriter dos Cloudflare Workers suporta apenas objetos Response.
Handlers de Elemento
O método on(selector, handlers) permite que você registre handlers para elementos HTML que correspondem a um seletor CSS. Os handlers são chamados para cada elemento correspondente durante o parsing:
rewriter.on("div.content", {
// Handle elements
element(element) {
element.setAttribute("class", "new-content");
element.append("<p>New content</p>", { html: true });
},
// Handle text nodes
text(text) {
text.replace("new text");
},
// Handle comments
comments(comment) {
comment.remove();
},
});Os handlers podem ser assíncronos e retornar uma Promise. Note que operações assíncronas bloquearão a transformação até que completem:
rewriter.on("div", {
async element(element) {
await Bun.sleep(1000);
element.setInnerContent("<span>replace</span>", { html: true });
},
});Suporte a Seletores CSS
O método on() suporta uma ampla gama de seletores CSS:
// Seletores de tag
rewriter.on("p", handler);
// Seletores de classe
rewriter.on("p.red", handler);
// Seletores de ID
rewriter.on("h1#header", handler);
// Seletores de atributo
rewriter.on("p[data-test]", handler); // Tem atributo
rewriter.on('p[data-test="one"]', handler); // Correspondência exata
rewriter.on('p[data-test="one" i]', handler); // Case-insensitive
rewriter.on('p[data-test="one" s]', handler); // Case-sensitive
rewriter.on('p[data-test~="two"]', handler); // Word match
rewriter.on('p[data-test^="a"]', handler); // Começa com
rewriter.on('p[data-test$="1"]', handler); // Termina com
rewriter.on('p[data-test*="b"]', handler); // Contém
rewriter.on('p[data-test|="a"]', handler); // Dash-separated
// Combinadores
rewriter.on("div span", handler); // Descendente
rewriter.on("div > span", handler); // Filho direto
// 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);
// Seletor universal
rewriter.on("*", handler);Operações de Elemento
Elementos fornecem vários métodos para manipulação. Todos os métodos de modificação retornam a instância do elemento para encadeamento:
rewriter.on("div", {
element(el) {
// Atributos
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");
// Manipulação de conteúdo
el.setInnerContent("New content"); // Escapa HTML por padrão
el.setInnerContent("<p>HTML content</p>", { html: true }); // Analisa HTML
el.setInnerContent(""); // Limpa conteúdo
// Manipulação de posição
el.before("Content before").after("Content after").prepend("First child").append("Last child");
// Inserção de conteúdo 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 });
// Remoção
el.remove(); // Remove elemento e conteúdo
el.removeAndKeepContent(); // Remove apenas as tags do elemento
// Propriedades
console.log(el.tagName); // Nome da tag em minúsculas
console.log(el.namespaceURI); // URI do namespace do elemento
console.log(el.selfClosing); // Se o elemento é auto-fechável (e.g. <div />)
console.log(el.canHaveContent); // Se o elemento pode conter conteúdo (falso para elementos void como <br>)
console.log(el.removed); // Se o elemento foi removido
// Iteração de atributos
for (const [name, value] of el.attributes) {
console.log(name, value);
}
// Tratamento de tag de fechamento
el.onEndTag(endTag => {
endTag.before("Before end tag");
endTag.after("After end tag");
endTag.remove(); // Remove a tag de fechamento
console.log(endTag.name); // Nome da tag em minúsculas
});
},
});Operações de Texto
Handlers de texto fornecem métodos para manipulação de texto. Chunks de texto representam porções de conteúdo de texto e fornecem informações sobre sua posição no nó de texto:
rewriter.on("p", {
text(text) {
// Conteúdo
console.log(text.text); // Conteúdo do texto
console.log(text.lastInTextNode); // Se este é o último chunk
console.log(text.removed); // Se o texto foi removido
// Manipulação
text.before("Before text").after("After text").replace("New text").remove();
// Inserção de conteúdo HTML
text
.before("<span>before</span>", { html: true })
.after("<span>after</span>", { html: true })
.replace("<span>replace</span>", { html: true });
},
});Operações de Comentário
Handlers de comentário permitem manipulação de comentários com métodos similares aos nós de texto:
rewriter.on("*", {
comments(comment) {
// Conteúdo
console.log(comment.text); // Texto do comentário
comment.text = "New comment text"; // Define o texto do comentário
console.log(comment.removed); // Se o comentário foi removido
// Manipulação
comment.before("Before comment").after("After comment").replace("New comment").remove();
// Inserção de conteúdo HTML
comment
.before("<span>before</span>", { html: true })
.after("<span>after</span>", { html: true })
.replace("<span>replace</span>", { html: true });
},
});Handlers de Documento
O método onDocument(handlers) permite que você trate eventos de nível de documento. Estes handlers são chamados para eventos que ocorrem no nível do documento em vez de dentro de elementos específicos:
rewriter.onDocument({
// Handle doctype
doctype(doctype) {
console.log(doctype.name); // "html"
console.log(doctype.publicId); // identificador público se presente
console.log(doctype.systemId); // identificador de sistema se presente
},
// Handle text nodes
text(text) {
console.log(text.text);
},
// Handle comments
comments(comment) {
console.log(comment.text);
},
// Handle document end
end(end) {
end.append("<!-- Footer -->", { html: true });
},
});Tratamento de Response
Ao transformar um Response:
- O código de status, headers e outras propriedades da response são preservados
- O body é transformado mantendo as capacidades de streaming
- Content-encoding (como gzip) é tratado automaticamente
- O body da response original é marcado como usado após a transformação
- Headers são clonados para a nova response
Tratamento de Erros
Operações do HTMLRewriter podem lançar erros em vários casos:
- Sintaxe de seletor inválida no método
on() - Conteúdo HTML inválido em métodos de transformação
- Erros de stream ao processar bodies de Response
- Falhas de alocação de memória
- Tipos de entrada inválidos (e.g., passar Symbol)
- Erros de body já usado
Erros devem ser capturados e tratados apropriadamente:
try {
const result = rewriter.transform(input);
// Processa o resultado
} catch (error) {
console.error("Erro do HTMLRewriter:", error);
}Veja também
Você também pode ler a documentação da Cloudflare, com a qual esta API pretende ser compatível.