@miaskiewicz/turbo-dom 0.1.23 → 0.1.24

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.23",
3
+ "version": "0.1.24",
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",
@@ -5,23 +5,32 @@ function makeLive(getArray, extra = {}) {
5
5
  const target = function () {};
6
6
  return new Proxy(target, {
7
7
  get(_t, key) {
8
- const arr = getArray();
9
- if (key === 'length') return arr.length;
10
- if (key === 'item') return (i) => arr[i] ?? null;
11
- if (key === 'forEach') return (cb, thisArg) => arr.forEach(cb, thisArg);
12
- if (key === 'entries') return () => arr.entries();
13
- if (key === 'keys') return () => arr.keys();
14
- if (key === 'values') return () => arr[Symbol.iterator]();
15
- if (key === Symbol.iterator) return () => arr[Symbol.iterator]();
16
- if (key === 'toString') return () => '[object NodeList]';
17
- if (key in extra) return extra[key](arr);
18
- if (typeof key === 'string' && /^\d+$/.test(key)) return arr[Number(key)] ?? undefined;
8
+ if (typeof key === 'string') {
9
+ // hot path: indexed access coll[i]. A property key starting with a digit
10
+ // is a numeric index detect via charCode, no regex, no string-compare
11
+ // chain. getArray() is called only when actually needed.
12
+ const c = key.charCodeAt(0);
13
+ if (c >= 48 && c <= 57) return getArray()[+key];
14
+ if (key === 'length') return getArray().length;
15
+ if (key === 'item') return (i) => getArray()[i] ?? null;
16
+ if (key === 'forEach') return (cb, thisArg) => getArray().forEach(cb, thisArg);
17
+ if (key === 'entries') return () => getArray().entries();
18
+ if (key === 'keys') return () => getArray().keys();
19
+ if (key === 'values') return () => getArray()[Symbol.iterator]();
20
+ if (key === 'toString') return () => '[object NodeList]';
21
+ if (key in extra) return extra[key](getArray());
22
+ return undefined;
23
+ }
24
+ if (key === Symbol.iterator) return () => getArray()[Symbol.iterator]();
19
25
  return undefined;
20
26
  },
21
27
  has(_t, key) {
22
- const arr = getArray();
23
- if (typeof key === 'string' && /^\d+$/.test(key)) return Number(key) < arr.length;
24
- return key === 'length' || key === 'item' || key === 'forEach' || key in extra;
28
+ if (typeof key === 'string') {
29
+ const c = key.charCodeAt(0);
30
+ if (c >= 48 && c <= 57) return +key < getArray().length;
31
+ return key === 'length' || key === 'item' || key === 'forEach' || key in extra;
32
+ }
33
+ return false;
25
34
  },
26
35
  ownKeys() {
27
36
  const arr = getArray();
@@ -30,8 +39,11 @@ function makeLive(getArray, extra = {}) {
30
39
  getOwnPropertyDescriptor(_t, key) {
31
40
  const arr = getArray();
32
41
  if (key === 'length') return { configurable: true, enumerable: false, value: arr.length };
33
- if (typeof key === 'string' && /^\d+$/.test(key) && Number(key) < arr.length) {
34
- return { configurable: true, enumerable: true, value: arr[Number(key)] };
42
+ if (typeof key === 'string') {
43
+ const c = key.charCodeAt(0);
44
+ if (c >= 48 && c <= 57 && +key < arr.length) {
45
+ return { configurable: true, enumerable: true, value: arr[+key] };
46
+ }
35
47
  }
36
48
  return undefined;
37
49
  },
@@ -114,13 +114,16 @@ function normalizeOptions(options) {
114
114
 
115
115
  export class EventTarget {
116
116
  constructor() {
117
- // type -> array of { callback, capture, once, passive }
118
- this.__listeners = new Map();
117
+ // type -> array of { callback, capture, once, passive }. Lazily created on
118
+ // first addEventListener — most inflated nodes never get a listener, so this
119
+ // skips a Map allocation per node (inflation is a top hot path).
120
+ this.__listeners = null;
119
121
  }
120
122
 
121
123
  addEventListener(type, callback, options) {
122
124
  if (callback == null) return;
123
125
  const o = normalizeOptions(options);
126
+ if (!this.__listeners) this.__listeners = new Map();
124
127
  let list = this.__listeners.get(type);
125
128
  if (!list) { list = []; this.__listeners.set(type, list); }
126
129
  // dedupe on (callback, capture) per spec
@@ -129,6 +132,7 @@ export class EventTarget {
129
132
  }
130
133
 
131
134
  removeEventListener(type, callback, options) {
135
+ if (!this.__listeners) return;
132
136
  const o = normalizeOptions(options);
133
137
  const list = this.__listeners.get(type);
134
138
  if (!list) return;