コンテンツにスキップ

HTMLRewriter

背景

HTMLRewriterクラスは、開発者がCloudflare Workersアプリケーション内で包括的で表現力豊かなHTMLパーサーを構築できるようにします。これは、Workersアプリケーション内で直接jQueryのような体験を提供するものと考えることができます。強力なJavaScript APIを利用してHTMLを解析し変換することで、HTMLRewriterは開発者が深く機能的なアプリケーションを構築できるようにします。

HTMLRewriterクラスは、Workersスクリプト内で一度インスタンス化され、onおよびonDocument関数を使用して複数のハンドラーが接続されるべきです。


コンストラクタ

new HTMLRewriter().on('*', new ElementHandler()).onDocument(new DocumentHandler());

グローバルタイプ

HTMLRewriter API全体で、多くのプロパティとメソッドが使用するいくつかの一貫したタイプがあります:

  • Content 文字列

    • 出力ストリームに挿入されるコンテンツは文字列である必要があります。
  • ContentOptions オブジェクト

    • { html: Boolean } 挿入されたコンテンツに対するHTMLRewriterの扱い方を制御します。htmlブール値がtrueに設定されている場合、コンテンツは生のHTMLとして扱われます。htmlブール値がfalseに設定されているか、提供されていない場合、コンテンツはテキストとして扱われ、適切なHTMLエスケープが適用されます。

ハンドラー

HTMLRewriterで使用できる2つのハンドラータイプがあります:要素ハンドラーとドキュメントハンドラー。

要素ハンドラー

要素ハンドラーは、HTMLRewriterインスタンスの.on関数を使用して接続されたときに、受信した要素に応答します。要素ハンドラーは、elementcomments、およびtextに応答する必要があります。以下の例は、ElementHandlerクラスを使用してdiv要素を処理します。

class ElementHandler {
element(element) {
// 受信した要素、例えば`div`
console.log(`Incoming element: ${element.tagName}`);
}
comments(comment) {
// 受信したコメント
}
text(text) {
// 受信したテキストの一部
}
}
async function handleRequest(req) {
const res = await fetch(req);
return new HTMLRewriter().on('div', new ElementHandler()).transform(res);
}

ドキュメントハンドラー

ドキュメントハンドラーは、受信したHTMLドキュメントを表します。ドキュメントハンドラー上で、ドキュメントのdoctypecommentstext、およびendをクエリおよび操作するためのいくつかの関数を定義できます。要素ハンドラーとは異なり、ドキュメントハンドラーのdoctypecommentstext、およびend関数は特定のセレクターによってスコープされません。ドキュメントハンドラーの関数は、ページ上のすべてのコンテンツ、つまり最上位のHTMLタグの外側のコンテンツを含むすべてのコンテンツに対して呼び出されます:

class DocumentHandler {
doctype(doctype) {
// 受信したdoctype、例えば<!DOCTYPE html>
}
comments(comment) {
// 受信したコメント
}
text(text) {
// 受信したテキストの一部
}
end(end) {
// ドキュメントの終わり
}
}

非同期ハンドラー

要素ハンドラーとドキュメントハンドラーの両方で定義されたすべての関数は、voidまたはPromise<void>のいずれかを返すことができます。ハンドラー関数をasyncにすることで、fetch、Workers KV、Durable Objects、またはキャッシュを介して外部リソースにアクセスできます。

class UserElementHandler {
async element(element) {
let response = await fetch(new Request('/user'));
// レスポンスを使用してユーザー情報を埋め込む
}
}
async function handleRequest(req) {
const res = await fetch(req);
// IDが`user_info`のdivに対してHTMLRewriterを介してユーザー要素ハンドラーを実行
return new HTMLRewriter().on('div#user_info', new UserElementHandler()).transform(res);
}

要素

element引数は、要素ハンドラーでのみ使用され、DOM要素の表現です。要素をクエリおよび操作するためのいくつかのメソッドがあります:

