@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/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
@@ -0,0 +1,4 @@
1
+ fn main() {
2
+ #[cfg(feature = "napi-bind")]
3
+ napi_build::setup();
4
+ }
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
+ }