lahama 2.5.2 → 4.0.0

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.
Files changed (2) hide show
  1. package/dist/lahama.js +136 -447
  2. package/package.json +6 -1
package/dist/lahama.js CHANGED
@@ -1,163 +1,26 @@
1
+ import 'vitest/dist/chunks/reporters.d.OXEK7y4s.d.ts';
2
+
1
3
  function withoutNulls(arr) {
2
4
  return arr.filter((item) => item != null)
3
5
  }
4
- function arraysDiff(oldArray, newArray) {
5
- return {
6
- added : newArray.filter(
7
- (newItem) => !oldArray.includes(newItem)
8
- ),
9
- removed : oldArray.filter(
10
- (oldItem) => !newArray.includes(oldItem)
11
- )
12
- }
13
- }
14
- const ARRAY_DIFF_OP = {
15
- ADD : 'add',
16
- REMOVE : 'remove',
17
- MOVE : 'move',
18
- NOOP : 'noop'
19
- };
20
6
  const a = { };
21
7
  const b = { };
22
8
  console.log(a === b);
23
- class ArrayWithOriginalIndices {
24
- #array = []
25
- #originalIndices = []
26
- #equalsFn
27
- constructor(array, equalsFn) {
28
- this.#array = [...array];
29
- this.#originalIndices = array.map((_, i) => i);
30
- this.#equalsFn = equalsFn;
31
- }
32
- get length() {
33
- return this.#array.length
34
- }
35
- isRemoval(index, newArray) {
36
- if (index >= this.length) {
37
- return false
38
- }
39
- const item = this.#array[index];
40
- const indexInNewArray = newArray.findIndex((newItem) =>
41
- this.#equalsFn(item, newItem)
42
- );
43
- return indexInNewArray === -1
44
- }
45
- removeItem(index) {
46
- const operation = {
47
- op : ARRAY_DIFF_OP.REMOVE,
48
- index,
49
- item : this.#array[index],
50
- };
51
- this.#array.splice(index, 1);
52
- return operation
53
- }
54
- isNoop(index, newArray) {
55
- if (index >= this.length) {
56
- return false
57
- }
58
- const item = this.#array[index];
59
- const newItem = newArray[index];
60
- return this.#equalsFn(item, newItem)
61
- }
62
- originalIndexAt(index) {
63
- return this.#originalIndices[index]
64
- }
65
- noopItem(index) {
66
- return {
67
- op : ARRAY_DIFF_OP.NOOP,
68
- originalIndex : this.originalIndexAt(index),
69
- item : this.#array[index],
70
- }
71
- }
72
- isAddition(item, fromIdx) {
73
- return this.findIndexFrom(item, fromIdx) === -1
74
- }
75
- findIndexFrom(item, fromIndex) {
76
- for (let i = fromIndex; i < this.length; i++) {
77
- if (this.#equalsFn(item, this.#array[i])) {
78
- return i
79
- }
80
- }
81
- return -1
82
- }
83
- addItem(item, index) {
84
- const operation = {
85
- op : ARRAY_DIFF_OP.ADD,
86
- index,
87
- item
88
- };
89
- this.#array.splice(index, 0, item);
90
- this.#originalIndices.splice(index, 0, -1);
91
- return operation
92
- }
93
- moveItem(item, toIndex) {
94
- const fromIndex = this.findIndexFrom(item, toIndex);
95
- const operation = {
96
- op : ARRAY_DIFF_OP.MOVE,
97
- originalIndex : this.originalIndexAt(fromIndex),
98
- from : fromIndex,
99
- index : toIndex,
100
- item : this.#array[fromIndex]
101
- };
102
- const [_item] = this.#array.splice(fromIndex, 1);
103
- this.#array.splice(toIndex, 0 , _item);
104
- const [originalIndex] =
105
- this.#originalIndices.splice(fromIndex, 1);
106
- this.#originalIndices.splice(toIndex, 0, originalIndex);
107
- return operation
108
- }
109
- removeItemsAfter(index) {
110
- const operations = [];
111
- while (this.length > index) {
112
- operations.push(this.removeItem(index));
113
- }
114
- return operations
115
- }
116
- }
117
- function arraysDiffSequence(
118
- oldArray,
119
- newArray,
120
- equalsFn = (a, b) => a === b
121
- ) {
122
- const sequence = [];
123
- const array = new ArrayWithOriginalIndices(oldArray, equalsFn);
124
- for (let index = 0; index < newArray.length; index++) {
125
- //!REMOVE CASE
126
- if (array.isRemoval(index, newArray)) {
127
- sequence.push(array.removeItem(index));
128
- index--;
129
- continue
130
- }
131
- //!noop case
132
- if (array.isNoop(index, newArray)) {
133
- sequence.push(array.noopItem(index));
134
- continue
135
- }
136
- //!addition case
137
- const item = newArray[index];
138
- if (array.isAddition(item , index)) {
139
- sequence.push(array.addItem(item, index));
140
- continue
141
- }
142
- //!move case
143
- sequence.push(array.moveItem(item, index));
144
- }
145
- //!remove extra items
146
- sequence.push(...array.removeItemsAfter(newArray.length));
147
- return sequence
148
- }
149
9
 