プロパティ

  • tagName 文字列

    • タグの名前、例えば"h1""div"。このプロパティには異なる値を割り当てて、要素のタグを変更できます。
  • attributes イテレーター(読み取り専用)

    • タグの属性の[name, value]ペア。
  • removed ブール値

    • 要素が削除されたか、以前のハンドラーの1つによって置き換えられたかどうかを示します。
  • namespaceURI 文字列

メソッド

  • getAttribute(namestring) : 文字列 | null

    • 要素の指定された属性名の値を返します。見つからない場合はnullを返します。
  • hasAttribute(namestring) : ブール値

    • 要素に属性が存在するかどうかを示すブール値を返します。
  • setAttribute(namestring, valuestring) : Element

    • 属性を指定された値に設定し、存在しない場合は属性を作成します。
  • removeAttribute(namestring) : Element

    • 属性を削除します。
  • before(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の前にコンテンツを挿入します。
  • after(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の直後にコンテンツを挿入します。
  • prepend(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の開始タグの直後にコンテンツを挿入します。
  • append(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の終了タグの直前にコンテンツを挿入します。
  • replace(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素を削除し、その場所にコンテンツを挿入します。
  • setInnerContent(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素のコンテンツを置き換えます。
  • remove() : Element

    • 要素とそのすべてのコンテンツを削除します。
  • removeAndKeepContent() : Element

    • 要素の開始タグと終了タグを削除しますが、その内部コンテンツは保持します。
  • onEndTag(handlerFunction<void>) : void

    • 要素の終了タグに達したときに呼び出されるハンドラーを登録します。

EndTag

endTag引数は、element.onEndTagで登録されたハンドラーでのみ使用され、DOM要素の制限された表現です。

プロパティ

  • name 文字列

    • タグの名前、例えば"h1""div"。このプロパティには異なる値を割り当てて、要素のタグを変更できます。

メソッド

  • before(contentContent, contentOptionsContentOptionsoptional) : EndTag

    • 終了タグの直前にコンテンツを挿入します。
  • after(contentContent, contentOptionsContentOptionsoptional) : EndTag

    • 終了タグの直後にコンテンツを挿入します。
  • remove() : EndTag

    • 要素をそのすべてのコンテンツと共に削除します。

テキストチャンク

Cloudflareはゼロコピーのストリーミング解析を行うため、テキストチャンクは構文木のテキストノードとは異なります。構文木のテキストノードは、オリジンからワイヤーを介して到着する際に複数のチャンクで表される可能性があります。

次のマークアップを考えてみてください:<div>Hey. How are you?</div>。Workersスクリプトは、オリジンからテキストノード全体を一度に受信しない可能性があります。代わりに、text要素ハンドラーは受信したテキストノードの各部分に対して呼び出されます。例えば、ハンドラーは“Hey. How ”で呼び出され、その後“are you?”で呼び出されるかもしれません。最後のチャンクが到着すると、テキストのlastInTextNodeプロパティはtrueに設定されます。開発者はこれらのチャンクを連結することを確認する必要があります。

プロパティ

  • removed ブール値

    • 要素が削除されたか、以前のハンドラーの1つによって置き換えられたかどうかを示します。
  • text 文字列(読み取り専用)

    • チャンクのテキストコンテンツ。チャンクがテキストノードの最後のチャンクである場合は空である可能性があります。
  • lastInTextNode ブール値(読み取り専用)

    • チャンクがテキストノードの最後のチャンクであるかどうかを示します。

メソッド

  • before(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の前にコンテンツを挿入します。
  • after(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の直後にコンテンツを挿入します。
  • replace(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素を削除し、その場所にコンテンツを挿入します。
  • remove() : Element

    • 要素とそのすべてのコンテンツを削除します。

コメント

要素ハンドラーのcomments関数は、開発者がHTMLコメントタグをクエリおよび操作できるようにします。

class ElementHandler {
comments(comment) {
// 受信したコメント要素、例えば<!-- My comment -->
}
}

プロパティ

  • comment.removed ブール値

    • 要素が削除されたか、以前のハンドラーの1つによって置き換えられたかどうかを示します。
  • comment.text 文字列

    • コメントのテキスト。このプロパティには異なる値を割り当てて、コメントのテキストを変更できます。

メソッド

  • before(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の前にコンテンツを挿入します。
  • after(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素の直後にコンテンツを挿入します。
  • replace(contentContent, contentOptionsContentOptionsoptional) : Element

    • 要素を削除し、その場所にコンテンツを挿入します。
  • remove() : Element

    • 要素とそのすべてのコンテンツを削除します。

Doctype

ドキュメントハンドラーのdoctype関数は、開発者がドキュメントのdoctypeをクエリできるようにします。

class DocumentHandler {
doctype(doctype) {
// 受信したdoctype要素、例えば
// <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
}
}

プロパティ

  • doctype.name 文字列 | null(読み取り専用)

    • doctype名。
  • doctype.publicId 文字列 | null(読み取り専用)

    • PUBLIC原子の後のdoctype内の引用された文字列。
  • doctype.systemId 文字列 | null(読み取り専用)

    • SYSTEM原子の後、またはpublicIdの直後のdoctype内の引用された文字列。

終了

ドキュメントハンドラーのend関数は、開発者がドキュメントの最後にコンテンツを追加できるようにします。

class DocumentHandler {
end(end) {
// ドキュメントの終わり
}
}

メソッド

  • append(contentContent, contentOptionsContentOptionsoptional) : DocumentEnd

    • ドキュメントの終わりの後にコンテンツを挿入します。

セレクター

これがセレクターの定義とその使用目的です。

  • *

    • すべての要素。
  • E

    • タイプEのすべての要素。
  • E:nth-child(n)

    • 親のn番目の子であるE要素。
  • E:first-child

    • 親の最初の子であるE要素。
  • E:nth-of-type(n)

    • 同じタイプのn番目の兄弟であるE要素。
  • E:first-of-type

    • 同じタイプの最初の兄弟であるE要素。
  • E:not(s)

    • 複合セレクターのいずれにも一致しないE要素。
  • E.warning

    • クラスwarningに属するE要素。
  • E#myid

    • IDがmyidに等しいE要素。
  • E[foo]

    • foo属性を持つE要素。
  • E[foo="bar"]

    • foo属性の値がbarに正確に等しいE要素。
  • E[foo="bar" i]

    • foo属性の値がbarの任意の(ASCII範囲の)大文字小文字の変化に正確に等しいE要素。
  • E[foo="bar" s]

    • foo属性の値がbarに正確かつ大文字小文字を区別して等しいE要素。
  • E[foo~="bar"]

    • foo属性の値が空白で区切られた値のリストで、そのうちの1つがbarに正確に等しいE要素。
  • E[foo^="bar"]

    • foo属性の値が文字列barで正確に始まるE要素。
  • E[foo$="bar"]

    • foo属性の値が文字列barで正確に終わるE要素。
  • E[foo*="bar"]

    • foo属性の値が部分文字列barを含むE要素。
  • E[foo|="en"]

    • foo属性の値がenで始まるハイフン区切りの値のリストであるE要素。
  • E F

    • E要素の子孫であるF要素。
  • E > F

    • E要素の子であるF要素。

エラー

ハンドラーが例外をスローすると、解析は直ちに停止し、変換されたレスポンスボディはスローされた例外でエラーとなり、未変換のレスポンスボディはキャンセル(クローズ)されます。変換されたレスポンスボディがすでにクライアントに部分的にストリーミングされている場合、クライアントは切り捨てられたレスポンスを見ることになります。

async function handle(request) {
let oldResponse = await fetch(request);
let newResponse = new HTMLRewriter()
.on('*', {
element(element) {
throw new Error('A really bad error.');
},
})
.transform(oldResponse);
// この時点で、`await newResponse.text()`のような式は
// `new Error("A really bad error.")`をスローします。
// その後、`newResponse.body`の使用は同じエラーをスローし、
// `oldResponse.body`はクローズされます。
// あるいは、これによりクライアントに切り捨てられたレスポンスが生成されます:
return newResponse;
}

関連リソース