@miaskiewicz/turbo-dom 0.1.57 → 0.1.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/package.json +23 -4
- package/pkg/turbo_dom_parser.d.ts +11 -0
- package/pkg/turbo_dom_parser.js +197 -0
- package/pkg/turbo_dom_parser_bg.wasm +0 -0
- package/pkg/turbo_dom_parser_bg.wasm.d.ts +11 -0
- package/pkg-web/turbo_dom_parser.d.ts +47 -0
- package/pkg-web/turbo_dom_parser.js +289 -0
- package/pkg-web/turbo_dom_parser_bg.wasm +0 -0
- package/pkg-web/turbo_dom_parser_bg.wasm.d.ts +11 -0
- package/src/core.rs +3 -3
- package/src/runtime/buffer.mjs +21 -1
- package/src/runtime/cascade.mjs +8 -1
- package/src/runtime/cssom.mjs +109 -0
- package/src/runtime/dom.mjs +26 -7
- package/src/runtime/index.mjs +12 -5
- package/src/runtime/parser.mjs +68 -0
- package/src/runtime/window.mjs +24 -6
- package/turbo-dom-parser.linux-x64-gnu.node +0 -0
- package/turbo-dom-parser.win32-x64-msvc.node +0 -0
package/README.md
CHANGED
|
@@ -85,6 +85,37 @@ parseBuffer('<div id=a>...</div>'); // compact SoA typed-array bu
|
|
|
85
85
|
parseFragment('<rect/>', 'svg path'); // fragment in a context element
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
### Choosing the parser backend (native vs WASM)
|
|
89
|
+
|
|
90
|
+
The runtime resolves a parser lazily on first parse: native N-API addon first, WASM
|
|
91
|
+
fallback. Force it per environment or globally:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
import { createEnvironment, setParserMode } from '@miaskiewicz/turbo-dom/runtime';
|
|
95
|
+
|
|
96
|
+
createEnvironment(html, { parser: 'wasm' }); // 'wasm' | 'native' | 'auto' (default)
|
|
97
|
+
setParserMode('wasm'); // process-global; also TURBO_DOM_PARSER=wasm
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Embedding in a non-Node runtime (bare V8, no Node builtins)
|
|
101
|
+
|
|
102
|
+
The native addon and the `--target nodejs` WASM build both need Node. For a fully
|
|
103
|
+
node-free host, instantiate the `--target web` WASM yourself and inject the binding —
|
|
104
|
+
the runtime then never touches `node:module`/`fs`:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
import init, { initSync, parse, parseBuffer, parseFragment } from '@miaskiewicz/turbo-dom/parser-wasm';
|
|
108
|
+
import { setParser, createEnvironment } from '@miaskiewicz/turbo-dom/runtime';
|
|
109
|
+
|
|
110
|
+
initSync({ module: wasmBytes }); // you supply the bytes (sync, no fs)
|
|
111
|
+
setParser({ parse, parseBuffer, parseFragment });
|
|
112
|
+
createEnvironment('<div id=app/>'); // now runs with zero Node deps
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`setParser` also reads `globalThis.__TURBO_DOM_PARSER__` if you prefer injection by
|
|
116
|
+
global. Use `installGlobals(globalThis, { html })` (from `@miaskiewicz/turbo-dom/install`)
|
|
117
|
+
to set up `document`/`window` on any global object.
|
|
118
|
+
|
|
88
119
|
## Compatibility
|
|
89
120
|
|
|
90
121
|
| | turbo-dom | happy-dom | jsdom |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miaskiewicz/turbo-dom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
4
4
|
"description": "Faster, more spec-correct DOM for test runners — native html5ever (Rust/WASM) parser + lazy copy-on-write DOM. A drop-in-style alternative to jsdom/happy-dom for vitest & jest.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.js",
|
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
"./environment/vitest": {
|
|
19
19
|
"import": "./src/environment/vitest.mjs"
|
|
20
20
|
},
|
|
21
|
+
"./environment/install": {
|
|
22
|
+
"import": "./src/environment/install.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./install": {
|
|
25
|
+
"import": "./src/environment/install.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./parser-wasm": {
|
|
28
|
+
"types": "./pkg-web/turbo_dom_parser.d.ts",
|
|
29
|
+
"import": "./pkg-web/turbo_dom_parser.js"
|
|
30
|
+
},
|
|
21
31
|
"./jest": {
|
|
22
32
|
"require": "./src/environment/jest.cjs"
|
|
23
33
|
},
|
|
@@ -27,6 +37,14 @@
|
|
|
27
37
|
"index.js",
|
|
28
38
|
"index.d.ts",
|
|
29
39
|
"*.node",
|
|
40
|
+
"pkg/turbo_dom_parser.js",
|
|
41
|
+
"pkg/turbo_dom_parser.d.ts",
|
|
42
|
+
"pkg/turbo_dom_parser_bg.wasm",
|
|
43
|
+
"pkg/turbo_dom_parser_bg.wasm.d.ts",
|
|
44
|
+
"pkg-web/turbo_dom_parser.js",
|
|
45
|
+
"pkg-web/turbo_dom_parser.d.ts",
|
|
46
|
+
"pkg-web/turbo_dom_parser_bg.wasm",
|
|
47
|
+
"pkg-web/turbo_dom_parser_bg.wasm.d.ts",
|
|
30
48
|
"src/**/*.mjs",
|
|
31
49
|
"src/**/*.cjs",
|
|
32
50
|
"src/**/*.d.ts",
|
|
@@ -51,12 +69,13 @@
|
|
|
51
69
|
}
|
|
52
70
|
},
|
|
53
71
|
"scripts": {
|
|
54
|
-
"build": "napi build --platform --release",
|
|
72
|
+
"build": "napi build --platform --release && npm run build:wasm:pkg && npm run build:wasm:web",
|
|
55
73
|
"build:debug": "napi build --platform",
|
|
56
74
|
"build:wasm": "cargo build --release --no-default-features --features wasm-bind --target wasm32-unknown-unknown",
|
|
57
75
|
"build:wasm:pkg": "wasm-pack build --target nodejs --out-dir pkg --no-default-features --features wasm-bind",
|
|
76
|
+
"build:wasm:web": "wasm-pack build --target web --out-dir pkg-web --no-default-features --features wasm-bind",
|
|
58
77
|
"test": "node --test 'test/*.mjs'",
|
|
59
|
-
"test:cov": "node --test --experimental-test-coverage --test-coverage-include='src/runtime/**' --test-coverage-lines=99 --test-coverage-functions=
|
|
78
|
+
"test:cov": "node --test --experimental-test-coverage --test-coverage-include='src/runtime/**' --test-coverage-lines=99 --test-coverage-functions=92 --test-coverage-branches=88 'test/*.mjs'",
|
|
60
79
|
"test:rust": "cargo test --lib",
|
|
61
80
|
"test:all": "npm run build && npm run test:rust && npm test",
|
|
62
81
|
"prepare": "git config core.hooksPath .githooks || true",
|
|
@@ -67,7 +86,7 @@
|
|
|
67
86
|
"bench:suite": "node bench/suite.mjs",
|
|
68
87
|
"bench:wasm": "node bench/wasm.mjs",
|
|
69
88
|
"bench:all": "node bench/parse.mjs && node bench/construct.mjs && node bench/suite.mjs && node bench/query.mjs && node bench/wasm.mjs",
|
|
70
|
-
"prepublishOnly": "napi build --platform --release",
|
|
89
|
+
"prepublishOnly": "napi build --platform --release && npm run build:wasm:pkg && npm run build:wasm:web",
|
|
71
90
|
"test:vitest": "vitest run -c test-vitest/vitest.config.mjs",
|
|
72
91
|
"test:jest": "jest -c test-jest/jest.config.cjs",
|
|
73
92
|
"bench:query": "node bench/query.mjs",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Same contract as the native addon, marshaled via serde → JS object.
|
|
6
|
+
*/
|
|
7
|
+
export function parse(html: string): any;
|
|
8
|
+
|
|
9
|
+
export function parseBuffer(html: string): any;
|
|
10
|
+
|
|
11
|
+
export function parseFragment(html: string, context?: string | null): any;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/* @ts-self-types="./turbo_dom_parser.d.ts" */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Same contract as the native addon, marshaled via serde → JS object.
|
|
5
|
+
* @param {string} html
|
|
6
|
+
* @returns {any}
|
|
7
|
+
*/
|
|
8
|
+
function parse(html) {
|
|
9
|
+
const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
10
|
+
const len0 = WASM_VECTOR_LEN;
|
|
11
|
+
const ret = wasm.parse(ptr0, len0);
|
|
12
|
+
if (ret[2]) {
|
|
13
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
14
|
+
}
|
|
15
|
+
return takeFromExternrefTable0(ret[0]);
|
|
16
|
+
}
|
|
17
|
+
exports.parse = parse;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} html
|
|
21
|
+
* @returns {any}
|
|
22
|
+
*/
|
|
23
|
+
function parseBuffer(html) {
|
|
24
|
+
const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
25
|
+
const len0 = WASM_VECTOR_LEN;
|
|
26
|
+
const ret = wasm.parseBuffer(ptr0, len0);
|
|
27
|
+
if (ret[2]) {
|
|
28
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
29
|
+
}
|
|
30
|
+
return takeFromExternrefTable0(ret[0]);
|
|
31
|
+
}
|
|
32
|
+
exports.parseBuffer = parseBuffer;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} html
|
|
36
|
+
* @param {string | null} [context]
|
|
37
|
+
* @returns {any}
|
|
38
|
+
*/
|
|
39
|
+
function parseFragment(html, context) {
|
|
40
|
+
const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
41
|
+
const len0 = WASM_VECTOR_LEN;
|
|
42
|
+
var ptr1 = isLikeNone(context) ? 0 : passStringToWasm0(context, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
43
|
+
var len1 = WASM_VECTOR_LEN;
|
|
44
|
+
const ret = wasm.parseFragment(ptr0, len0, ptr1, len1);
|
|
45
|
+
if (ret[2]) {
|
|
46
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
47
|
+
}
|
|
48
|
+
return takeFromExternrefTable0(ret[0]);
|
|
49
|
+
}
|
|
50
|
+
exports.parseFragment = parseFragment;
|
|
51
|
+
function __wbg_get_imports() {
|
|
52
|
+
const import0 = {
|
|
53
|
+
__proto__: null,
|
|
54
|
+
__wbg_String_8564e559799eccda: function(arg0, arg1) {
|
|
55
|
+
const ret = String(arg1);
|
|
56
|
+
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
57
|
+
const len1 = WASM_VECTOR_LEN;
|
|
58
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
|
59
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
|
60
|
+
},
|
|
61
|
+
__wbg___wbindgen_throw_1506f2235d1bdba0: function(arg0, arg1) {
|
|
62
|
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
63
|
+
},
|
|
64
|
+
__wbg_new_ce1ab61c1c2b300d: function() {
|
|
65
|
+
const ret = new Object();
|
|
66
|
+
return ret;
|
|
67
|
+
},
|
|
68
|
+
__wbg_new_d90091b82fdf5b91: function() {
|
|
69
|
+
const ret = new Array();
|
|
70
|
+
return ret;
|
|
71
|
+
},
|
|
72
|
+
__wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
|
|
73
|
+
arg0[arg1] = arg2;
|
|
74
|
+
},
|
|
75
|
+
__wbg_set_dca99999bba88a9a: function(arg0, arg1, arg2) {
|
|
76
|
+
arg0[arg1 >>> 0] = arg2;
|
|
77
|
+
},
|
|
78
|
+
__wbindgen_cast_0000000000000001: function(arg0) {
|
|
79
|
+
// Cast intrinsic for `F64 -> Externref`.
|
|
80
|
+
const ret = arg0;
|
|
81
|
+
return ret;
|
|
82
|
+
},
|
|
83
|
+
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
84
|
+
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
85
|
+
const ret = getStringFromWasm0(arg0, arg1);
|
|
86
|
+
return ret;
|
|
87
|
+
},
|
|
88
|
+
__wbindgen_init_externref_table: function() {
|
|
89
|
+
const table = wasm.__wbindgen_externrefs;
|
|
90
|
+
const offset = table.grow(4);
|
|
91
|
+
table.set(0, undefined);
|
|
92
|
+
table.set(offset + 0, undefined);
|
|
93
|
+
table.set(offset + 1, null);
|
|
94
|
+
table.set(offset + 2, true);
|
|
95
|
+
table.set(offset + 3, false);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
__proto__: null,
|
|
100
|
+
"./turbo_dom_parser_bg.js": import0,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let cachedDataViewMemory0 = null;
|
|
105
|
+
function getDataViewMemory0() {
|
|
106
|
+
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
|
107
|
+
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
108
|
+
}
|
|
109
|
+
return cachedDataViewMemory0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getStringFromWasm0(ptr, len) {
|
|
113
|
+
return decodeText(ptr >>> 0, len);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let cachedUint8ArrayMemory0 = null;
|
|
117
|
+
function getUint8ArrayMemory0() {
|
|
118
|
+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
|
119
|
+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
120
|
+
}
|
|
121
|
+
return cachedUint8ArrayMemory0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isLikeNone(x) {
|
|
125
|
+
return x === undefined || x === null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function passStringToWasm0(arg, malloc, realloc) {
|
|
129
|
+
if (realloc === undefined) {
|
|
130
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
131
|
+
const ptr = malloc(buf.length, 1) >>> 0;
|
|
132
|
+
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
|
133
|
+
WASM_VECTOR_LEN = buf.length;
|
|
134
|
+
return ptr;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let len = arg.length;
|
|
138
|
+
let ptr = malloc(len, 1) >>> 0;
|
|
139
|
+
|
|
140
|
+
const mem = getUint8ArrayMemory0();
|
|
141
|
+
|
|
142
|
+
let offset = 0;
|
|
143
|
+
|
|
144
|
+
for (; offset < len; offset++) {
|
|
145
|
+
const code = arg.charCodeAt(offset);
|
|
146
|
+
if (code > 0x7F) break;
|
|
147
|
+
mem[ptr + offset] = code;
|
|
148
|
+
}
|
|
149
|
+
if (offset !== len) {
|
|
150
|
+
if (offset !== 0) {
|
|
151
|
+
arg = arg.slice(offset);
|
|
152
|
+
}
|
|
153
|
+
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
|
154
|
+
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
|
155
|
+
const ret = cachedTextEncoder.encodeInto(arg, view);
|
|
156
|
+
|
|
157
|
+
offset += ret.written;
|
|
158
|
+
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
WASM_VECTOR_LEN = offset;
|
|
162
|
+
return ptr;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function takeFromExternrefTable0(idx) {
|
|
166
|
+
const value = wasm.__wbindgen_externrefs.get(idx);
|
|
167
|
+
wasm.__externref_table_dealloc(idx);
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
172
|
+
cachedTextDecoder.decode();
|
|
173
|
+
function decodeText(ptr, len) {
|
|
174
|
+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const cachedTextEncoder = new TextEncoder();
|
|
178
|
+
|
|
179
|
+
if (!('encodeInto' in cachedTextEncoder)) {
|
|
180
|
+
cachedTextEncoder.encodeInto = function (arg, view) {
|
|
181
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
182
|
+
view.set(buf);
|
|
183
|
+
return {
|
|
184
|
+
read: arg.length,
|
|
185
|
+
written: buf.length
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let WASM_VECTOR_LEN = 0;
|
|
191
|
+
|
|
192
|
+
const wasmPath = `${__dirname}/turbo_dom_parser_bg.wasm`;
|
|
193
|
+
const wasmBytes = require('fs').readFileSync(wasmPath);
|
|
194
|
+
const wasmModule = new WebAssembly.Module(wasmBytes);
|
|
195
|
+
let wasmInstance = new WebAssembly.Instance(wasmModule, __wbg_get_imports());
|
|
196
|
+
let wasm = wasmInstance.exports;
|
|
197
|
+
wasm.__wbindgen_start();
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
export const memory: WebAssembly.Memory;
|
|
4
|
+
export const parse: (a: number, b: number) => [number, number, number];
|
|
5
|
+
export const parseBuffer: (a: number, b: number) => [number, number, number];
|
|
6
|
+
export const parseFragment: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
7
|
+
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
8
|
+
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
9
|
+
export const __wbindgen_externrefs: WebAssembly.Table;
|
|
10
|
+
export const __externref_table_dealloc: (a: number) => void;
|
|
11
|
+
export const __wbindgen_start: () => void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Same contract as the native addon, marshaled via serde → JS object.
|
|
6
|
+
*/
|
|
7
|
+
export function parse(html: string): any;
|
|
8
|
+
|
|
9
|
+
export function parseBuffer(html: string): any;
|
|
10
|
+
|
|
11
|
+
export function parseFragment(html: string, context?: string | null): any;
|
|
12
|
+
|
|
13
|
+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
14
|
+
|
|
15
|
+
export interface InitOutput {
|
|
16
|
+
readonly memory: WebAssembly.Memory;
|
|
17
|
+
readonly parse: (a: number, b: number) => [number, number, number];
|
|
18
|
+
readonly parseBuffer: (a: number, b: number) => [number, number, number];
|
|
19
|
+
readonly parseFragment: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
20
|
+
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
21
|
+
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
22
|
+
readonly __wbindgen_externrefs: WebAssembly.Table;
|
|
23
|
+
readonly __externref_table_dealloc: (a: number) => void;
|
|
24
|
+
readonly __wbindgen_start: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Instantiates the given `module`, which can either be bytes or
|
|
31
|
+
* a precompiled `WebAssembly.Module`.
|
|
32
|
+
*
|
|
33
|
+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
34
|
+
*
|
|
35
|
+
* @returns {InitOutput}
|
|
36
|
+
*/
|
|
37
|
+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
41
|
+
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
42
|
+
*
|
|
43
|
+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
44
|
+
*
|
|
45
|
+
* @returns {Promise<InitOutput>}
|
|
46
|
+
*/
|
|
47
|
+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/* @ts-self-types="./turbo_dom_parser.d.ts" */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Same contract as the native addon, marshaled via serde → JS object.
|
|
5
|
+
* @param {string} html
|
|
6
|
+
* @returns {any}
|
|
7
|
+
*/
|
|
8
|
+
export function parse(html) {
|
|
9
|
+
const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
10
|
+
const len0 = WASM_VECTOR_LEN;
|
|
11
|
+
const ret = wasm.parse(ptr0, len0);
|
|
12
|
+
if (ret[2]) {
|
|
13
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
14
|
+
}
|
|
15
|
+
return takeFromExternrefTable0(ret[0]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} html
|
|
20
|
+
* @returns {any}
|
|
21
|
+
*/
|
|
22
|
+
export function parseBuffer(html) {
|
|
23
|
+
const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
24
|
+
const len0 = WASM_VECTOR_LEN;
|
|
25
|
+
const ret = wasm.parseBuffer(ptr0, len0);
|
|
26
|
+
if (ret[2]) {
|
|
27
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
28
|
+
}
|
|
29
|
+
return takeFromExternrefTable0(ret[0]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} html
|
|
34
|
+
* @param {string | null} [context]
|
|
35
|
+
* @returns {any}
|
|
36
|
+
*/
|
|
37
|
+
export function parseFragment(html, context) {
|
|
38
|
+
const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
39
|
+
const len0 = WASM_VECTOR_LEN;
|
|
40
|
+
var ptr1 = isLikeNone(context) ? 0 : passStringToWasm0(context, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
41
|
+
var len1 = WASM_VECTOR_LEN;
|
|
42
|
+
const ret = wasm.parseFragment(ptr0, len0, ptr1, len1);
|
|
43
|
+
if (ret[2]) {
|
|
44
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
45
|
+
}
|
|
46
|
+
return takeFromExternrefTable0(ret[0]);
|
|
47
|
+
}
|
|
48
|
+
function __wbg_get_imports() {
|
|
49
|
+
const import0 = {
|
|
50
|
+
__proto__: null,
|
|
51
|
+
__wbg_String_8564e559799eccda: function(arg0, arg1) {
|
|
52
|
+
const ret = String(arg1);
|
|
53
|
+
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
54
|
+
const len1 = WASM_VECTOR_LEN;
|
|
55
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
|
56
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
|
57
|
+
},
|
|
58
|
+
__wbg___wbindgen_throw_1506f2235d1bdba0: function(arg0, arg1) {
|
|
59
|
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
60
|
+
},
|
|
61
|
+
__wbg_new_ce1ab61c1c2b300d: function() {
|
|
62
|
+
const ret = new Object();
|
|
63
|
+
return ret;
|
|
64
|
+
},
|
|
65
|
+
__wbg_new_d90091b82fdf5b91: function() {
|
|
66
|
+
const ret = new Array();
|
|
67
|
+
return ret;
|
|
68
|
+
},
|
|
69
|
+
__wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
|
|
70
|
+
arg0[arg1] = arg2;
|
|
71
|
+
},
|
|
72
|
+
__wbg_set_dca99999bba88a9a: function(arg0, arg1, arg2) {
|
|
73
|
+
arg0[arg1 >>> 0] = arg2;
|
|
74
|
+
},
|
|
75
|
+
__wbindgen_cast_0000000000000001: function(arg0) {
|
|
76
|
+
// Cast intrinsic for `F64 -> Externref`.
|
|
77
|
+
const ret = arg0;
|
|
78
|
+
return ret;
|
|
79
|
+
},
|
|
80
|
+
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
81
|
+
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
82
|
+
const ret = getStringFromWasm0(arg0, arg1);
|
|
83
|
+
return ret;
|
|
84
|
+
},
|
|
85
|
+
__wbindgen_init_externref_table: function() {
|
|
86
|
+
const table = wasm.__wbindgen_externrefs;
|
|
87
|
+
const offset = table.grow(4);
|
|
88
|
+
table.set(0, undefined);
|
|
89
|
+
table.set(offset + 0, undefined);
|
|
90
|
+
table.set(offset + 1, null);
|
|
91
|
+
table.set(offset + 2, true);
|
|
92
|
+
table.set(offset + 3, false);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
__proto__: null,
|
|
97
|
+
"./turbo_dom_parser_bg.js": import0,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let cachedDataViewMemory0 = null;
|
|
102
|
+
function getDataViewMemory0() {
|
|
103
|
+
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
|
104
|
+
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
105
|
+
}
|
|
106
|
+
return cachedDataViewMemory0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getStringFromWasm0(ptr, len) {
|
|
110
|
+
return decodeText(ptr >>> 0, len);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let cachedUint8ArrayMemory0 = null;
|
|
114
|
+
function getUint8ArrayMemory0() {
|
|
115
|
+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
|
116
|
+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
117
|
+
}
|
|
118
|
+
return cachedUint8ArrayMemory0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isLikeNone(x) {
|
|
122
|
+
return x === undefined || x === null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function passStringToWasm0(arg, malloc, realloc) {
|
|
126
|
+
if (realloc === undefined) {
|
|
127
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
128
|
+
const ptr = malloc(buf.length, 1) >>> 0;
|
|
129
|
+
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
|
130
|
+
WASM_VECTOR_LEN = buf.length;
|
|
131
|
+
return ptr;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let len = arg.length;
|
|
135
|
+
let ptr = malloc(len, 1) >>> 0;
|
|
136
|
+
|
|
137
|
+
const mem = getUint8ArrayMemory0();
|
|
138
|
+
|
|
139
|
+
let offset = 0;
|
|
140
|
+
|
|
141
|
+
for (; offset < len; offset++) {
|
|
142
|
+
const code = arg.charCodeAt(offset);
|
|
143
|
+
if (code > 0x7F) break;
|
|
144
|
+
mem[ptr + offset] = code;
|
|
145
|
+
}
|
|
146
|
+
if (offset !== len) {
|
|
147
|
+
if (offset !== 0) {
|
|
148
|
+
arg = arg.slice(offset);
|
|
149
|
+
}
|
|
150
|
+
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
|
151
|
+
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
|
152
|
+
const ret = cachedTextEncoder.encodeInto(arg, view);
|
|
153
|
+
|
|
154
|
+
offset += ret.written;
|
|
155
|
+
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
WASM_VECTOR_LEN = offset;
|
|
159
|
+
return ptr;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function takeFromExternrefTable0(idx) {
|
|
163
|
+
const value = wasm.__wbindgen_externrefs.get(idx);
|
|
164
|
+
wasm.__externref_table_dealloc(idx);
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
169
|
+
cachedTextDecoder.decode();
|
|
170
|
+
const MAX_SAFARI_DECODE_BYTES = 2146435072;
|
|
171
|
+
let numBytesDecoded = 0;
|
|
172
|
+
function decodeText(ptr, len) {
|
|
173
|
+
numBytesDecoded += len;
|
|
174
|
+
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
|
|
175
|
+
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
176
|
+
cachedTextDecoder.decode();
|
|
177
|
+
numBytesDecoded = len;
|
|
178
|
+
}
|
|
179
|
+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const cachedTextEncoder = new TextEncoder();
|
|
183
|
+
|
|
184
|
+
if (!('encodeInto' in cachedTextEncoder)) {
|
|
185
|
+
cachedTextEncoder.encodeInto = function (arg, view) {
|
|
186
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
187
|
+
view.set(buf);
|
|
188
|
+
return {
|
|
189
|
+
read: arg.length,
|
|
190
|
+
written: buf.length
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let WASM_VECTOR_LEN = 0;
|
|
196
|
+
|
|
197
|
+
let wasmModule, wasmInstance, wasm;
|
|
198
|
+
function __wbg_finalize_init(instance, module) {
|
|
199
|
+
wasmInstance = instance;
|
|
200
|
+
wasm = instance.exports;
|
|
201
|
+
wasmModule = module;
|
|
202
|
+
cachedDataViewMemory0 = null;
|
|
203
|
+
cachedUint8ArrayMemory0 = null;
|
|
204
|
+
wasm.__wbindgen_start();
|
|
205
|
+
return wasm;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function __wbg_load(module, imports) {
|
|
209
|
+
if (typeof Response === 'function' && module instanceof Response) {
|
|
210
|
+
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
|
211
|
+
try {
|
|
212
|
+
return await WebAssembly.instantiateStreaming(module, imports);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
const validResponse = module.ok && expectedResponseType(module.type);
|
|
215
|
+
|
|
216
|
+
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
|
|
217
|
+
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
|
218
|
+
|
|
219
|
+
} else { throw e; }
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const bytes = await module.arrayBuffer();
|
|
224
|
+
return await WebAssembly.instantiate(bytes, imports);
|
|
225
|
+
} else {
|
|
226
|
+
const instance = await WebAssembly.instantiate(module, imports);
|
|
227
|
+
|
|
228
|
+
if (instance instanceof WebAssembly.Instance) {
|
|
229
|
+
return { instance, module };
|
|
230
|
+
} else {
|
|
231
|
+
return instance;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function expectedResponseType(type) {
|
|
236
|
+
switch (type) {
|
|
237
|
+
case 'basic': case 'cors': case 'default': return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function initSync(module) {
|
|
244
|
+
if (wasm !== undefined) return wasm;
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if (module !== undefined) {
|
|
248
|
+
if (Object.getPrototypeOf(module) === Object.prototype) {
|
|
249
|
+
({module} = module)
|
|
250
|
+
} else {
|
|
251
|
+
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const imports = __wbg_get_imports();
|
|
256
|
+
if (!(module instanceof WebAssembly.Module)) {
|
|
257
|
+
module = new WebAssembly.Module(module);
|
|
258
|
+
}
|
|
259
|
+
const instance = new WebAssembly.Instance(module, imports);
|
|
260
|
+
return __wbg_finalize_init(instance, module);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function __wbg_init(module_or_path) {
|
|
264
|
+
if (wasm !== undefined) return wasm;
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
if (module_or_path !== undefined) {
|
|
268
|
+
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
|
269
|
+
({module_or_path} = module_or_path)
|
|
270
|
+
} else {
|
|
271
|
+
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (module_or_path === undefined) {
|
|
276
|
+
module_or_path = new URL('turbo_dom_parser_bg.wasm', import.meta.url);
|
|
277
|
+
}
|
|
278
|
+
const imports = __wbg_get_imports();
|
|
279
|
+
|
|
280
|
+
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
|
281
|
+
module_or_path = fetch(module_or_path);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
|
285
|
+
|
|
286
|
+
return __wbg_finalize_init(instance, module);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export { initSync, __wbg_init as default };
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
export const memory: WebAssembly.Memory;
|
|
4
|
+
export const parse: (a: number, b: number) => [number, number, number];
|
|
5
|
+
export const parseBuffer: (a: number, b: number) => [number, number, number];
|
|
6
|
+
export const parseFragment: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
7
|
+
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
8
|
+
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
9
|
+
export const __wbindgen_externrefs: WebAssembly.Table;
|
|
10
|
+
export const __externref_table_dealloc: (a: number) => void;
|
|
11
|
+
export const __wbindgen_start: () => void;
|
package/src/core.rs
CHANGED
|
@@ -20,7 +20,7 @@ pub const DOCUMENT_NODE: u8 = 9;
|
|
|
20
20
|
pub const DOCUMENT_TYPE_NODE: u8 = 10;
|
|
21
21
|
pub const DOCUMENT_FRAGMENT_NODE: u8 = 11;
|
|
22
22
|
|
|
23
|
-
#[cfg_attr(feature = "wasm-bind", derive(serde::Serialize))]
|
|
23
|
+
#[cfg_attr(feature = "wasm-bind", derive(serde::Serialize), serde(rename_all = "camelCase"))]
|
|
24
24
|
#[derive(Debug, Clone)]
|
|
25
25
|
pub struct Attr {
|
|
26
26
|
pub name: String,
|
|
@@ -30,7 +30,7 @@ pub struct Attr {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/// One marshaled DOM node. Nested children = complete tree, no lazy indices.
|
|
33
|
-
#[cfg_attr(feature = "wasm-bind", derive(serde::Serialize))]
|
|
33
|
+
#[cfg_attr(feature = "wasm-bind", derive(serde::Serialize), serde(rename_all = "camelCase"))]
|
|
34
34
|
#[derive(Debug, Clone)]
|
|
35
35
|
pub struct Node {
|
|
36
36
|
/// DOM nodeType.
|
|
@@ -73,7 +73,7 @@ pub fn parse_html_document(html: &str) -> Node {
|
|
|
73
73
|
// the arrays and inflates node objects only on access (no eager full-tree alloc).
|
|
74
74
|
|
|
75
75
|
/// Index-addressed flat tree. Node index IS its id; node 0 is the document.
|
|
76
|
-
#[cfg_attr(feature = "wasm-bind", derive(serde::Serialize))]
|
|
76
|
+
#[cfg_attr(feature = "wasm-bind", derive(serde::Serialize), serde(rename_all = "camelCase"))]
|
|
77
77
|
#[derive(Default)]
|
|
78
78
|
pub struct Soa {
|
|
79
79
|
pub node_type: Vec<u8>, // DOM nodeType
|
package/src/runtime/buffer.mjs
CHANGED
|
@@ -42,7 +42,27 @@ export function unpack(soa) {
|
|
|
42
42
|
tagNames, attrNames, attrPrefixes, attrValues, strings,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
// Decode the UTF-8 string blob. Prefer the platform TextDecoder (Node/browser);
|
|
46
|
+
// fall back to a tiny inline UTF-8 decoder so turbo-dom loads in a bare V8 with no
|
|
47
|
+
// web-platform globals (the native/Node path keeps using the real TextDecoder —
|
|
48
|
+
// this branch is never taken there).
|
|
49
|
+
export function utf8Decode(bytes) {
|
|
50
|
+
let out = '', i = 0;
|
|
51
|
+
const n = bytes.length;
|
|
52
|
+
while (i < n) {
|
|
53
|
+
const c = bytes[i++];
|
|
54
|
+
if (c < 0x80) out += String.fromCharCode(c);
|
|
55
|
+
else if (c < 0xe0) out += String.fromCharCode(((c & 0x1f) << 6) | (bytes[i++] & 0x3f));
|
|
56
|
+
else if (c < 0xf0) out += String.fromCharCode(((c & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f));
|
|
57
|
+
else {
|
|
58
|
+
let cp = ((c & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
|
|
59
|
+
cp -= 0x10000;
|
|
60
|
+
out += String.fromCharCode(0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
const STR_DECODER = typeof TextDecoder !== 'undefined' ? new TextDecoder() : { decode: utf8Decode };
|
|
46
66
|
|
|
47
67
|
export class Buffer {
|
|
48
68
|
constructor(soa) {
|
package/src/runtime/cascade.mjs
CHANGED
|
@@ -111,7 +111,14 @@ function buildIndex(scope) {
|
|
|
111
111
|
const styles = scope.getElementsByTagName('style');
|
|
112
112
|
const rules = [];
|
|
113
113
|
let order = 0;
|
|
114
|
-
for (let i = 0; i < styles.length; i++)
|
|
114
|
+
for (let i = 0; i < styles.length; i++) {
|
|
115
|
+
order = parseStylesheet(styles[i].textContent || '', order, rules);
|
|
116
|
+
// CSS-in-JS engines inject via sheet.insertRule(), which never touches
|
|
117
|
+
// textContent. Read those rules too (only present on emotion-touched <style>s;
|
|
118
|
+
// __sheet is undefined otherwise, so plain stylesheets pay nothing here).
|
|
119
|
+
const sheet = styles[i].__sheet;
|
|
120
|
+
if (sheet) for (let k = 0; k < sheet.cssRules.length; k++) order = parseStylesheet(sheet.cssRules[k].cssText, order, rules);
|
|
121
|
+
}
|
|
115
122
|
return rules;
|
|
116
123
|
}
|
|
117
124
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Minimal CSSOM — just enough for CSS-in-JS engines (emotion / styled-components /
|
|
2
|
+
// MUI) to inject rules at runtime without throwing, AND to feed those rules into
|
|
3
|
+
// the partial cascade (cascade.mjs) so getComputedStyle resolves them.
|
|
4
|
+
//
|
|
5
|
+
// emotion's `sheetForTag` reads `tag.sheet` and `document.styleSheets`, then calls
|
|
6
|
+
// `sheet.insertRule(rule, index)` per CSS rule. It only needs insertRule to not
|
|
7
|
+
// throw and `cssRules.length` to be readable — but we go one cheap step further:
|
|
8
|
+
// insertRule bumps Document.__version (via the owner <style>'s __touch), so the
|
|
9
|
+
// version-keyed style index in cascade.mjs rebuilds and picks the rules up.
|
|
10
|
+
//
|
|
11
|
+
// HONEST: this is a rule STORE, not a CSS engine. No serialization normalization,
|
|
12
|
+
// no @media evaluation, no specificity beyond what cascade.mjs already does. Rules
|
|
13
|
+
// are kept as their raw cssText; cascade.mjs parses them the same way it parses a
|
|
14
|
+
// <style>'s textContent.
|
|
15
|
+
|
|
16
|
+
export class CSSStyleRule {
|
|
17
|
+
constructor(cssText) {
|
|
18
|
+
this.cssText = String(cssText);
|
|
19
|
+
this.type = 1; // CSSRule.STYLE_RULE
|
|
20
|
+
}
|
|
21
|
+
get selectorText() {
|
|
22
|
+
const i = this.cssText.indexOf('{');
|
|
23
|
+
return i < 0 ? '' : this.cssText.slice(0, i).trim();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Split a stylesheet string into top-level rule strings (brace-depth aware), used
|
|
28
|
+
// by replaceSync/constructor text. Mirrors cascade.mjs's depth scan but keeps the
|
|
29
|
+
// braces so each piece is a self-contained cssText.
|
|
30
|
+
function splitTopLevelRules(css) {
|
|
31
|
+
const out = [];
|
|
32
|
+
const n = css.length;
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i < n) {
|
|
35
|
+
let j = i;
|
|
36
|
+
while (j < n && css[j] !== '{' && css[j] !== '}') j++;
|
|
37
|
+
if (j >= n) break;
|
|
38
|
+
if (css[j] === '}') { i = j + 1; continue; }
|
|
39
|
+
let depth = 1, k = j + 1;
|
|
40
|
+
while (k < n && depth > 0) { const ch = css[k]; if (ch === '{') depth++; else if (ch === '}') depth--; k++; }
|
|
41
|
+
const piece = css.slice(i, k).trim();
|
|
42
|
+
if (piece) out.push(piece);
|
|
43
|
+
i = k;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class CSSStyleSheet {
|
|
49
|
+
// ownerNode is the <style> element (null for constructed/adopted sheets).
|
|
50
|
+
constructor(ownerNode = null, init = {}) {
|
|
51
|
+
this.ownerNode = ownerNode;
|
|
52
|
+
this.cssRules = [];
|
|
53
|
+
this.type = 'text/css';
|
|
54
|
+
this.disabled = false;
|
|
55
|
+
this.media = init.media != null ? init.media : [];
|
|
56
|
+
this.title = init.title != null ? init.title : null;
|
|
57
|
+
this.href = null;
|
|
58
|
+
this.parentStyleSheet = null;
|
|
59
|
+
this.ownerRule = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// legacy alias some libraries read
|
|
63
|
+
get rules() { return this.cssRules; }
|
|
64
|
+
|
|
65
|
+
insertRule(rule, index = 0) {
|
|
66
|
+
const i = index === undefined ? 0 : index;
|
|
67
|
+
if (i < 0 || i > this.cssRules.length) throw new RangeError('insertRule: index out of bounds');
|
|
68
|
+
this.cssRules.splice(i, 0, new CSSStyleRule(rule));
|
|
69
|
+
this.#invalidate();
|
|
70
|
+
return i;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
deleteRule(index) {
|
|
74
|
+
if (index < 0 || index >= this.cssRules.length) throw new RangeError('deleteRule: index out of bounds');
|
|
75
|
+
this.cssRules.splice(index, 1);
|
|
76
|
+
this.#invalidate();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// legacy IE-era APIs (some older libs still call these)
|
|
80
|
+
addRule(selector = '', style = '', index) {
|
|
81
|
+
const i = index === undefined ? this.cssRules.length : index;
|
|
82
|
+
this.insertRule(`${selector} { ${style} }`, i);
|
|
83
|
+
return -1; // spec'd legacy return
|
|
84
|
+
}
|
|
85
|
+
removeRule(index = 0) { this.deleteRule(index); }
|
|
86
|
+
|
|
87
|
+
// constructable-stylesheet API (adoptedStyleSheets); replaceSync is synchronous.
|
|
88
|
+
replaceSync(text) {
|
|
89
|
+
this.cssRules = splitTopLevelRules(String(text)).map((r) => new CSSStyleRule(r));
|
|
90
|
+
this.#invalidate();
|
|
91
|
+
}
|
|
92
|
+
replace(text) { this.replaceSync(text); return Promise.resolve(this); }
|
|
93
|
+
|
|
94
|
+
// Bump the owning Document's version so cascade.mjs's version-keyed style index
|
|
95
|
+
// (and any getComputedStyle cache) rebuilds. Constructed sheets (no ownerNode)
|
|
96
|
+
// have nothing to invalidate until adopted — a no-op, which is correct.
|
|
97
|
+
#invalidate() {
|
|
98
|
+
const n = this.ownerNode;
|
|
99
|
+
if (n && n.__touch) n.__touch();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Live StyleSheetList for document.styleSheets: a plain array (indexable +
|
|
104
|
+
// iterable, what emotion needs) with the spec's `item()` accessor bolted on.
|
|
105
|
+
export function styleSheetList(sheets) {
|
|
106
|
+
const list = sheets.slice();
|
|
107
|
+
list.item = (i) => list[i] ?? null;
|
|
108
|
+
return list;
|
|
109
|
+
}
|
package/src/runtime/dom.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Mutation promotes the affected node to fully-owned (COW). Reads are transparent
|
|
7
7
|
// across the boundary — a buffer-backed read and an owned read are indistinguishable.
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { getParser } from './parser.mjs';
|
|
10
10
|
import {
|
|
11
11
|
EventTarget, Event, CustomEvent,
|
|
12
12
|
UIEvent, MouseEvent, KeyboardEvent, FocusEvent,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { liveNodeList, liveHTMLCollection } from './collections.mjs';
|
|
15
15
|
import { matchesSelector, querySelector as qsel, querySelectorAll as qselAll } from './selectors.mjs';
|
|
16
16
|
import { serializeInner, serializeOuter } from './html-serialize.mjs';
|
|
17
|
+
import { CSSStyleSheet, styleSheetList } from './cssom.mjs';
|
|
17
18
|
|
|
18
19
|
// Per-node query-result cache keyed by (selector, document version). querySelectorAll
|
|
19
20
|
// returns a STATIC list per spec, so caching is safe until the next mutation bumps
|
|
@@ -56,9 +57,6 @@ function cachedQS(node, sel) {
|
|
|
56
57
|
import { Buffer } from './buffer.mjs';
|
|
57
58
|
import { makeCanvasStub } from './stubs.mjs';
|
|
58
59
|
|
|
59
|
-
const require = createRequire(import.meta.url);
|
|
60
|
-
const native = require('../../index.js');
|
|
61
|
-
|
|
62
60
|
export const ELEMENT_NODE = 1;
|
|
63
61
|
export const TEXT_NODE = 3;
|
|
64
62
|
export const COMMENT_NODE = 8;
|
|
@@ -379,6 +377,7 @@ export class Element extends Node {
|
|
|
379
377
|
this.__qsCache = undefined; // querySelector cache
|
|
380
378
|
this.__computedStyle = undefined; // getComputedStyle snapshot (version-keyed)
|
|
381
379
|
this.__shadow = undefined; // attached shadow root (open or closed)
|
|
380
|
+
this.__sheet = undefined; // CSSOM CSSStyleSheet for <style> (lazy, CSS-in-JS)
|
|
382
381
|
}
|
|
383
382
|
|
|
384
383
|
get nodeType() { return ELEMENT_NODE; }
|
|
@@ -476,6 +475,16 @@ export class Element extends Node {
|
|
|
476
475
|
return this.__dataset;
|
|
477
476
|
}
|
|
478
477
|
|
|
478
|
+
// CSSOM seam for CSS-in-JS (emotion/styled-components/MUI): a <style> exposes a
|
|
479
|
+
// CSSStyleSheet so engines can insertRule() at runtime. Created lazily on first
|
|
480
|
+
// read (only emotion-touched <style>s ever get one), so plain documents pay
|
|
481
|
+
// nothing. Rules inserted here flow into the cascade via cascade.mjs reading
|
|
482
|
+
// this.__sheet. Non-<style> elements have no sheet (null), matching the platform.
|
|
483
|
+
get sheet() {
|
|
484
|
+
if (this.localName !== 'style') return null;
|
|
485
|
+
return this.__sheet ?? (this.__sheet = new CSSStyleSheet(this));
|
|
486
|
+
}
|
|
487
|
+
|
|
479
488
|
// ---- form-control properties (on the prototype so libraries that read
|
|
480
489
|
// element.constructor.prototype descriptors — e.g. user-event — find them) ----
|
|
481
490
|
get value() {
|
|
@@ -686,7 +695,7 @@ export class Element extends Node {
|
|
|
686
695
|
// ---- innerHTML / outerHTML ----
|
|
687
696
|
get innerHTML() { return serializeInner(this); }
|
|
688
697
|
set innerHTML(html) {
|
|
689
|
-
const frag =
|
|
698
|
+
const frag = getParser().parseFragment(String(html), this.__ns ? `${this.__ns} ${this.localName}` : this.localName);
|
|
690
699
|
this.__kids = [];
|
|
691
700
|
this.__touch();
|
|
692
701
|
for (const rawChild of frag.children) {
|
|
@@ -960,7 +969,7 @@ export class ShadowRoot extends DocumentFragment {
|
|
|
960
969
|
get nodeName() { return '#document-fragment'; }
|
|
961
970
|
get innerHTML() { return serializeInner(this); }
|
|
962
971
|
set innerHTML(html) {
|
|
963
|
-
const frag =
|
|
972
|
+
const frag = getParser().parseFragment(String(html), ''); // empty context → body
|
|
964
973
|
this.__kids = [];
|
|
965
974
|
this.__touch();
|
|
966
975
|
for (const rawChild of frag.children) {
|
|
@@ -1543,6 +1552,16 @@ export class Document extends Node {
|
|
|
1543
1552
|
get activeElement() { return this.__active || this.body || null; }
|
|
1544
1553
|
__setActive(el) { this.__active = el; }
|
|
1545
1554
|
|
|
1555
|
+
// Live StyleSheetList. emotion iterates this to find its own <style>'s sheet by
|
|
1556
|
+
// ownerNode. Touching `.sheet` here lazily creates a CSSStyleSheet per <style>
|
|
1557
|
+
// (test-time only — never on parse/query paths).
|
|
1558
|
+
get styleSheets() {
|
|
1559
|
+
const styles = this.getElementsByTagName('style');
|
|
1560
|
+
const sheets = [];
|
|
1561
|
+
for (let i = 0; i < styles.length; i++) sheets.push(styles[i].sheet);
|
|
1562
|
+
return styleSheetList(sheets);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1546
1565
|
// ---- factories (owned nodes, no buffer) ----
|
|
1547
1566
|
createElement(tag) { return new Element(this, String(tag).toLowerCase(), ''); }
|
|
1548
1567
|
createElementNS(ns, qualified) {
|
|
@@ -1699,7 +1718,7 @@ export { Event, CustomEvent };
|
|
|
1699
1718
|
|
|
1700
1719
|
// Parse an HTML string into a fresh Document over the immutable SoA buffer.
|
|
1701
1720
|
export function parseDocument(html) {
|
|
1702
|
-
const soa =
|
|
1721
|
+
const soa = getParser().parseBuffer(String(html));
|
|
1703
1722
|
const doc = new Document();
|
|
1704
1723
|
doc.__load(soa);
|
|
1705
1724
|
return doc;
|
package/src/runtime/index.mjs
CHANGED
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
// env.reset(); // Layer 5: cheap per-file reset
|
|
7
7
|
// env.reset('<body>next</body>');
|
|
8
8
|
|
|
9
|
-
import { createRequire } from 'node:module';
|
|
10
9
|
import { Document } from './dom.mjs';
|
|
11
10
|
import { createWindow } from './window.mjs';
|
|
12
11
|
import { unpack } from './buffer.mjs';
|
|
13
|
-
|
|
14
|
-
const require = createRequire(import.meta.url);
|
|
15
|
-
const native = require('../../index.js');
|
|
12
|
+
import { getParser, setParserMode } from './parser.mjs';
|
|
16
13
|
|
|
17
14
|
export { Document } from './dom.mjs';
|
|
18
15
|
export * from './dom.mjs';
|
|
19
16
|
|
|
17
|
+
// Parser-selection seam (public API): inject a ready binding for a node-free host,
|
|
18
|
+
// or force 'wasm' | 'native' | 'auto'. See parser.mjs.
|
|
19
|
+
export { setParser, setParserMode, getParser } from './parser.mjs';
|
|
20
|
+
|
|
20
21
|
// Parse cache: the SoA buffer is READ-ONLY (every mutation goes to a Document's
|
|
21
22
|
// own __kids/__attrs/__cache overlay, never the buffer), so the same buffer can
|
|
22
23
|
// back many Documents. Test suites call setup with the SAME html per file
|
|
@@ -34,7 +35,7 @@ function parseBufferCached(html) {
|
|
|
34
35
|
if (html !== __parseCacheMRU) { __parseCache.delete(html); __parseCache.set(html, hit); __parseCacheMRU = html; }
|
|
35
36
|
return hit;
|
|
36
37
|
}
|
|
37
|
-
let soa =
|
|
38
|
+
let soa = getParser().parseBuffer(html);
|
|
38
39
|
// Unpack the packed blob into typed-array views ONCE here, not per Document in
|
|
39
40
|
// every Buffer ctor — the views are read-only over the shared immutable buffer,
|
|
40
41
|
// so all Documents backed by this cached soa reuse them (no re-unpack per file).
|
|
@@ -65,6 +66,12 @@ function promoteDeclarativeShadowRoots(document) {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
export function createEnvironment(html = '<!doctype html><html><head></head><body></body></html>', options = {}) {
|
|
69
|
+
// Optional parser selection: options.parser === 'wasm' | 'native' | 'auto'. Applied
|
|
70
|
+
// before the first parse below; process-global (the parser binding is a singleton),
|
|
71
|
+
// so the last mode set wins. Embedders wanting a fully node-free path inject a
|
|
72
|
+
// binding via setParser()/globalThis.__TURBO_DOM_PARSER__ instead.
|
|
73
|
+
if (options.parser) setParserMode(options.parser);
|
|
74
|
+
|
|
68
75
|
// Layer 1: native parse → immutable SoA buffer (typed arrays, one boundary copy).
|
|
69
76
|
let currentHtml = String(html);
|
|
70
77
|
let soa = parseBufferCached(currentHtml);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Parser binding registry for the whole runtime (dom.mjs, index.mjs).
|
|
2
|
+
//
|
|
3
|
+
// Resolution order, lazily on the FIRST parse (getParser):
|
|
4
|
+
// 1. an explicitly injected binding — setParser(b) or globalThis.__TURBO_DOM_PARSER__.
|
|
5
|
+
// This is the node-free path: an embedder (e.g. a non-Node V8 host) instantiates
|
|
6
|
+
// the wasm itself and injects the {parse,parseBuffer,parseFragment} object, so
|
|
7
|
+
// NONE of the Node loaders below ever run.
|
|
8
|
+
// 2. an explicit mode — setParserMode(m), the `parser` option on createEnvironment,
|
|
9
|
+
// TURBO_DOM_PARSER env, or globalThis.__TURBO_DOM_PARSER_MODE__:
|
|
10
|
+
// 'wasm' loads pkg/, 'native' loads the prebuilt .node addon.
|
|
11
|
+
// 3. auto (default): native addon first, wasm fallback.
|
|
12
|
+
//
|
|
13
|
+
// node:module is imported via GUARDED top-level await, so this module still LOADS in
|
|
14
|
+
// a bare V8 with no Node builtins — provided a parser is injected before the first
|
|
15
|
+
// parse, the Node `require` loaders are never reached. (Node hosts get it normally.)
|
|
16
|
+
|
|
17
|
+
let _createRequire;
|
|
18
|
+
try { ({ createRequire: _createRequire } = await import('node:module')); } catch { /* bare V8: parser must be injected */ }
|
|
19
|
+
|
|
20
|
+
let _parser = null;
|
|
21
|
+
let _mode = null; // 'wasm' | 'native' | 'auto' | null (consult env/global)
|
|
22
|
+
|
|
23
|
+
function req(spec) {
|
|
24
|
+
if (!_createRequire) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'turbo-dom: no parser available — node:module is missing (bare V8) and nothing was injected. ' +
|
|
27
|
+
'Call setParser(binding) (or set globalThis.__TURBO_DOM_PARSER__) before the first parse.',
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return _createRequire(import.meta.url)(spec);
|
|
31
|
+
}
|
|
32
|
+
const loadNative = () => req('../../index.js');
|
|
33
|
+
const loadWasm = () => req('../../pkg/turbo_dom_parser.js');
|
|
34
|
+
|
|
35
|
+
// Inject a ready parser binding ({parse, parseBuffer, parseFragment}). Pass null to
|
|
36
|
+
// clear it (next getParser re-resolves). The node-free seam for embedders.
|
|
37
|
+
export function setParser(binding) { _parser = binding || null; }
|
|
38
|
+
|
|
39
|
+
// Force a parser mode: 'wasm' | 'native' | 'auto' (or null to consult env/global).
|
|
40
|
+
// Resets the resolved parser so the next parse honors the new mode.
|
|
41
|
+
export function setParserMode(mode) {
|
|
42
|
+
_mode = mode || null;
|
|
43
|
+
_parser = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveMode() {
|
|
47
|
+
if (_mode) return _mode;
|
|
48
|
+
const g = typeof globalThis !== 'undefined' ? globalThis : {};
|
|
49
|
+
if (g.__TURBO_DOM_PARSER_MODE__) return g.__TURBO_DOM_PARSER_MODE__;
|
|
50
|
+
if (typeof process !== 'undefined' && process.env && process.env.TURBO_DOM_PARSER) return process.env.TURBO_DOM_PARSER;
|
|
51
|
+
return 'auto';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Resolve (once) and return the active parser binding. Memoized — every call after
|
|
55
|
+
// the first is a single field read, so call sites can use getParser().parseBuffer(…)
|
|
56
|
+
// freely without caching it themselves.
|
|
57
|
+
export function getParser() {
|
|
58
|
+
if (_parser) return _parser;
|
|
59
|
+
const g = typeof globalThis !== 'undefined' ? globalThis : {};
|
|
60
|
+
if (g.__TURBO_DOM_PARSER__) return (_parser = g.__TURBO_DOM_PARSER__);
|
|
61
|
+
const mode = resolveMode();
|
|
62
|
+
if (mode === 'wasm') return (_parser = loadWasm());
|
|
63
|
+
if (mode === 'native') return (_parser = loadNative());
|
|
64
|
+
// auto: native addon first, wasm fallback; surface the native error if both fail.
|
|
65
|
+
try { _parser = loadNative(); }
|
|
66
|
+
catch (nativeErr) { try { _parser = loadWasm(); } catch { throw nativeErr; } }
|
|
67
|
+
return _parser;
|
|
68
|
+
}
|
package/src/runtime/window.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
makeLocation, makeHistory,
|
|
12
12
|
} from './stubs.mjs';
|
|
13
13
|
import { makeGetComputedStyle } from './cascade.mjs';
|
|
14
|
+
import { CSSStyleSheet } from './cssom.mjs';
|
|
14
15
|
import {
|
|
15
16
|
Node, Element, Text, Comment, Document, DocumentFragment, DocumentType, Event, CustomEvent,
|
|
16
17
|
MutationObserver, DOMParser, XMLSerializer, ShadowRoot,
|
|
@@ -205,16 +206,33 @@ function performanceNow() {
|
|
|
205
206
|
return nodePerformance.now();
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
// Resolve the host base classes ONCE, with minimal fallbacks so turbo-dom loads &
|
|
210
|
+
// runs in a bare V8 lacking web-platform globals. Where the platform provides the
|
|
211
|
+
// real class (Node/browser) we extend it and keep full behavior; the fallbacks are
|
|
212
|
+
// only enough to not throw at load and to round-trip the few fields turbo-dom uses.
|
|
213
|
+
// Embedders wanting full URL parsing / Blob semantics should still polyfill the
|
|
214
|
+
// host globals — these are honest minimums, not a spec implementation.
|
|
215
|
+
const URLBase = typeof URL !== 'undefined' ? URL : class URL {
|
|
216
|
+
constructor(url, base) { this.href = base ? `${base}${url}` : String(url); }
|
|
217
|
+
toString() { return this.href; }
|
|
218
|
+
get origin() { return ''; }
|
|
219
|
+
};
|
|
220
|
+
const BlobBase = typeof globalThis.Blob !== 'undefined' ? globalThis.Blob : class Blob {
|
|
221
|
+
constructor(parts = [], opts = {}) { this.__parts = parts; this.type = opts.type || ''; this.size = 0; }
|
|
222
|
+
async text() { return (this.__parts || []).join(''); }
|
|
223
|
+
async arrayBuffer() { return new ArrayBuffer(0); }
|
|
224
|
+
slice() { return new BlobBase(); }
|
|
225
|
+
};
|
|
226
|
+
|
|
208
227
|
let __objUrlSeq = 0;
|
|
209
228
|
function makeURL() {
|
|
210
|
-
class TurboURL extends
|
|
229
|
+
class TurboURL extends URLBase {}
|
|
211
230
|
TurboURL.createObjectURL = () => `blob:turbo-dom/${++__objUrlSeq}`;
|
|
212
231
|
TurboURL.revokeObjectURL = () => {};
|
|
213
232
|
return TurboURL;
|
|
214
233
|
}
|
|
215
234
|
function makeFile() {
|
|
216
|
-
|
|
217
|
-
return class File extends B {
|
|
235
|
+
return class File extends BlobBase {
|
|
218
236
|
constructor(bits = [], name = 'file', opts = {}) { super(bits, opts); this.name = String(name); this.lastModified = opts.lastModified || 0; }
|
|
219
237
|
};
|
|
220
238
|
}
|
|
@@ -317,9 +335,9 @@ const STATIC_BASE = {
|
|
|
317
335
|
HTMLDataElement: tagClass('data'), HTMLTimeElement: tagClass('time'),
|
|
318
336
|
HTMLSlotElement: tagClass('slot'), HTMLMenuElement: tagClass('menu'),
|
|
319
337
|
HTMLDocument: Document,
|
|
320
|
-
MutationObserver, DOMParser, XMLSerializer,
|
|
321
|
-
URL: TURBO_URL, URLSearchParams,
|
|
322
|
-
Blob:
|
|
338
|
+
MutationObserver, DOMParser, XMLSerializer, CSSStyleSheet,
|
|
339
|
+
URL: TURBO_URL, URLSearchParams: typeof URLSearchParams !== 'undefined' ? URLSearchParams : undefined,
|
|
340
|
+
Blob: BlobBase, File: TURBO_FILE, FileReader,
|
|
323
341
|
AbortController: globalThis.AbortController, AbortSignal: globalThis.AbortSignal,
|
|
324
342
|
TextEncoder: globalThis.TextEncoder, TextDecoder: globalThis.TextDecoder,
|
|
325
343
|
fetch: globalThis.fetch ? (...a) => globalThis.fetch(...a) : undefined,
|
|
Binary file
|
|
Binary file
|