Skip to content

Commit

Permalink
use lol-html 2 without asyncify
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeChampion committed Nov 17, 2024
1 parent 90552d5 commit 5b27b05
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 54 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ edition = "2018"
[dependencies]
js-sys = "0.3.72"
lol_html = "2.0.0"
serde = { version = "1.0.214", features = ["derive"] }
serde = { version = "1.0.215", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"
wasm-bindgen = "0.2.74"
wasm-bindgen = "0.2.95"
thiserror = "2.0.3"

[lib]
Expand Down
14 changes: 14 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified pkg/csp_nonce_html_transformer_bg.wasm
Binary file not shown.
4 changes: 3 additions & 1 deletion src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ impl IntoNativeHandlers<NativeElementContentHandlers<'static>> for ElementConten
let (js_arg, anchor) = Element::from_native(el);
let js_arg = JsValue::from(js_arg);
let res = match handler.call1(&this, &js_arg) {
Ok(_) => Ok(()),
Ok(_) => {
Ok(())
}
Err(e) => Err(HandlerJsErrorWrap(e).into()),
};
mem::drop(anchor);
Expand Down
1 change: 1 addition & 0 deletions src/html_rewriter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ impl HTMLRewriter {
Ok(())
}


pub fn write(&mut self, chunk: &[u8]) -> JsResult<()> {
self.inner_mut()?
.write(chunk)
Expand Down
70 changes: 24 additions & 46 deletions src/html_rewriter_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ import { HTMLRewriter as RawHTMLRewriter } from "../pkg/html_rewriter.js";

import { ElementHandlers } from "./types.d.ts";

class HTMLRewriter {
static initPromise?: Promise<void>;
type SelectorElementHandlers = [selector: string, handlers: ElementHandlers];

constructor() {}
export class HTMLRewriter {
readonly #elementHandlers: SelectorElementHandlers[] = [];

elementHandlers: [selector: string, handlers: ElementHandlers][] = [];

on(selector: string, handlers: ElementHandlers): HTMLRewriter {
this.elementHandlers.push([selector, handlers]);
on(selector: string, handlers: ElementHandlers): this {
this.#elementHandlers.push([selector, handlers]);
return this;
}

transform(response: Response): Response {
const body = response.body as ReadableStream<Uint8Array> | null;
// HTMLRewriter doesn't run the end handler if the body is null, so it's
// pointless to setup the readable stream.
// pointless to setup the transform stream.
if (body === null) return new Response(body, response);

if (response instanceof Response) {
Expand All @@ -27,56 +25,36 @@ class HTMLRewriter {
}

let rewriter: RawHTMLRewriter;
const readable = new ReadableStream<Uint8Array>({
start: async (controller) => {
const transformStream = new TransformStream<Uint8Array, Uint8Array>({
start: (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 HTMLRewriter.initPromise;
// console.log('creating rewriter')
rewriter = new RawHTMLRewriter((chunk: Uint8Array) => {
// enqueue will throw on empty chunks
if (chunk.length !== 0) controller.enqueue(chunk);
});
// RawHTMLRewriter can only be used once.
rewriter = new RawHTMLRewriter(
(output: Uint8Array) => {
// enqueue will throw on empty chunks
if (output.length !== 0) controller.enqueue(output);
},
);
// Add all registered handlers
for (const [selector, handlers] of this.elementHandlers) {
for (const [selector, handlers] of this.#elementHandlers) {
rewriter.on(selector, handlers);
}

// Pipe the response body to the rewriter
const reader = body.getReader();
try {
while (true) {
// console.log('reading')
const { done, value } = await reader.read();
if (done) break;
rewriter.write(value);
}

rewriter.end();
} catch (error) {
// rewriter.end()
controller.error(error);
} finally {
rewriter.free();
reader.releaseLock();
controller.close();
}
},
// 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(readable, response);
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;
}
}

export function HTMLRewriterWrapper(
initPromise: typeof HTMLRewriter.initPromise,
) {
HTMLRewriter.initPromise = initPromise;
return HTMLRewriter;
}
7 changes: 2 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import init from "../pkg/html_rewriter.js";
import { HTMLRewriterWrapper } from "./html_rewriter_wrapper.ts";
import { HTMLRewriter } from "./html_rewriter_wrapper.ts";
import { Element } from "./types.d.ts";

type Params = {
Expand Down Expand Up @@ -146,10 +146,7 @@ export async function csp(originalResponse: Response, params?: Params) {
}

const querySelectors = ["script", 'link[rel="preload"][as="script"]'];
const HTMLRewriter: ReturnType<typeof HTMLRewriterWrapper> =
HTMLRewriterWrapper(
await init(),
);
await init();
return new HTMLRewriter()
.on(querySelectors.join(","), {
element(element: Element) {
Expand Down

0 comments on commit 5b27b05

Please sign in to comment.