UNPKG

@worker-tools/html-rewriter

Version:

WASM-based implementation of Cloudflare's HTML Rewriter for use in Deno, browsers, etc.

122 lines 5.38 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.withEnableEsiTags = exports.HTMLRewriter = void 0; const _base = __importStar(require("./vendor/html_rewriter.js")); const { default: initWASM } = _base; const base = _base; const resolvable_promise_1 = require("@worker-tools/resolvable-promise"); const kEnableEsiTags = Symbol("kEnableEsiTags"); // In case a server doesn't return the proper mime type (e.g. githubusercontent.com).. const toWASMResponse = (response) => { if (response.headers.get('content-type')?.startsWith('application/wasm')) return response; const { body, headers: hs, ...props } = response; const headers = new Headers(hs); headers.set('content-type', 'application/wasm'); return new Response(body, { ...props, headers }); }; const initialized = new resolvable_promise_1.ResolvablePromise(); let executing = false; class HTMLRewriter { #elementHandlers = []; #documentHandlers = []; [kEnableEsiTags] = false; constructor() { if (!initialized.settled && !executing) { executing = true; fetch(new URL("./vendor/html_rewriter_bg.wasm", require("url").pathToFileURL(__filename).href).href) .then(r => r.ok ? r : (() => { throw Error('WASM response not ok'); })()) .then(toWASMResponse) .then(initWASM) .then(() => initialized.resolve()) .catch(err => { executing = false; console.error(err); }); } } on(selector, handlers) { this.#elementHandlers.push([selector, handlers]); return this; } onDocument(handlers) { this.#documentHandlers.push(handlers); return this; } transform(response) { const body = response.body; // HTMLRewriter doesn't run the end handler if the body is null, so it's // pointless to setup the transform stream. if (body === null) return new Response(body, response); if (response instanceof Response) { // Make sure we validate chunks are BufferSources and convert them to // Uint8Arrays as required by the Rust glue code. response = new Response(response.body, response); } let rewriter; const transformStream = new TransformStream({ start: async (controller) => { // Create a rewriter instance for this transformation that writes its // output to the transformed response's stream. Note that each // BaseHTMLRewriter can only be used once. await initialized; rewriter = new base.HTMLRewriter((output) => { // enqueue will throw on empty chunks if (output.length !== 0) controller.enqueue(output); }, { enableEsiTags: this[kEnableEsiTags] }); // Add all registered handlers for (const [selector, handlers] of this.#elementHandlers) { rewriter.on(selector, handlers); } for (const handlers of this.#documentHandlers) { rewriter.onDocument(handlers); } }, // The finally() below will ensure the rewriter is always freed. // chunk is guaranteed to be a Uint8Array as we're using the // @miniflare/core Response class, which transforms to a byte stream. transform: (chunk) => rewriter.write(chunk), flush: () => rewriter.end(), }); const promise = body.pipeTo(transformStream.writable); promise.catch(() => { }).finally(() => rewriter?.free()); // Return a response with the transformed body, copying over headers, etc const res = new Response(transformStream.readable, response); // If Content-Length is set, it's probably going to be wrong, since we're // rewriting content, so remove it res.headers.delete("Content-Length"); return res; } } exports.HTMLRewriter = HTMLRewriter; function withEnableEsiTags(rewriter) { rewriter[kEnableEsiTags] = true; return rewriter; } exports.withEnableEsiTags = withEnableEsiTags; //# sourceMappingURL=index.js.map