-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodule.ts
103 lines (94 loc) · 3.02 KB
/
module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
class USVString {
#encoder = new TextEncoder();
#decoder = new TextDecoder();
decode = (input: BufferSource, options?: TextDecodeOptions) =>
this.#decoder.decode(input, options);
encode = (input: string) => this.#encoder.encode(input);
}
export type { ElementHandlers } from "html-rewriter-wasm";
import * as cf from "html-rewriter-wasm";
export class HTMLRewriter {
static #string = new USVString();
#output = "";
#isFlushed = false;
#cache = new Map<string, [cf.ElementHandlers, ElementHandlers]>();
#new = () =>
new cf.HTMLRewriter(
(chunk) => this.#output += HTMLRewriter.#string.decode(chunk),
{ enableEsiTags: false },
);
#rewrite = this.#new();
#mayReconstruct() {
const yes = this.#isFlushed;
if (yes) {
this.#rewrite.free();
this.#rewrite = this.#new();
this.#isFlushed = false;
}
return yes;
}
#reinstate() {
this.#cache.forEach(([handle], selector) =>
this.#rewrite.on(selector, handle)
);
}
free() {
this.#rewrite.free();
this.#cache.clear();
}
off(selector: string, handlers?: cf.ElementHandlers) {
if (handlers) {
const [handle, list] = this.#cache.get(selector)!;
for (const h in handlers) {
type H = keyof typeof handlers;
const lh = list[h as H]; // @ts-ignore: typescript can't infer handlers[h]
lh?.delete(handlers[h as H]);
if (lh?.size === 0) {
delete list[h as H];
delete handle[h as H];
}
}
if (Object.keys(handle).length === 0) this.#cache.delete(selector);
} else this.#cache.delete(selector);
return this;
}
on(selector: string, handlers: cf.ElementHandlers) {
let notCached: [cf.ElementHandlers, ElementHandlers] | undefined;
const [handle, list] = this.#cache.get(selector) ??
(this.#cache.set(selector, notCached = [{}, {}]), notCached);
for (const h in handlers) { // @ts-ignore: current typescript can't infer this
(list[h] ??= new Set()).add(handlers[h]); // @ts-ignore: current typescript can't infer this
handle[h] ??= (...args) => list[h].forEach((handle) => handle(...args));
}
if (this.#mayReconstruct() || notCached) {
this.#rewrite.on(selector, handle!);
}
return this;
}
#ephemeral = new Map<string, cf.ElementHandlers>();
once(selector: string, handlers: cf.ElementHandlers) {
this.on(selector, handlers);
this.#ephemeral.set(selector, handlers);
}
async transform(input: string) {
if (this.#mayReconstruct()) this.#reinstate();
await this.#rewrite.write(HTMLRewriter.#string.encode(input));
this.#isFlushed = true;
const result: string = this.#output;
await this.#reset();
return result;
}
async #reset() {
await this.#rewrite.end();
this.#output = "";
if (this.#ephemeral.size > 0) {
this.#ephemeral.forEach((handlers, selector) =>
this.off(selector, handlers)
);
this.#ephemeral.clear();
}
}
}
type ElementHandlers = {
[K in keyof cf.ElementHandlers]: Set<cf.ElementHandlers[K]>;
};