HTMLRewriter クラスは、Cloudflare Workers アプリ内で包括的で表現力のある HTML パーサーを構築できるようにします。Workers アプリ内で、jQuery のような体験だと考えると分かりやすいです。強力な JavaScript API で HTML を解析・変換し、深く機能するアプリを作れます。
HTMLRewriter クラスは、Workers スクリプト内で一度インスタンス化し、on と onDocument 関数で複数のハンドラーを付けます。
new HTMLRewriter()
.on("*", new ElementHandler())
.onDocument(new DocumentHandler());HTMLRewriter API 全体で、多くのプロパティとメソッドが次の型を一貫して使います。
-
Contentstring | Response | ReadableStream- 出力ストリームに挿入するコンテンツは、文字列、
Response、またはReadableStreamです。
- 出力ストリームに挿入するコンテンツは、文字列、
-
ContentOptionsObject{ html: Boolean }は、HTMLRewriter が挿入コンテンツを扱う方法を制御します。htmlが true の場合、コンテンツは生の HTML として扱われます。htmlが false または未指定の場合、コンテンツはテキストとして扱われ、適切な HTML エスケープが適用されます。
HTMLRewriter で使えるハンドラーは、要素ハンドラーとドキュメントハンドラーの 2 種類です。
要素ハンドラーは、HTMLRewriter インスタンスの .on 関数で付けると、着信する要素に応答します。要素ハンドラーは element、comments、text に応答します。次の例は、ElementHandler クラスで div 要素を処理します。
class ElementHandler {
element(element) {
// An incoming element, such as `div`
console.log(`Incoming element: ${element.tagName}`);
}
comments(comment) {
// An incoming comment
}
text(text) {
// An incoming piece of text
}
}
async function handleRequest(req) {
const res = await fetch(req);
return new HTMLRewriter().on("div", new ElementHandler()).transform(res);
}ドキュメントハンドラーは、着信する HTML ドキュメントを表します。ドキュメントハンドラーには、ドキュメントの doctype、comments、text、end を照会・操作する関数を定義できます。要素ハンドラーと異なり、ドキュメントハンドラーの doctype、comments、text、end は特定のセレクターでスコープされません。ドキュメントハンドラーの関数は、トップレベルの HTML タグの外を含む、ページ上のすべてのコンテンツに対して呼ばれます。
class DocumentHandler {
doctype(doctype) {
// An incoming doctype, such as <!DOCTYPE html>
}
comments(comment) {
// An incoming comment
}
text(text) {
// An incoming piece of text
}
end(end) {
// The end of the document
}
}要素ハンドラーとドキュメントハンドラーのすべての関数は、void または Promise<void> を返せます。ハンドラー関数を async にすると、fetch 経由の API、Workers KV、Durable Objects、キャッシュなどの外部リソースにアクセスできます。
class UserElementHandler {
async element(element) {
let response = await fetch(new Request("/user"));
// fill in user info using response
}
}
async function handleRequest(req) {
const res = await fetch(req);
// run the user element handler via HTMLRewriter on a div with ID `user_info`
return new HTMLRewriter()
.on("div#user_info", new UserElementHandler())
.transform(res);
}要素ハンドラーでのみ使う element 引数は、DOM 要素の表現です。要素を照会・操作するメソッドがいくつかあります。
-
tagNamestring- タグ名です。例:
"h1"や"div"。別の値を代入して、要素のタグを変更できます。
- タグ名です。例:
-
attributesIterator read-only- タグの属性の
[name, value]ペアです。
- タグの属性の
-
removedboolean- 要素が、前のハンドラーのいずれかによって削除または置換されたかを示します。
-
namespaceURIstring- 要素の namespace URI ↗ を表します。
-
getAttribute(name:string)string | null- 要素上の指定した属性名の値を返します。見つからない場合は
nullです。
- 要素上の指定した属性名の値を返します。見つからない場合は
-
hasAttribute(name:string)boolean- 要素に属性が存在するかを示す boolean を返します。
-
setAttribute(name:string, valuestring)Element- 属性を指定した値に設定します。存在しない場合は作成します。
-
removeAttribute(name:string)Element- 属性を削除します。
-
before(content:Content, contentOptionsContentOptionsoptional)Element- 要素の前にコンテンツを挿入します。
-
after(content:Content, contentOptionsContentOptionsoptional)Element- 要素の直後にコンテンツを挿入します。
-
prepend(content:Content, contentOptionsContentOptionsoptional)Element- 要素の開始タグの直後にコンテンツを挿入します。
-
append(content:Content, contentOptionsContentOptionsoptional)Element- 要素の終了タグの直前にコンテンツを挿入します。
-
replace(content:Content, contentOptionsContentOptionsoptional)Element- 要素を削除し、その場所にコンテンツを挿入します。
-
setInnerContent(content:Content, contentOptionsContentOptionsoptional)Element- 要素のコンテンツを置き換えます。
-
remove():Element- 要素とその中身をすべて削除します。
-
removeAndKeepContent():Element- 要素の開始タグと終了タグを削除し、内側のコンテンツは残します。
-
onEndTag(handler:Function<void>)void- 要素の終了タグに到達したときに呼ばれるハンドラーを登録します。
element.onEndTag で登録したハンドラーでのみ使う endTag 引数は、DOM 要素の限定的な表現です。
namestring- タグ名です。例:
"h1"や"div"。別の値を代入して、要素のタグを変更できます。
- タグ名です。例:
-
before(content:Content, contentOptionsContentOptionsoptional)EndTag- 終了タグの直前にコンテンツを挿入します。
-
after(content:Content, contentOptionsContentOptionsoptional)EndTag- 終了タグの直後にコンテンツを挿入します。
-
remove():EndTag- 要素とその中身をすべて削除します。
Cloudflare はゼロコピーのストリーミング解析を行うため、テキストチャンクは字句木のテキストノードと同じではありません。字句木の 1 つのテキストノードが、オリジンから回線経由で届く複数のチャンクとして表されることがあります。
次のマークアップを考えます: <div>Hey. How are you?</div>。Workers スクリプトがオリジンからテキストノード全体を一度に受け取らないことがあります。その場合、text 要素ハンドラーは、テキストノードの受け取った各部分に対して呼ばれます。たとえば、まず "Hey. How "、次に "are you?" で呼ばれることがあります。最後のチャンクが届くと、テキストの lastInTextNode プロパティが true になります。これらのチャンクは連結してください。
-
removedboolean- 要素が、前のハンドラーのいずれかによって削除または置換されたかを示します。
-
textstring read-only- チャンクのテキスト内容です。テキストノードの最後のチャンクである場合、空になることがあります。
-
lastInTextNodeboolean read-only- チャンクがテキストノードの最後のチャンクかどうかを示します。
-
before(content:Content, contentOptionsContentOptionsoptional)Element- 要素の前にコンテンツを挿入します。
-
after(content:Content, contentOptionsContentOptionsoptional)Element- 要素の直後にコンテンツを挿入します。
-
replace(content:Content, contentOptionsContentOptionsoptional)Element- 要素を削除し、その場所にコンテンツを挿入します。
-
remove():Element- 要素とその中身をすべて削除します。
要素ハンドラーの comments 関数で、HTML コメントタグを照会・操作できます。
class ElementHandler {
comments(comment) {
// An incoming comment element, such as <!-- My comment -->
}
}-
comment.removedboolean- 要素が、前のハンドラーのいずれかによって削除または置換されたかを示します。
-
comment.textstring- コメントのテキストです。別の値を代入して、コメントのテキストを変更できます。
-
before(content:Content, contentOptionsContentOptionsoptional)Element- 要素の前にコンテンツを挿入します。
-
after(content:Content, contentOptionsContentOptionsoptional)Element- 要素の直後にコンテンツを挿入します。
-
replace(content:Content, contentOptionsContentOptionsoptional)Element- 要素を削除し、その場所にコンテンツを挿入します。
-
remove():Element- 要素とその中身をすべて削除します。
ドキュメントハンドラーの doctype 関数で、ドキュメントの doctype ↗ を照会できます。
class DocumentHandler {
doctype(doctype) {
// An incoming doctype element, such as
// <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
}
}-
doctype.namestring | null read-only- doctype 名です。
-
doctype.publicIdstring | null read-only- doctype 内の PUBLIC 原子のあとの引用符付き文字列です。
-
doctype.systemIdstring | null read-only- doctype 内の SYSTEM 原子のあと、または
publicIdの直後にある引用符付き文字列です。
- doctype 内の SYSTEM 原子のあと、または
ドキュメントハンドラーの end 関数で、ドキュメントの末尾にコンテンツを追加できます。
class DocumentHandler {
end(end) {
// The end of the document
}
}-
append(content:Content, 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);
// At this point, an expression like `await newResponse.text()`
// will throw `new Error("A really bad error.")`.
// Thereafter, any use of `newResponse.body` will throw the same error,
// and `oldResponse.body` will be closed.
// Alternatively, this will produce a truncated response to the client:
return newResponse;
}