@miaskiewicz/turbo-dom 0.1.33 → 0.1.35

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.1.33",
3
+ "version": "0.1.35",
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",
@@ -363,6 +363,16 @@ export class Element extends Node {
363
363
  this.__attrIdx = -1; // buffer index for lazy attr inflation
364
364
  this.content = null; // <template> content fragment
365
365
  this.shadowRoot = null; // open shadow root, if attached
366
+ // Predeclare the hot lazy fields (left at their "absent" sentinel) so every
367
+ // Element shares ONE hidden class. Without this, fields added on demand in
368
+ // varying order across elements make the shared query/match/style/event code
369
+ // megamorphic on property loads. Costs a few in-object slots, buys monomorphism.
370
+ this.__childNodesList = undefined; // childNodes NodeList (memoized live view)
371
+ this.__childrenList = undefined; // children HTMLCollection (memoized live view)
372
+ this.__qaCache = undefined; // querySelectorAll cache (selector → {v,r})
373
+ this.__qsCache = undefined; // querySelector cache
374
+ this.__computedStyle = undefined; // getComputedStyle snapshot (version-keyed)
375
+ this.__shadow = undefined; // attached shadow root (open or closed)
366
376
  }
367
377
 
368
378
  get nodeType() { return ELEMENT_NODE; }
