@miaskiewicz/turbo-dom 0.1.2
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/Cargo.lock +588 -0
- package/Cargo.toml +34 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/build.rs +4 -0
- package/index.d.ts +62 -0
- package/index.js +318 -0
- package/package.json +121 -0
- package/src/core.rs +492 -0
- package/src/environment/install.mjs +34 -0
- package/src/environment/jest.cjs +40 -0
- package/src/environment/vitest.mjs +31 -0
- package/src/lib.rs +161 -0
- package/src/runtime/buffer.mjs +35 -0
- package/src/runtime/collections.mjs +50 -0
- package/src/runtime/dom.mjs +863 -0
- package/src/runtime/events.mjs +213 -0
- package/src/runtime/html-serialize.mjs +72 -0
- package/src/runtime/index.mjs +46 -0
- package/src/runtime/selectors.mjs +239 -0
- package/src/runtime/stubs.mjs +148 -0
- package/src/runtime/window.mjs +168 -0
- package/turbo-dom-parser.darwin-arm64.node +0 -0
- package/turbo-dom-parser.linux-arm64-gnu.node +0 -0
- package/turbo-dom-parser.linux-x64-gnu.node +0 -0
- package/turbo-dom-parser.linux-x64-musl.node +0 -0
- package/turbo-dom-parser.win32-x64-msvc.node +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# turbo-dom
|
|
2
|
+
|
|
3
|
+
A faster, more spec-correct DOM for test runners — a drop-in-style alternative to
|
|
4
|
+
**jsdom** and **happy-dom** for **vitest** and **jest**.
|
|
5
|
+
|
|
6
|
+
The HTML parser is native ([html5ever](https://github.com/servo/html5ever), Servo's
|
|
7
|
+
WHATWG tree constructor, via Rust/N-API with a WASM fallback). The DOM itself stays in
|
|
8
|
+
JavaScript but is **lazy** — nodes inflate from a compact typed-array buffer only when a
|
|
9
|
+
test touches them, and `window` globals materialize only on first use.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -D @miaskiewicz/turbo-dom
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- ✅ **More compatible than happy-dom** — 99.72% on html5lib-tests vs happy-dom's 37%.
|
|
16
|
+
Runs React Testing Library, `user-event`, downshift, Radix UI, and Headless UI unmodified.
|
|
17
|
+
- ⚡ **Faster than jsdom** — ~19× lower per-file setup, 11–39× faster HTML parsing.
|
|
18
|
+
- 🎯 **Honest, not lying** — no fake layout numbers; `getBoundingClientRect()` is zeros and
|
|
19
|
+
`getComputedStyle` reflects only what you set. Geometry tests belong in a real browser.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
### vitest
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// vitest.config.ts
|
|
27
|
+
import { defineConfig } from 'vitest/config';
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
test: {
|
|
31
|
+
environment: '@miaskiewicz/turbo-dom/environment/vitest',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### jest
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// jest.config.js
|
|
40
|
+
module.exports = {
|
|
41
|
+
testEnvironment: '@miaskiewicz/turbo-dom/jest',
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Now `document`, `window`, and friends are global in your tests — write them exactly like
|
|
46
|
+
you would against jsdom/happy-dom:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
import { render, screen } from '@testing-library/react';
|
|
50
|
+
import userEvent from '@testing-library/user-event';
|
|
51
|
+
|
|
52
|
+
test('counter increments', async () => {
|
|
53
|
+
render(<Counter />);
|
|
54
|
+
await userEvent.click(screen.getByRole('button'));
|
|
55
|
+
expect(screen.getByText('count: 1')).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Without a test runner
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { createEnvironment } from '@miaskiewicz/turbo-dom/runtime';
|
|
63
|
+
|
|
64
|
+
const env = createEnvironment('<!doctype html><body><div id="app"></div></body>');
|
|
65
|
+
env.document.querySelector('#app'); // nodes inflate lazily from the parse buffer
|
|
66
|
+
env.window.localStorage; // globals materialize on first touch
|
|
67
|
+
env.reset(); // fast per-file reset (reuses the parse buffer)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Just the parser
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const { parse, parseBuffer, parseFragment } = require('@miaskiewicz/turbo-dom');
|
|
74
|
+
|
|
75
|
+
parse('<div id=a><span>hi</span></div>'); // nested tree
|
|
76
|
+
parseBuffer('<div id=a>...</div>'); // compact SoA typed-array buffer
|
|
77
|
+
parseFragment('<rect/>', 'svg path'); // fragment in a context element
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Compatibility
|
|
81
|
+
|
|
82
|
+
| | turbo-dom | happy-dom | jsdom |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| html5lib-tests conformance | **99.72%** | 37.35% | 97.03% |
|
|
85
|
+
| @testing-library/dom + user-event | ✅ | ✅ | ✅ |
|
|
86
|
+
| React + Radix / Headless UI / downshift | ✅ | partial | ✅ |
|
|
87
|
+
| Real layout / `getComputedStyle` cascade | ❌ (honest stub) | partial | partial |
|
|
88
|
+
|
|
89
|
+
turbo-dom inherits Servo's tree constructor, so the "messy input" cases hand-rolled parsers
|
|
90
|
+
get wrong — adoption-agency reparenting (`<a><p></a></p>`), table foster-parenting, optional
|
|
91
|
+
end tags, `<template>` content, SVG/MathML — all match the spec. The 5 remaining
|
|
92
|
+
conformance misses are bleeding-edge `<select>`-family proposals upstream `html5ever` hasn't
|
|
93
|
+
adopted yet.
|
|
94
|
+
|
|
95
|
+
## Performance
|
|
96
|
+
|
|
97
|
+
Measured on darwin-arm64, Node 24 (`npm run bench:all`):
|
|
98
|
+
|
|
99
|
+
| benchmark | turbo-dom | happy-dom | jsdom |
|
|
100
|
+
|---|---:|---:|---:|
|
|
101
|
+
| per-file setup + 1 query (ops/s) | **6,808** | 526 | 266 |
|
|
102
|
+
| full suite, 200 files (ms/file) | **0.13** | 1.45 | 3.36 |
|
|
103
|
+
| parse 56 KB SSR (ops/s) | **502** | 46 | 23 |
|
|
104
|
+
| parse 20 KB real page (ops/s) | **3,912** | 230 | 100 |
|
|
105
|
+
|
|
106
|
+
Why it's fast: parsing is native; the JS DOM doesn't allocate node objects for parts of the
|
|
107
|
+
tree a test never reads; and `window` doesn't build the ~12 globals (storage, observers,
|
|
108
|
+
matchMedia…) a render-only test never touches.
|
|
109
|
+
|
|
110
|
+
## How it works
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
test code (RTL, user-event)
|
|
114
|
+
└─ lazy window (Proxy, self-replacing globals) ← JS
|
|
115
|
+
└─ lazy copy-on-write node tree (memoized identity) ← JS
|
|
116
|
+
└─ immutable Structure-of-Arrays parse buffer ← shared
|
|
117
|
+
└─ Rust: html5ever → flat typed-array buffer ← native (N-API / WASM)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The parser runs in Rust (compute-bound, one boundary crossing per parse). The DOM stays in
|
|
121
|
+
JS (chatty, fine-grained) but pays only for what a test touches. Full design notes:
|
|
122
|
+
[turbo-dom-spec.md](./turbo-dom-spec.md).
|
|
123
|
+
|
|
124
|
+
## Limitations (by design)
|
|
125
|
+
|
|
126
|
+
- **No layout.** `getBoundingClientRect()` returns zeros; `getClientRects()` is empty.
|
|
127
|
+
- **`getComputedStyle` is inline-only** — it reflects the `style` attribute and explicitly
|
|
128
|
+
set properties, never an invented cascade. Style/geometry assertions belong in a real
|
|
129
|
+
browser (Playwright/WebDriver).
|
|
130
|
+
- Canvas, `<select>` rendering, and similar visual APIs are honest no-op stubs.
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
Requires Node ≥ 18 and a Rust toolchain (`rustup`, stable).
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm install
|
|
138
|
+
npm run build # native addon (.node)
|
|
139
|
+
npm test # JS suite (unit, conformance, differential, gauntlets)
|
|
140
|
+
npm run test:rust # Rust core tests
|
|
141
|
+
npm run conformance # html5lib-tests report
|
|
142
|
+
npm run bench:all # benchmarks
|
|
143
|
+
npm run build:wasm # wasm32 fallback
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Contributions welcome — issues and PRs at
|
|
147
|
+
[github.com/miaskiewicz/turbo-dom](https://github.com/miaskiewicz/turbo-dom).
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
[MIT](./LICENSE).
|
package/build.rs
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/* auto-generated by NAPI-RS */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Marshaled node returned to JS. Mirrors `core::Node` as a napi object so the
|
|
8
|
+
* whole tree crosses the boundary in one return value (full-marshaling mode).
|
|
9
|
+
*/
|
|
10
|
+
export interface JsNode {
|
|
11
|
+
nodeType: number
|
|
12
|
+
name: string
|
|
13
|
+
value: string
|
|
14
|
+
namespace: string
|
|
15
|
+
publicId: string
|
|
16
|
+
systemId: string
|
|
17
|
+
attrs: Array<JsAttr>
|
|
18
|
+
children: Array<JsNode>
|
|
19
|
+
}
|
|
20
|
+
export interface JsAttr {
|
|
21
|
+
name: string
|
|
22
|
+
value: string
|
|
23
|
+
prefix: string
|
|
24
|
+
}
|
|
25
|
+
/** Parse a full HTML document. Boundary crossed exactly once. */
|
|
26
|
+
export declare function parse(html: string): JsNode
|
|
27
|
+
/**
|
|
28
|
+
* Parse-only: returns node count, builds no JS tree. For isolating raw parse
|
|
29
|
+
* cost from tree-build + boundary marshaling in benchmarks.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseRaw(html: string): number
|
|
32
|
+
/**
|
|
33
|
+
* SoA flat buffer: structure as typed arrays, crossed once. JS inflates node
|
|
34
|
+
* objects lazily from this — no eager full-tree allocation. The fast path.
|
|
35
|
+
*/
|
|
36
|
+
export interface JsSoa {
|
|
37
|
+
nodeType: Uint8Array
|
|
38
|
+
ns: Uint8Array
|
|
39
|
+
tagId: Uint32Array
|
|
40
|
+
parent: Int32Array
|
|
41
|
+
firstChild: Int32Array
|
|
42
|
+
nextSib: Int32Array
|
|
43
|
+
textId: Int32Array
|
|
44
|
+
pubId: Int32Array
|
|
45
|
+
sysId: Int32Array
|
|
46
|
+
attrStart: Int32Array
|
|
47
|
+
attrCount: Uint16Array
|
|
48
|
+
attrNameId: Uint32Array
|
|
49
|
+
attrValue: Array<string>
|
|
50
|
+
attrPrefixId: Uint32Array
|
|
51
|
+
tagNames: Array<string>
|
|
52
|
+
attrNames: Array<string>
|
|
53
|
+
attrPrefixes: Array<string>
|
|
54
|
+
strings: Array<string>
|
|
55
|
+
}
|
|
56
|
+
/** Parse a document into the SoA flat buffer (the fast runtime path). */
|
|
57
|
+
export declare function parseBuffer(html: string): JsSoa
|
|
58
|
+
/**
|
|
59
|
+
* Parse an HTML fragment (innerHTML-style). `context` is the context element:
|
|
60
|
+
* e.g. "body" (default), "td", or namespaced "svg path" / "math ms".
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseFragment(html: string, context?: string | undefined | null): JsNode
|
package/index.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* prettier-ignore */
|
|
4
|
+
|
|
5
|
+
/* auto-generated by NAPI-RS */
|
|
6
|
+
|
|
7
|
+
const { existsSync, readFileSync } = require('fs')
|
|
8
|
+
const { join } = require('path')
|
|
9
|
+
|
|
10
|
+
const { platform, arch } = process
|
|
11
|
+
|
|
12
|
+
let nativeBinding = null
|
|
13
|
+
let localFileExisted = false
|
|
14
|
+
let loadError = null
|
|
15
|
+
|
|
16
|
+
function isMusl() {
|
|
17
|
+
// For Node 10
|
|
18
|
+
if (!process.report || typeof process.report.getReport !== 'function') {
|
|
19
|
+
try {
|
|
20
|
+
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
|
21
|
+
return readFileSync(lddPath, 'utf8').includes('musl')
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
const { glibcVersionRuntime } = process.report.getReport().header
|
|
27
|
+
return !glibcVersionRuntime
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (platform) {
|
|
32
|
+
case 'android':
|
|
33
|
+
switch (arch) {
|
|
34
|
+
case 'arm64':
|
|
35
|
+
localFileExisted = existsSync(join(__dirname, 'turbo-dom-parser.android-arm64.node'))
|
|
36
|
+
try {
|
|
37
|
+
if (localFileExisted) {
|
|
38
|
+
nativeBinding = require('./turbo-dom-parser.android-arm64.node')
|
|
39
|
+
} else {
|
|
40
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-android-arm64')
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
loadError = e
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
case 'arm':
|
|
47
|
+
localFileExisted = existsSync(join(__dirname, 'turbo-dom-parser.android-arm-eabi.node'))
|
|
48
|
+
try {
|
|
49
|
+
if (localFileExisted) {
|
|
50
|
+
nativeBinding = require('./turbo-dom-parser.android-arm-eabi.node')
|
|
51
|
+
} else {
|
|
52
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-android-arm-eabi')
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
loadError = e
|
|
56
|
+
}
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
throw new Error(`Unsupported architecture on Android ${arch}`)
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
case 'win32':
|
|
63
|
+
switch (arch) {
|
|
64
|
+
case 'x64':
|
|
65
|
+
localFileExisted = existsSync(
|
|
66
|
+
join(__dirname, 'turbo-dom-parser.win32-x64-msvc.node')
|
|
67
|
+
)
|
|
68
|
+
try {
|
|
69
|
+
if (localFileExisted) {
|
|
70
|
+
nativeBinding = require('./turbo-dom-parser.win32-x64-msvc.node')
|
|
71
|
+
} else {
|
|
72
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-win32-x64-msvc')
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
loadError = e
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
case 'ia32':
|
|
79
|
+
localFileExisted = existsSync(
|
|
80
|
+
join(__dirname, 'turbo-dom-parser.win32-ia32-msvc.node')
|
|
81
|
+
)
|
|
82
|
+
try {
|
|
83
|
+
if (localFileExisted) {
|
|
84
|
+
nativeBinding = require('./turbo-dom-parser.win32-ia32-msvc.node')
|
|
85
|
+
} else {
|
|
86
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-win32-ia32-msvc')
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
loadError = e
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
case 'arm64':
|
|
93
|
+
localFileExisted = existsSync(
|
|
94
|
+
join(__dirname, 'turbo-dom-parser.win32-arm64-msvc.node')
|
|
95
|
+
)
|
|
96
|
+
try {
|
|
97
|
+
if (localFileExisted) {
|
|
98
|
+
nativeBinding = require('./turbo-dom-parser.win32-arm64-msvc.node')
|
|
99
|
+
} else {
|
|
100
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-win32-arm64-msvc')
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
loadError = e
|
|
104
|
+
}
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
|
108
|
+
}
|
|
109
|
+
break
|
|
110
|
+
case 'darwin':
|
|
111
|
+
localFileExisted = existsSync(join(__dirname, 'turbo-dom-parser.darwin-universal.node'))
|
|
112
|
+
try {
|
|
113
|
+
if (localFileExisted) {
|
|
114
|
+
nativeBinding = require('./turbo-dom-parser.darwin-universal.node')
|
|
115
|
+
} else {
|
|
116
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-darwin-universal')
|
|
117
|
+
}
|
|
118
|
+
break
|
|
119
|
+
} catch {}
|
|
120
|
+
switch (arch) {
|
|
121
|
+
case 'x64':
|
|
122
|
+
localFileExisted = existsSync(join(__dirname, 'turbo-dom-parser.darwin-x64.node'))
|
|
123
|
+
try {
|
|
124
|
+
if (localFileExisted) {
|
|
125
|
+
nativeBinding = require('./turbo-dom-parser.darwin-x64.node')
|
|
126
|
+
} else {
|
|
127
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-darwin-x64')
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
loadError = e
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
case 'arm64':
|
|
134
|
+
localFileExisted = existsSync(
|
|
135
|
+
join(__dirname, 'turbo-dom-parser.darwin-arm64.node')
|
|
136
|
+
)
|
|
137
|
+
try {
|
|
138
|
+
if (localFileExisted) {
|
|
139
|
+
nativeBinding = require('./turbo-dom-parser.darwin-arm64.node')
|
|
140
|
+
} else {
|
|
141
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-darwin-arm64')
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
loadError = e
|
|
145
|
+
}
|
|
146
|
+
break
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
case 'freebsd':
|
|
152
|
+
if (arch !== 'x64') {
|
|
153
|
+
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
|
154
|
+
}
|
|
155
|
+
localFileExisted = existsSync(join(__dirname, 'turbo-dom-parser.freebsd-x64.node'))
|
|
156
|
+
try {
|
|
157
|
+
if (localFileExisted) {
|
|
158
|
+
nativeBinding = require('./turbo-dom-parser.freebsd-x64.node')
|
|
159
|
+
} else {
|
|
160
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-freebsd-x64')
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
loadError = e
|
|
164
|
+
}
|
|
165
|
+
break
|
|
166
|
+
case 'linux':
|
|
167
|
+
switch (arch) {
|
|
168
|
+
case 'x64':
|
|
169
|
+
if (isMusl()) {
|
|
170
|
+
localFileExisted = existsSync(
|
|
171
|
+
join(__dirname, 'turbo-dom-parser.linux-x64-musl.node')
|
|
172
|
+
)
|
|
173
|
+
try {
|
|
174
|
+
if (localFileExisted) {
|
|
175
|
+
nativeBinding = require('./turbo-dom-parser.linux-x64-musl.node')
|
|
176
|
+
} else {
|
|
177
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-x64-musl')
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
loadError = e
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
localFileExisted = existsSync(
|
|
184
|
+
join(__dirname, 'turbo-dom-parser.linux-x64-gnu.node')
|
|
185
|
+
)
|
|
186
|
+
try {
|
|
187
|
+
if (localFileExisted) {
|
|
188
|
+
nativeBinding = require('./turbo-dom-parser.linux-x64-gnu.node')
|
|
189
|
+
} else {
|
|
190
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-x64-gnu')
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
loadError = e
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
case 'arm64':
|
|
198
|
+
if (isMusl()) {
|
|
199
|
+
localFileExisted = existsSync(
|
|
200
|
+
join(__dirname, 'turbo-dom-parser.linux-arm64-musl.node')
|
|
201
|
+
)
|
|
202
|
+
try {
|
|
203
|
+
if (localFileExisted) {
|
|
204
|
+
nativeBinding = require('./turbo-dom-parser.linux-arm64-musl.node')
|
|
205
|
+
} else {
|
|
206
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-arm64-musl')
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
loadError = e
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
localFileExisted = existsSync(
|
|
213
|
+
join(__dirname, 'turbo-dom-parser.linux-arm64-gnu.node')
|
|
214
|
+
)
|
|
215
|
+
try {
|
|
216
|
+
if (localFileExisted) {
|
|
217
|
+
nativeBinding = require('./turbo-dom-parser.linux-arm64-gnu.node')
|
|
218
|
+
} else {
|
|
219
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-arm64-gnu')
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
loadError = e
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break
|
|
226
|
+
case 'arm':
|
|
227
|
+
if (isMusl()) {
|
|
228
|
+
localFileExisted = existsSync(
|
|
229
|
+
join(__dirname, 'turbo-dom-parser.linux-arm-musleabihf.node')
|
|
230
|
+
)
|
|
231
|
+
try {
|
|
232
|
+
if (localFileExisted) {
|
|
233
|
+
nativeBinding = require('./turbo-dom-parser.linux-arm-musleabihf.node')
|
|
234
|
+
} else {
|
|
235
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-arm-musleabihf')
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
loadError = e
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
localFileExisted = existsSync(
|
|
242
|
+
join(__dirname, 'turbo-dom-parser.linux-arm-gnueabihf.node')
|
|
243
|
+
)
|
|
244
|
+
try {
|
|
245
|
+
if (localFileExisted) {
|
|
246
|
+
nativeBinding = require('./turbo-dom-parser.linux-arm-gnueabihf.node')
|
|
247
|
+
} else {
|
|
248
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-arm-gnueabihf')
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
loadError = e
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break
|
|
255
|
+
case 'riscv64':
|
|
256
|
+
if (isMusl()) {
|
|
257
|
+
localFileExisted = existsSync(
|
|
258
|
+
join(__dirname, 'turbo-dom-parser.linux-riscv64-musl.node')
|
|
259
|
+
)
|
|
260
|
+
try {
|
|
261
|
+
if (localFileExisted) {
|
|
262
|
+
nativeBinding = require('./turbo-dom-parser.linux-riscv64-musl.node')
|
|
263
|
+
} else {
|
|
264
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-riscv64-musl')
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
loadError = e
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
localFileExisted = existsSync(
|
|
271
|
+
join(__dirname, 'turbo-dom-parser.linux-riscv64-gnu.node')
|
|
272
|
+
)
|
|
273
|
+
try {
|
|
274
|
+
if (localFileExisted) {
|
|
275
|
+
nativeBinding = require('./turbo-dom-parser.linux-riscv64-gnu.node')
|
|
276
|
+
} else {
|
|
277
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-riscv64-gnu')
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
loadError = e
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
break
|
|
284
|
+
case 's390x':
|
|
285
|
+
localFileExisted = existsSync(
|
|
286
|
+
join(__dirname, 'turbo-dom-parser.linux-s390x-gnu.node')
|
|
287
|
+
)
|
|
288
|
+
try {
|
|
289
|
+
if (localFileExisted) {
|
|
290
|
+
nativeBinding = require('./turbo-dom-parser.linux-s390x-gnu.node')
|
|
291
|
+
} else {
|
|
292
|
+
nativeBinding = require('@miaskiewicz/turbo-dom-linux-s390x-gnu')
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
loadError = e
|
|
296
|
+
}
|
|
297
|
+
break
|
|
298
|
+
default:
|
|
299
|
+
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
|
300
|
+
}
|
|
301
|
+
break
|
|
302
|
+
default:
|
|
303
|
+
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!nativeBinding) {
|
|
307
|
+
if (loadError) {
|
|
308
|
+
throw loadError
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Failed to load native binding`)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { parse, parseRaw, parseBuffer, parseFragment } = nativeBinding
|
|
314
|
+
|
|
315
|
+
module.exports.parse = parse
|
|
316
|
+
module.exports.parseRaw = parseRaw
|
|
317
|
+
module.exports.parseBuffer = parseBuffer
|
|
318
|
+
module.exports.parseFragment = parseFragment
|
package/package.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@miaskiewicz/turbo-dom",
|
|
3
|
+
"version": "0.1.2",
|
|
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
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./index.d.ts",
|
|
10
|
+
"import": "./index.js",
|
|
11
|
+
"require": "./index.js"
|
|
12
|
+
},
|
|
13
|
+
"./runtime": {
|
|
14
|
+
"types": "./src/runtime/index.d.ts",
|
|
15
|
+
"import": "./src/runtime/index.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./environment/vitest": {
|
|
18
|
+
"import": "./src/environment/vitest.mjs"
|
|
19
|
+
},
|
|
20
|
+
"./jest": {
|
|
21
|
+
"require": "./src/environment/jest.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"index.js",
|
|
27
|
+
"index.d.ts",
|
|
28
|
+
"*.node",
|
|
29
|
+
"src/**/*.mjs",
|
|
30
|
+
"src/**/*.cjs",
|
|
31
|
+
"src/**/*.d.ts",
|
|
32
|
+
"src/*.rs",
|
|
33
|
+
"build.rs",
|
|
34
|
+
"Cargo.toml",
|
|
35
|
+
"Cargo.lock",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"napi": {
|
|
40
|
+
"name": "turbo-dom-parser",
|
|
41
|
+
"triples": {
|
|
42
|
+
"defaults": true,
|
|
43
|
+
"additional": [
|
|
44
|
+
"x86_64-unknown-linux-gnu",
|
|
45
|
+
"x86_64-unknown-linux-musl",
|
|
46
|
+
"aarch64-unknown-linux-gnu",
|
|
47
|
+
"aarch64-apple-darwin",
|
|
48
|
+
"x86_64-pc-windows-msvc"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "napi build --platform --release",
|
|
54
|
+
"build:debug": "napi build --platform",
|
|
55
|
+
"build:wasm": "cargo build --release --no-default-features --features wasm-bind --target wasm32-unknown-unknown",
|
|
56
|
+
"build:wasm:pkg": "wasm-pack build --target nodejs --out-dir pkg --no-default-features --features wasm-bind",
|
|
57
|
+
"test": "node --test 'test/*.mjs'",
|
|
58
|
+
"test:rust": "cargo test --lib",
|
|
59
|
+
"test:all": "npm run build && npm run test:rust && npm test",
|
|
60
|
+
"conformance": "node harness/conformance.mjs",
|
|
61
|
+
"conformance:delta": "node harness/delta.mjs",
|
|
62
|
+
"bench": "node bench/parse.mjs",
|
|
63
|
+
"bench:construct": "node bench/construct.mjs",
|
|
64
|
+
"bench:suite": "node bench/suite.mjs",
|
|
65
|
+
"bench:wasm": "node bench/wasm.mjs",
|
|
66
|
+
"bench:all": "node bench/parse.mjs && node bench/construct.mjs && node bench/suite.mjs && node bench/wasm.mjs",
|
|
67
|
+
"prepublishOnly": "napi build --platform --release"
|
|
68
|
+
},
|
|
69
|
+
"keywords": [
|
|
70
|
+
"dom",
|
|
71
|
+
"html",
|
|
72
|
+
"html5ever",
|
|
73
|
+
"parser",
|
|
74
|
+
"jsdom",
|
|
75
|
+
"happy-dom",
|
|
76
|
+
"vitest",
|
|
77
|
+
"jest",
|
|
78
|
+
"test-environment",
|
|
79
|
+
"testing",
|
|
80
|
+
"napi",
|
|
81
|
+
"wasm"
|
|
82
|
+
],
|
|
83
|
+
"repository": {
|
|
84
|
+
"type": "git",
|
|
85
|
+
"url": "git+https://github.com/miaskiewicz/turbo-dom.git"
|
|
86
|
+
},
|
|
87
|
+
"homepage": "https://github.com/miaskiewicz/turbo-dom#readme",
|
|
88
|
+
"bugs": {
|
|
89
|
+
"url": "https://github.com/miaskiewicz/turbo-dom/issues"
|
|
90
|
+
},
|
|
91
|
+
"author": "miaskiewicz",
|
|
92
|
+
"engines": {
|
|
93
|
+
"node": ">=18"
|
|
94
|
+
},
|
|
95
|
+
"peerDependencies": {
|
|
96
|
+
"jest-environment-node": ">=29",
|
|
97
|
+
"vitest": ">=1"
|
|
98
|
+
},
|
|
99
|
+
"peerDependenciesMeta": {
|
|
100
|
+
"jest-environment-node": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"vitest": {
|
|
104
|
+
"optional": true
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"devDependencies": {
|
|
108
|
+
"@headlessui/react": "^2.2.10",
|
|
109
|
+
"@napi-rs/cli": "^2.18.4",
|
|
110
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
111
|
+
"@testing-library/dom": "^10.4.1",
|
|
112
|
+
"@testing-library/react": "^16.3.2",
|
|
113
|
+
"@testing-library/user-event": "^14.6.1",
|
|
114
|
+
"downshift": "^9.3.6",
|
|
115
|
+
"happy-dom": "^20.10.1",
|
|
116
|
+
"jsdom": "^29.1.1",
|
|
117
|
+
"parse5": "^8.0.1",
|
|
118
|
+
"react": "^19.2.7",
|
|
119
|
+
"react-dom": "^19.2.7"
|
|
120
|
+
}
|
|
121
|
+
}
|