@miaskiewicz/turbo-dom 0.2.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miaskiewicz/turbo-dom",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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",
@@ -845,6 +845,13 @@ export class Element extends Node {
845
845
  hasAttributeNS(_ns, name) { return this.hasAttribute(name); }
846
846
  removeAttributeNS(_ns, name) { this.removeAttribute(name); }
847
847
  getAttributeNode(name) { const a = (this.__attrs ?? (this.__attrs = this.__buildAttrs())).find((x) => x.name === name); return a ? { name: a.name, value: a.value, ownerElement: this } : null; }
848
+ getAttributeNodeNS(_ns, name) { return this.getAttributeNode(name); }
849
+ // Attr-node methods are thin shims over the name-based API (no real Attr/NamedNodeMap).
850
+ // React 19's releaseSingletonInstance reads node.attributes (a detached array copy) then
851
+ // removeAttributeNode(attr)s each — safe because the iterated copy isn't __attrs.
852
+ setAttributeNode(attr) { const prev = this.getAttributeNode(attr.name); this.setAttribute(attr.name, attr.value); return prev; }
853
+ setAttributeNodeNS(attr) { return this.setAttributeNode(attr); }
854
+ removeAttributeNode(attr) { const removed = this.getAttributeNode(attr && attr.name); if (attr && attr.name != null) this.removeAttribute(attr.name); return removed; }
848
855
 
849
856
  // adjacency
850
857
  insertAdjacentElement(position, el) {
@@ -201,10 +201,49 @@ export function createWindow(document, { url = 'http://localhost/' } = {}) {
201
201
  };
202
202
  }
203
203
 
204
- import { performance as nodePerformance } from 'node:perf_hooks';
205
- function performanceNow() {
206
- return nodePerformance.now();
207
- }
204
+ // No static `node:` import — the runtime must load in a bare V8 isolate (no Node,
205
+ // no web-platform globals). Capture the HOST `performance` at module load, BEFORE
206
+ // turbo-dom installs its own `performance` global (else performanceNow → installed
207
+ // performance.now → performanceNow → ∞). Falls back to Date.now() in a bare V8.
208
+ const hostPerformance = globalThis.performance;
209
+ const performanceNow = typeof hostPerformance?.now === 'function'
210
+ ? () => hostPerformance.now()
211
+ : () => Date.now();
212
+
213
+ // base64 without depending on the Node `Buffer` global — prefer the platform
214
+ // btoa/atob, then Buffer (Node), then a pure-JS fallback so a bare-isolate page
215
+ // that calls btoa/atob doesn't throw.
216
+ const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
217
+ const turboBtoa = typeof globalThis.btoa === 'function'
218
+ ? (s) => globalThis.btoa(s)
219
+ : typeof Buffer !== 'undefined'
220
+ ? (s) => Buffer.from(String(s), 'binary').toString('base64')
221
+ : (s) => {
222
+ const str = String(s); let out = '';
223
+ for (let i = 0; i < str.length;) {
224
+ const c1 = str.charCodeAt(i++), c2 = str.charCodeAt(i++), c3 = str.charCodeAt(i++);
225
+ const e1 = c1 >> 2, e2 = ((c1 & 3) << 4) | (c2 >> 4);
226
+ let e3 = ((c2 & 15) << 2) | (c3 >> 6), e4 = c3 & 63;
227
+ if (isNaN(c2)) { e3 = e4 = 64; } else if (isNaN(c3)) { e4 = 64; }
228
+ out += B64[e1] + B64[e2] + (e3 === 64 ? '=' : B64[e3]) + (e4 === 64 ? '=' : B64[e4]);
229
+ }
230
+ return out;
231
+ };
232
+ const turboAtob = typeof globalThis.atob === 'function'
233
+ ? (s) => globalThis.atob(s)
234
+ : typeof Buffer !== 'undefined'
235
+ ? (s) => Buffer.from(String(s), 'base64').toString('binary')
236
+ : (s) => {
237
+ const str = String(s).replace(/[^A-Za-z0-9+/]/g, ''); let out = '';
238
+ for (let i = 0; i < str.length;) {
239
+ const e1 = B64.indexOf(str[i++]), e2 = B64.indexOf(str[i++]);
240
+ const e3 = B64.indexOf(str[i++]), e4 = B64.indexOf(str[i++]);
241
+ out += String.fromCharCode((e1 << 2) | (e2 >> 4));
242
+ if (e3 !== -1 && e3 !== 64) out += String.fromCharCode(((e2 & 15) << 4) | (e3 >> 2));
243
+ if (e4 !== -1 && e4 !== 64) out += String.fromCharCode(((e3 & 3) << 6) | e4);
244
+ }
245
+ return out;
246
+ };
208
247
 
209
248
  // Resolve the host base classes ONCE, with minimal fallbacks so turbo-dom loads &
210
249
  // runs in a bare V8 lacking web-platform globals. Where the platform provides the
@@ -344,8 +383,8 @@ const STATIC_BASE = {
344
383
  Headers: globalThis.Headers, Request: globalThis.Request, Response: globalThis.Response,
345
384
  FormData: TurboFormData, ReadableStream: globalThis.ReadableStream,
346
385
  crypto: globalThis.crypto, Crypto: globalThis.Crypto, SubtleCrypto: globalThis.SubtleCrypto,
347
- btoa: (s) => Buffer.from(String(s), 'binary').toString('base64'),
348
- atob: (s) => Buffer.from(String(s), 'base64').toString('binary'),
386
+ btoa: (s) => turboBtoa(String(s)),
387
+ atob: (s) => turboAtob(String(s)),
349
388
  MessageChannel: globalThis.MessageChannel, MessagePort: globalThis.MessagePort,
350
389
  BroadcastChannel: globalThis.BroadcastChannel, EventSource: globalThis.EventSource,
351
390
  reportError: (e) => { void e; },