150
10
  const DOM_TYPES = {
151
11
  TEXT : 'text',
152
12
  ELEMENT : 'element',
153
13
  FRAGMENT : 'fragment',
14
+ COMPONENT : 'component',
154
15
  };
155
16
  function h(tag, props = {} , children = []) {
17
+ const type =
18
+ typeof tag === 'string' ? DOM_TYPES.ELEMENT : DOM_TYPES.COMPONENT;
156
19
  return {
157
20
  tag,
158
21
  props,
22
+ type,
159
23
  children: mapTextNodes(withoutNulls(children)),
160
- type : DOM_TYPES.ELEMENT,
161
24
  }
162
25
  }
163
26
  function mapTextNodes(children) {
@@ -177,15 +40,31 @@ function hFragment(vNodes) {
177
40
  function addEventListener(
178
41
  eventName,
179
42
  handler,
180
- el
43
+ el,
44
+ hostComponent = null
181
45
  ) {
182
- el.addEventListener(eventName, handler);
183
- return handler
46
+ function boundHandler() {
47
+ hostComponent
48
+ ? handler.apply(hostComponent, arguments)
49
+ : handler(...arguments);
50
+ }
51
+ el.addEventListener(eventName, boundHandler);
52
+ return boundHandler;
184
53
  }
185
- function addEventListeners(listeners = {}, el) {
54
+ function addEventListeners(
55
+ listeners = {},
56
+ el,
57
+ hostComponent = null
58
+ ) {
186
59
  const addedListeners = {};
187
60
  Object.entries(listeners).forEach(([eventName, handler]) => {
188
- addedListeners[eventName] = addEventListener(eventName, handler, el);
61
+ const listener = addEventListener(
62
+ eventName,
63
+ handler,
64
+ el,
65
+ hostComponent
66
+ );
67
+ addedListeners[eventName] = listener;
189
68
  });
190
69
  return addedListeners
191
70
  }
@@ -195,45 +74,6 @@ function removeEventListeners(listeners = {}, el) {
195
74
  });
196
75
  }
197
76
 
198
- function destroyDom(vdom) {
199
- const { type } = vdom;
200
- switch (type) {
201
- case DOM_TYPES.TEXT : {
202
- removeTextNode(vdom);
203
- break
204
- }
205
- case DOM_TYPES.ELEMENT : {
206
- removeElementNode(vdom);
207
- break
208
- }
209
- case DOM_TYPES.FRAGMENT : {
210
- removeFragmentNode(vdom);
211
- break
212
- }
213
- default : {
214
- throw new Error(`Can't destroy DOM of type ${type}`)
215
- }
216
- }
217
- delete vdom.el;
218
- }
219
- function removeTextNode(vdom) {
220
- const { el } = vdom;
221
- el.remove();
222
- }
223
- function removeElementNode(vdom) {
224
- const { el , children, listeners } = vdom;
225
- el.remove();
226
- children.forEach(destroyDom);
227
- if (listeners) {
228
- removeEventListeners(listeners, el);
229
- delete vdom.listeners;
230
- }
231
- }
232
- function removeFragmentNode(vdom) {
233
- const { children } = vdom;
234
- children.forEach(destroyDom);
235
- }
236
-
237
77
  function setAttributes(el, attrs) {
238
78
  const { class : className, style, ...otherAttrs} = attrs;
239
79
  if (className) {
@@ -260,9 +100,6 @@ function setClass(el, className) {
260
100
  function setStyle(el, name, value) {
261
101
  el.style[name] = value;
262
102
  }
263
- function removeStyle(el, name) {
264
- el.style[name] = null;
265
- }
266
103
  function removeAttributeCustom(el, name) {
267
104
  el[name] = null;
268
105
  el.removeAttribute(name);
@@ -277,18 +114,61 @@ function setAttribute(el, name, value) {
277
114
  }
278
115
  }
279
116
 
280
- function mountDom(vdom, parentEl, index) {
117
+ function extractPropsAndEvents(vdom) {
118
+ const { on : events = {}, ...props } = vdom.props;
119
+ delete props.key;
120
+ return { props, events }
121
+ }
122
+
123
+ let isScheduled = false;
124
+ const jobs = [];
125
+ function enqueueJob(job) {
126
+ jobs.push(job);
127
+ scheduleUpdate();
128
+ }
129
+ function scheduleUpdate() {
130
+ if (isScheduled) return
131
+ isScheduled = true;
132
+ queueMicrotask(processJobs);
133
+ }
134
+ function processJobs() {
135
+ while (jobs.length > 0) {
136
+ const job = jobs.shift();
137
+ const result = job();
138
+ Promise.resolve(result).then(
139
+ () => {
140
+ },
141
+ (error) => {
142
+ console.error(`[scheduler]: ${error}`);
143
+ }
144
+ );
145
+ job();
146
+ }
147
+ isScheduled = false;
148
+ }
149
+
150
+ function mountDom(
151
+ vdom,
152
+ parentEl,
153
+ index,
154
+ hostComponent = null
155
+ ) {
281
156
  switch (vdom.type) {
282
157
  case DOM_TYPES.TEXT : {
283
- createTextNode(vdom, parentEl, index);
158
+ createTextNode(vdom, parentEl, index, );
284
159
  break
285
160
  }
286
161
  case DOM_TYPES.ELEMENT : {
287
- createElementNode(vdom, parentEl, index);
162
+ createElementNode(vdom, parentEl, index, hostComponent);
288
163
  break
289
164
  }
290
165
  case DOM_TYPES.FRAGMENT : {
291
- createFragmentNodes(vdom, parentEl, index);
166
+ createFragmentNodes(vdom, parentEl, index, hostComponent);
167
+ break
168
+ }
169
+ case DOM_TYPES.COMPONENT : {
170
+ createComponentNode(vdom, parentEl, index, hostComponent);
171
+ enqueueJob(() => vdom.component.onMounted());
292
172
  break
293
173
  }
294
174
  default : {
@@ -318,295 +198,104 @@ function createTextNode(vdom, parentEl, index) {
318
198
  vdom.el = textNode;
319
199
  insert(textNode, parentEl, index);
320
200
  }
321
- function createElementNode(vdom, parentEl, index) {
322
- const { tag, props , children } = vdom;
201
+ function createElementNode(vdom, parentEl, index, hostComponent) {
202
+ const { tag, children } = vdom;
323
203
  const element = document.createElement(tag);
324
- addProps(element, props , vdom);
204
+ addProps(element, vdom, hostComponent);
325
205
  vdom.el = element;
326
206
  //!function mountDom(vnode, parentEl) {
327
- children.forEach((child) => mountDom(child, element));
207
+ children.forEach((child) => mountDom(child, element, null, hostComponent));
328
208
  insert(element, parentEl, index);
329
209
  }
330
- function addProps(el, props, vdom) {
331
- const { on : events, ...attrs } = props;
332
- vdom.listeners = addEventListeners(events, el);
210
+ function addProps(el, props, vdom, hostComponent) {
211
+ const { props : attrs, events } = extractPropsAndEvents(vdom);
212
+ vdom.listeners = addEventListeners(events, el, hostComponent);
333
213
  setAttributes(el, attrs);
334
214
  }
335
- function createFragmentNodes(vdom, parentEl, index) {
215
+ function createFragmentNodes(vdom, parentEl, index, hostComponent) {
336
216
  const { children } = vdom;
337
217
  vdom.el = parentEl;
338
- children.forEach((child, i) => mountDom(child, parentEl, index ? index + i : null));
218
+ children.forEach((child, i) => mountDom(child, parentEl, index ? index + i : null, hostComponent));
219
+ }
220
+ function createComponentNode(vdom, parentEl, index, hostComponent) {
221
+ const Component = vdom.tag;
222
+ const { props, events } = extractPropsAndEvents(vdom);
223
+ const component = new Component(props, events, hostComponent);
224
+ component.mount(parentEl, index);
225
+ vdom.component = component;
226
+ vdom.el = component.firstElement;
339
227
  }
340
228
 
341
- class Dispatcher {
342
- #subs = new Map()
343
- #afterHandlers = []
344
- subscribe(commandName, handler) {
345
- if (!this.#subs.has(commandName)) {
346
- this.#subs.set(commandName, []);
347
- }
348
- const handlersArray = this.#subs.get(commandName);
349
- if (handlersArray.includes(handler)) {
350
- return () => {
351
- }
352
- }
353
- handlersArray.push(handler);
354
- return () => {
355
- const idx = handlersArray.indexOf(handler);
356
- handlersArray.splice(idx, 1);
357
- }
358
- }
359
- afterEveryCommand(handler) {
360
- this.#afterHandlers.push(handler);
361
- return () => {
362
- const idx = this.#afterHandlers.indexOf(handler);
363
- this.#afterHandlers.splice(idx, 1);
229
+ function destroyDom(vdom) {
230
+ const { type } = vdom;
231
+ switch (type) {
232
+ case DOM_TYPES.TEXT : {
233
+ removeTextNode(vdom);
234
+ break
364
235
  }
365
- }
366
- dispatch(commandName, payload) {
367
- if (this.#subs.has(commandName)) {
368
- this.#subs.get(commandName).forEach((handler) => handler(payload));
369
- } else {
370
- console.warn(`No handlers for command : ${commandName}`);
236
+ case DOM_TYPES.ELEMENT : {
237
+ removeElementNode(vdom);
238
+ break
371
239
  }
372
- this.#afterHandlers.forEach((handler) => handler());
373
- }
374
- }
375
-
376
- function areNodesEqual(nodeOne, nodeTwo) {
377
- if (!nodeOne || !nodeTwo) {
378
- console.error('Invalid VDOM node', { nodeOne, nodeTwo });
379
- }
380
- if (!nodeOne.type || !nodeTwo.type) {
381
- console.error('Invalid VDOM node', { nodeOne, nodeTwo });
382
- }
383
- if (nodeOne.type !== nodeTwo.type) {
384
- return false
385
- }
386
- if (nodeOne.type === DOM_TYPES.ELEMENT) {
387
- const { tag : tagOne } = nodeOne;
388
- const { tag : tagTwo} = nodeTwo;
389
- return tagOne === tagTwo
390
- }
391
- return true
392
- }
393
-
394
- function objectsDiff(oldObj, newObj) {
395
- const oldKeys = Object.keys(oldObj);
396
- const newKeys = Object.keys(newObj);
397
- return {
398
- added : newKeys.filter((key) => !(key in oldObj)),
399
- removed : oldKeys.filter((key) => !(key in newObj)),
400
- updated : newKeys.filter(
401
- (key) => key in oldObj && oldObj[key] !== newObj[key]
402
- ),
403
- }
404
- }
405
-
406
- function isNotEmptyString(str) {
407
- return str !== ''
408
- }
409
- function isNotBlankOrEmptyString(str) {
410
- return isNotEmptyString(str.trim())
411
- }
412
-
413
- function patchDOM(oldVdom, newVdom, parentEl) {
414
- if (!areNodesEqual(oldVdom, newVdom)) {
415
- const index = findIndexInParent(parentEl, oldVdom.el);
416
- destroyDom(oldVdom);
417
- mountDom(newVdom, parentEl, index);
418
- return newVdom
419
- }
420
- newVdom.el = oldVdom.el;
421
- switch (newVdom.type) {
422
- case DOM_TYPES.TEXT: {
423
- patchText(oldVdom, newVdom);
424
- return newVdom
240
+ case DOM_TYPES.FRAGMENT : {
241
+ removeFragmentNode(vdom);
242
+ break
425
243
  }
426
- case DOM_TYPES.ELEMENT: {
427
- patchElement(oldVdom, newVdom);
244
+ case DOM_TYPES.COMPONENT : {
245
+ vdom.component.unmount();
246
+ enqueueJob(() => vdom.component.onUnMounted());
428
247
  break
429
248
  }
249
+ default : {
250
+ throw new Error(`Can't destroy DOM of type ${type}`)
251
+ }
430
252
  }
431
- patchChildren(oldVdom, newVdom);
432
- return newVdom
433
- }
434
- function patchText(oldVdom, newVdom) {
435
- const el = oldVdom.el;
436
- const { value : oldText} = oldVdom;
437
- const { value : newText} = newVdom;
438
- if (oldText !== newText) {
439
- el.nodeValue = newText;
440
- }
441
- }
442
- function findIndexInParent(parentEl, el) {
443
- const index = Array.from(parentEl.childNodes).indexOf(el);
444
- if (index < 0) {
445
- return null
446
- }
447
- return index
448
- }
449
- function patchElement(oldVdom, newVdom) {
450
- const el = oldVdom.el;
451
- const {
452
- class : oldClass,
453
- style : oldStyle,
454
- on: oldEvents,
455
- ...oldAttrs
456
- } = oldVdom.props;
457
- const {
458
- class: newClass,
459
- style: newStyle,
460
- on: newEvents,
461
- ...newAttrs
462
- } = newVdom.props;
463
- const { listeners: oldListeners } = oldVdom;
464
- patchAttrs(el, oldAttrs, newAttrs);
465
- patchClasses(el, oldClass, newClass);
466
- patchStyles(el, oldStyle, newStyle);
467
- newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents);
468
- }
469
- function patchAttrs(el, oldAttrs, newAttrs) {
470
- const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
471
- for (const attr of removed) {
472
- removeAttributeCustom(el, attr);
473
- }
474
- for (const attr of added.concat(updated)) {
475
- setAttribute(el, attr, newAttrs[attr]);
476
- }
477
- }
478
- function patchClasses(el, oldClass, newClass) {
479
- const oldClasses = toClassList(oldClass);
480
- const newClasses = toClassList(newClass);
481
- const {added, removed} =
482
- arraysDiff(oldClasses, newClasses);
483
- if (removed.length > 0) {
484
- el.classList.remove(...removed);
485
- }
486
- if (added.length > 0) {
487
- el.classList.add(...added);
488
- }
489
- }
490
- function toClassList(classes = '') {
491
- return Array.isArray(classes)
492
- ? classes.filter(isNotBlankOrEmptyString)
493
- : classes.split(/(\s+)/)
494
- .filter(isNotBlankOrEmptyString)
495
- }
496
- function patchStyles(el, oldStyle = {}, newStyle = {}) {
497
- const {added, removed, updated } = objectsDiff(oldStyle, newStyle);
498
- for (const style of removed) {
499
- removeStyle(el, style);
500
- }
501
- for (const style of added.concat(updated)) {
502
- setStyle(el, style, newStyle[style]);
503
- }
253
+ delete vdom.el;
504
254
  }
505
- function patchEvents(
506
- el,
507
- oldListeners = {},
508
- oldEvents = {},
509
- newEvents = {},
510
- ) {
511
- const { removed, added, updated} =
512
- objectsDiff(oldEvents, newEvents);
513
- for (const eventName of removed.concat(updated)) {
514
- el.removeEventListener(eventName, oldListeners[eventName]);
515
- }
516
- const addedListeners = {};
517
- for (const eventName of added.concat(updated)) {
518
- const listener = addEventListener(eventName, newEvents[eventName], el);
519
- addedListeners[eventName] = listener;
520
- }
521
- return addedListeners
255
+ function removeTextNode(vdom) {
256
+ const { el } = vdom;
257
+ el.remove();
522
258
  }
523
- function extractChildren(vdom) {
524
- if (vdom.children == null) {
525
- return []
526
- }
527
- const children = [];
528
- for (const child of vdom.children) {
529
- if (child.type === DOM_TYPES.FRAGMENT) {
530
- children.push(...extractChildren(child));
531
- } else {
532
- children.push(child);
533
- }
259
+ function removeElementNode(vdom) {
260
+ const { el , children, listeners } = vdom;
261
+ el.remove();
262
+ children.forEach(destroyDom);
263
+ if (listeners) {
264
+ removeEventListeners(listeners, el);
265
+ delete vdom.listeners;
534
266
  }
535
- return children
536
267
  }
537
- function patchChildren(oldVdom, newVdom) {
538
- const oldChildren = extractChildren(oldVdom);
539
- const newChildren = extractChildren(newVdom);
540
- const parentEl = oldVdom.el;
541
- const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
542
- for (const operation of diffSeq) {
543
- const { originalIndex, index, item} = operation;
544
- switch (operation.op) {
545
- case ARRAY_DIFF_OP.ADD: {
546
- mountDom(item, parentEl, index);
547
- break
548
- }
549
- case ARRAY_DIFF_OP.REMOVE: {
550
- destroyDom(item);
551
- break
552
- }
553
- case ARRAY_DIFF_OP.MOVE: {
554
- const oldChild = oldChildren[originalIndex];
555
- const newChild = newChildren[index];
556
- const el = oldChild.el;
557
- const elAtTargetIndex = parentEl.childNodes[index];
558
- parentEl.insertBefore(el, elAtTargetIndex);
559
- patchDOM(oldChildren[originalIndex], newChild, parentEl);
560
- break
561
- }
562
- case ARRAY_DIFF_OP.NOOP : {
563
- patchDOM(oldChildren[originalIndex], newChildren[index], parentEl);
564
- break
565
- }
566
- }
567
- }
268
+ function removeFragmentNode(vdom) {
269
+ const { children } = vdom;
270
+ children.forEach(destroyDom);
568
271
  }
569
272
 
570
- function createApp({ state, view, reducers = {} }) {
273
+ function createApp(RootComponent, props = {}) {
571
274
  let parentEl = null;
572
- let vdom = null;
573
275
  let isMounted = false;
574
- const dispatcher = new Dispatcher();
575
- const subscriptions = [dispatcher.afterEveryCommand(renderApp)];
576
- function emit(eventName, payload) {
577
- dispatcher.dispatch(eventName, payload);
578
- }
579
- for (const actionName in reducers) {
580
- const reducer = reducers[actionName];
581
- const subs = dispatcher.subscribe(actionName, (payload) => {
582
- state = reducer(state, payload);
583
- });
584
- subscriptions.push(subs);
585
- }
586
- function renderApp() {
587
- const newVdom = view(state, emit);
588
- if (!newVdom) {
589
- console.error('View returned', newVdom);
590
- return;
591
- }
592
- vdom = patchDOM(vdom, newVdom, parentEl);
276
+ let vdom = null;
277
+ function reset() {
278
+ parentEl = null;
279
+ isMounted = false;
280
+ vdom = null;
593
281
  }
594
282
  return {
595
283
  mount(_parentEl) {
596
284
  if (isMounted) {
597
- throw new Error("The application is already mounted")
285
+ throw new Error(`The application is already mounted`)
598
286
  }
599
287
  parentEl = _parentEl;
600
- vdom = view(state, emit);
288
+ vdom = h(RootComponent, props);
601
289
  mountDom(vdom, parentEl);
602
290
  isMounted = true;
603
291
  },
604
292
  unmount() {
293
+ if (!isMounted) {
294
+ throw new Error(`The application is not mounted`)
295
+ }
605
296
  destroyDom(vdom);
606
- vdom = null;
607
- subscriptions.forEach((unsubscribe) => unsubscribe());
608
- isMounted = false;
609
- },
297
+ reset();
298
+ }
610
299
  }
611
300
  }
612
301
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lahama",
3
- "version": "2.5.2",
3
+ "version": "4.0.0",
4
4
  "description": "",
5
5
  "main": "dist/lahama.js",
6
6
  "files": [
@@ -20,6 +20,8 @@
20
20
  "type": "module",
21
21
  "devDependencies": {
22
22
  "@eslint/js": "^9.39.0",
23
+ "@rollup/plugin-commonjs": "^29.0.0",
24
+ "@rollup/plugin-node-resolve": "^16.0.3",
23
25
  "globals": "^16.5.0",
24
26
  "jsdom": "^27.2.0",
25
27
  "rimraf": "^3.0.2",
@@ -27,5 +29,8 @@
27
29
  "rollup-plugin-cleanup": "^3.2.1",
28
30
  "rollup-plugin-filesize": "^10.0.0",
29
31
  "vitest": "^4.0.15"
32
+ },
33
+ "dependencies": {
34
+ "fast-deep-equal": "^3.1.3"
30
35
  }
31
36
  }