@@ -79,6 +79,41 @@ function tagClass(matcher) {
79
79
  // the bare names on globalThis (which would make these delegates call themselves).
80
80
  const hostSetTimeout = globalThis.setTimeout;
81
81
  const hostClearTimeout = globalThis.clearTimeout;
82
+
83
+ // Env-independent lazy global factories — built ONCE at module load, shared by
84
+ // every window (none capture document/url/windowProxy). Materialization still
85
+ // self-replaces onto the per-env base, so each env gets its own instance; only
86
+ // the factory objects/closures are shared, saving ~28 allocations per test file.
87
+ const SHARED_LAZY = {
88
+ localStorage: () => new Storage(),
89
+ sessionStorage: () => new Storage(),
90
+ matchMedia: () => makeMatchMedia(),
91
+ getComputedStyle: () => makeGetComputedStyle(),
92
+ IntersectionObserver: () => IntersectionObserver,
93
+ ResizeObserver: () => ResizeObserver,
94
+ requestAnimationFrame: () => (cb) => hostSetTimeout(() => cb(performanceNow()), 0),
95
+ cancelAnimationFrame: () => (id) => hostClearTimeout(id),
96
+ navigator: () => ({
97
+ userAgent: 'Mozilla/5.0 (turbo-dom) AppleWebKit/537.36',
98
+ platform: 'turbo-dom', vendor: '', language: 'en-US', languages: ['en-US'],
99
+ onLine: true, cookieEnabled: true, doNotTrack: null, maxTouchPoints: 0,
100
+ hardwareConcurrency: 4, deviceMemory: 8, webdriver: false,
101
+ clipboard: { readText: async () => '', writeText: async () => {}, read: async () => [], write: async () => {} },
102
+ permissions: { query: async () => ({ state: 'prompt', addEventListener() {}, removeEventListener() {} }) },
103
+ sendBeacon: () => true, vibrate: () => false,
104
+ }),
105
+ performance: () => ({ now: performanceNow, timeOrigin: 0, mark() {}, measure() {}, getEntriesByName: () => [], getEntriesByType: () => [], clearMarks() {}, clearMeasures() {} }),
106
+ Storage: () => Storage,
107
+ devicePixelRatio: () => 1,
108
+ innerWidth: () => 1024,
109
+ innerHeight: () => 768,
110
+ outerWidth: () => 1024,
111
+ outerHeight: () => 768,
112
+ scrollX: () => 0, scrollY: () => 0, pageXOffset: () => 0, pageYOffset: () => 0,
113
+ screenX: () => 0, screenY: () => 0, screenLeft: () => 0, screenTop: () => 0,
114
+ screen: () => ({ width: 1024, height: 768, availWidth: 1024, availHeight: 768, colorDepth: 24, pixelDepth: 24, orientation: { type: 'landscape-primary', angle: 0, addEventListener() {}, removeEventListener() {} } }),
115
+ visualViewport: () => ({ width: 1024, height: 768, scale: 1, offsetLeft: 0, offsetTop: 0, pageLeft: 0, pageTop: 0, addEventListener() {}, removeEventListener() {} }),
116
+ };
82
117
  const hostSetInterval = globalThis.setInterval;
83
118
  const hostClearInterval = globalThis.clearInterval;
84
119
  const hostQueueMicrotask = globalThis.queueMicrotask;
@@ -107,39 +142,13 @@ export function createWindow(document, { url = 'http://localhost/' } = {}) {
107
142
  removeEventListener: (...a) => document.removeEventListener(...a),
108
143
  };
109
144
 
110
- // Lazy globals none constructed until touched.
145
+ // Per-env lazy globals: ONLY those that capture document/url/windowProxy.
146
+ // Everything env-independent lives in the module-level SHARED_LAZY (built once,
147
+ // not re-allocated per test file) — see below. None constructed until touched.
111
148
  const lazy = {
112
- localStorage: () => new Storage(),
113
- sessionStorage: () => new Storage(),
114
- matchMedia: () => makeMatchMedia(),
115
- getComputedStyle: () => makeGetComputedStyle(),
116
- IntersectionObserver: () => IntersectionObserver,
117
- ResizeObserver: () => ResizeObserver,
118
- requestAnimationFrame: () => (cb) => hostSetTimeout(() => cb(performanceNow()), 0),
119
- cancelAnimationFrame: () => (id) => hostClearTimeout(id),
120
149
  // subsystem grouping: history co-materializes with (and shares) location
121
150
  location: () => makeLocation(url),
122
151
  history: () => makeHistory(windowProxy.location),
123
- navigator: () => ({
124
- userAgent: 'Mozilla/5.0 (turbo-dom) AppleWebKit/537.36',
125
- platform: 'turbo-dom', vendor: '', language: 'en-US', languages: ['en-US'],
126
- onLine: true, cookieEnabled: true, doNotTrack: null, maxTouchPoints: 0,
127
- hardwareConcurrency: 4, deviceMemory: 8, webdriver: false,
128
- clipboard: { readText: async () => '', writeText: async () => {}, read: async () => [], write: async () => {} },
129
- permissions: { query: async () => ({ state: 'prompt', addEventListener() {}, removeEventListener() {} }) },
130
- sendBeacon: () => true, vibrate: () => false,
131
- }),
132
- performance: () => ({ now: performanceNow, timeOrigin: 0, mark() {}, measure() {}, getEntriesByName: () => [], getEntriesByType: () => [], clearMarks() {}, clearMeasures() {} }),
133
- Storage: () => Storage,
134
- devicePixelRatio: () => 1,
135
- innerWidth: () => 1024,
136
- innerHeight: () => 768,
137
- outerWidth: () => 1024,
138
- outerHeight: () => 768,
139
- scrollX: () => 0, scrollY: () => 0, pageXOffset: () => 0, pageYOffset: () => 0,
140
- screenX: () => 0, screenY: () => 0, screenLeft: () => 0, screenTop: () => 0,
141
- screen: () => ({ width: 1024, height: 768, availWidth: 1024, availHeight: 768, colorDepth: 24, pixelDepth: 24, orientation: { type: 'landscape-primary', angle: 0, addEventListener() {}, removeEventListener() {} } }),
142
- visualViewport: () => ({ width: 1024, height: 768, scale: 1, offsetLeft: 0, offsetTop: 0, pageLeft: 0, pageTop: 0, addEventListener() {}, removeEventListener() {} }),
143
152
  };
144
153
 
145
154
  windowProxy = new Proxy(base, {
@@ -147,7 +156,7 @@ export function createWindow(document, { url = 'http://localhost/' } = {}) {
147
156
  if (k === 'window' || k === 'self' || k === 'globalThis' || k === 'parent' || k === 'top') return windowProxy;
148
157
  if (k in t) return t[k]; // per-env (incl. overrides + materialized lazy)
149
158
  if (k in STATIC_BASE) return STATIC_BASE[k]; // shared stateless globals
150
- const factory = lazy[k];
159
+ const factory = lazy[k] || SHARED_LAZY[k];
151
160
  if (factory) {
152
161
  touched.add(k);
153
162
  const v = factory();
@@ -158,7 +167,7 @@ export function createWindow(document, { url = 'http://localhost/' } = {}) {
158
167
  },
159
168
  set(t, k, v) { t[k] = v; return true; }, // writes to base → shadows STATIC_BASE per-env
160
169
  has(t, k) {
161
- return k in t || k in STATIC_BASE || k in lazy ||
170
+ return k in t || k in STATIC_BASE || k in lazy || k in SHARED_LAZY ||
162
171
  k === 'window' || k === 'self' || k === 'globalThis' || k === 'parent' || k === 'top';
163
172
  },
164
173
  // so vi.spyOn(window, 'scrollTo'/'open'/…) finds STATIC_BASE methods as own
@@ -178,7 +187,7 @@ export function createWindow(document, { url = 'http://localhost/' } = {}) {
178
187
  // which lazy globals this test materialized (the "DOM surface used" report)
179
188
  touched: () => [...touched],
180
189
  // every global name this window can provide (for environment adapters)
181
- globalKeys: [...Object.keys(base), ...Object.keys(STATIC_BASE), ...Object.keys(lazy)],
190
+ globalKeys: [...Object.keys(base), ...Object.keys(STATIC_BASE), ...Object.keys(lazy), ...Object.keys(SHARED_LAZY)],
182
191
  // Layer 5: drop materialized global slots, keep the class machinery warm.
183
192
  resetGlobals() {
184
193
  for (const k of touched) delete base[k];