@ryupold/vode 1.8.6 → 1.8.8
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 +34 -56
- package/dist/vode.cjs.min.js +2 -2
- package/dist/vode.d.ts +6 -10
- package/dist/vode.es5.min.js +7 -7
- package/dist/vode.js +84 -138
- package/dist/vode.min.js +1 -1
- package/dist/vode.min.mjs +1 -1
- package/dist/vode.mjs +84 -138
- package/package.json +1 -1
- package/src/state-context.ts +6 -4
- package/src/vode.ts +105 -156
- package/test/helper.ts +78 -32
- package/test/index.ts +41 -16
- package/test/mocks.ts +132 -38
- package/test/tests-app.ts +117 -1
- package/test/tests-catch.ts +160 -0
- package/test/tests-defuse.ts +22 -1
- package/test/tests-examples.ts +992 -0
- package/test/tests-hydrate.ts +43 -9
- package/test/tests-memo.ts +91 -50
- package/test/tests-mount-unmount.ts +265 -1
- package/test/tests-patch-advanced.ts +84 -0
- package/test/tests-patch-merge.ts +66 -0
- package/test/tests-state-context.ts +32 -1
package/dist/vode.mjs
CHANGED
|
@@ -20,7 +20,6 @@ function app(container, state, dom, ...initialPatches) {
|
|
|
20
20
|
_vode.qSync = null;
|
|
21
21
|
_vode.qAsync = null;
|
|
22
22
|
_vode.stats = { lastSyncRenderTime: 0, lastAsyncRenderTime: 0, syncRenderCount: 0, asyncRenderCount: 0, liveEffectCount: 0, patchCount: 0, syncRenderPatchCount: 0, asyncRenderPatchCount: 0 };
|
|
23
|
-
_vode.unmounts = [];
|
|
24
23
|
const patchableState = state;
|
|
25
24
|
if ("patch" in state && typeof state.patch === "function" && Array.isArray(state.patch.initialPatches)) {
|
|
26
25
|
initialPatches = [...state.patch.initialPatches, ...initialPatches];
|
|
@@ -89,15 +88,15 @@ function app(container, state, dom, ...initialPatches) {
|
|
|
89
88
|
}
|
|
90
89
|
});
|
|
91
90
|
function renderDom(isAsync) {
|
|
92
|
-
const sw =
|
|
91
|
+
const sw = performance.now();
|
|
93
92
|
const vom = dom(_vode.state);
|
|
94
|
-
_vode.vode = render(_vode.state, container.parentElement, 0, 0, _vode.vode, vom
|
|
93
|
+
_vode.vode = render(_vode.state, container.parentElement, 0, 0, _vode.vode, vom);
|
|
95
94
|
if (container.tagName.toUpperCase() !== vom[0].toUpperCase()) {
|
|
96
95
|
container = _vode.vode.node;
|
|
97
96
|
container._vode = _vode;
|
|
98
97
|
}
|
|
99
98
|
if (!isAsync) {
|
|
100
|
-
_vode.stats.lastSyncRenderTime =
|
|
99
|
+
_vode.stats.lastSyncRenderTime = performance.now() - sw;
|
|
101
100
|
_vode.stats.syncRenderCount++;
|
|
102
101
|
_vode.isRendering = false;
|
|
103
102
|
if (_vode.qSync) _vode.renderSync();
|
|
@@ -126,14 +125,14 @@ function app(container, state, dom, ...initialPatches) {
|
|
|
126
125
|
await globals.currentViewTransition?.updateCallbackDone;
|
|
127
126
|
if (_vode.isAnimating || !_vode.qAsync || document.hidden) return;
|
|
128
127
|
_vode.isAnimating = true;
|
|
129
|
-
const sw =
|
|
128
|
+
const sw = performance.now();
|
|
130
129
|
try {
|
|
131
130
|
_vode.state = mergeState(_vode.state, _vode.qAsync, true);
|
|
132
131
|
_vode.qAsync = null;
|
|
133
132
|
globals.currentViewTransition = _vode.asyncRenderer(ar);
|
|
134
133
|
await globals.currentViewTransition?.updateCallbackDone;
|
|
135
134
|
} finally {
|
|
136
|
-
_vode.stats.lastAsyncRenderTime =
|
|
135
|
+
_vode.stats.lastAsyncRenderTime = performance.now() - sw;
|
|
137
136
|
_vode.stats.asyncRenderCount++;
|
|
138
137
|
_vode.isAnimating = false;
|
|
139
138
|
}
|
|
@@ -151,10 +150,7 @@ function app(container, state, dom, ...initialPatches) {
|
|
|
151
150
|
indexInParent,
|
|
152
151
|
indexInParent,
|
|
153
152
|
hydrate(container, true),
|
|
154
|
-
dom(state)
|
|
155
|
-
null,
|
|
156
|
-
_vode.unmounts,
|
|
157
|
-
0
|
|
153
|
+
dom(state)
|
|
158
154
|
);
|
|
159
155
|
_vode.isRendering = false;
|
|
160
156
|
if (_vode.qSync) _vode.renderSync();
|
|
@@ -235,11 +231,15 @@ function hydrate(element, prepareForRender) {
|
|
|
235
231
|
return void 0;
|
|
236
232
|
}
|
|
237
233
|
}
|
|
238
|
-
function memo(compare,
|
|
234
|
+
function memo(compare, component) {
|
|
239
235
|
if (!compare || !Array.isArray(compare)) throw new Error("first argument to memo() must be an array of values to compare");
|
|
240
|
-
if (typeof
|
|
241
|
-
|
|
242
|
-
|
|
236
|
+
if (typeof component !== "function") throw new Error("second argument to memo() must be a function that returns a child vode");
|
|
237
|
+
if (component.__memo) {
|
|
238
|
+
const comp = component;
|
|
239
|
+
component = (s) => comp(s);
|
|
240
|
+
}
|
|
241
|
+
component.__memo = compare;
|
|
242
|
+
return component;
|
|
243
243
|
}
|
|
244
244
|
function createState(state) {
|
|
245
245
|
if (!state || typeof state !== "object") throw new Error("createState() must be called with a state object");
|
|
@@ -324,7 +324,7 @@ function mergeState(target, source, allowDeletion) {
|
|
|
324
324
|
}
|
|
325
325
|
return target;
|
|
326
326
|
}
|
|
327
|
-
function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmlns
|
|
327
|
+
function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmlns) {
|
|
328
328
|
try {
|
|
329
329
|
newVode = remember(state, newVode, oldVode);
|
|
330
330
|
const isNoVode = !newVode || typeof newVode === "number" || typeof newVode === "boolean";
|
|
@@ -334,17 +334,7 @@ function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmln
|
|
|
334
334
|
const oldIsText = oldVode?.nodeType === Node.TEXT_NODE;
|
|
335
335
|
const oldNode = oldIsText ? oldVode : oldVode?.node;
|
|
336
336
|
if (isNoVode) {
|
|
337
|
-
|
|
338
|
-
const start = oldVode.unmountStart;
|
|
339
|
-
const count = oldVode.unmountCount;
|
|
340
|
-
for (let i = count - 1; i >= 0; i--) {
|
|
341
|
-
const fn = unmounts[start + i];
|
|
342
|
-
if (fn) {
|
|
343
|
-
state.patch(fn(state, oldNode));
|
|
344
|
-
unmounts[start + i] = null;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
337
|
+
unmountTree(state, oldVode);
|
|
348
338
|
oldNode?.remove();
|
|
349
339
|
return void 0;
|
|
350
340
|
}
|
|
@@ -367,24 +357,14 @@ function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmln
|
|
|
367
357
|
if (isText && (!oldNode || !oldIsText)) {
|
|
368
358
|
const text = document.createTextNode(newVode);
|
|
369
359
|
if (oldNode) {
|
|
370
|
-
|
|
371
|
-
const start = oldVode.unmountStart;
|
|
372
|
-
const count = oldVode.unmountCount;
|
|
373
|
-
for (let i = count - 1; i >= 0; i--) {
|
|
374
|
-
const fn = unmounts[start + i];
|
|
375
|
-
if (fn) {
|
|
376
|
-
state.patch(fn(state, oldNode));
|
|
377
|
-
unmounts[start + i] = null;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
360
|
+
unmountTree(state, oldVode);
|
|
381
361
|
oldNode.replaceWith(text);
|
|
382
362
|
} else {
|
|
383
363
|
let inserted = false;
|
|
384
364
|
for (let i = indexInParent; i < parent.childNodes.length; i++) {
|
|
385
365
|
const nextSibling = parent.childNodes[i];
|
|
386
366
|
if (nextSibling) {
|
|
387
|
-
nextSibling.before(text
|
|
367
|
+
nextSibling.before(text);
|
|
388
368
|
inserted = true;
|
|
389
369
|
break;
|
|
390
370
|
}
|
|
@@ -410,26 +390,14 @@ function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmln
|
|
|
410
390
|
newVode.node.removeAttribute("catch");
|
|
411
391
|
}
|
|
412
392
|
if (oldNode) {
|
|
413
|
-
|
|
414
|
-
const start = oldVode.unmountStart;
|
|
415
|
-
const count = oldVode.unmountCount;
|
|
416
|
-
for (let i = count - 1; i >= 0; i--) {
|
|
417
|
-
const fn = unmounts[start + i];
|
|
418
|
-
if (fn) {
|
|
419
|
-
state.patch(fn(state, oldNode));
|
|
420
|
-
unmounts[start + i] = null;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
unmounts[unmountStart] = properties?.onUnmount ?? null;
|
|
393
|
+
unmountTree(state, oldVode);
|
|
425
394
|
oldNode.replaceWith(newNode);
|
|
426
395
|
} else {
|
|
427
|
-
unmounts[unmountStart] = properties?.onUnmount ?? null;
|
|
428
396
|
let inserted = false;
|
|
429
397
|
for (let i = indexInParent; i < parent.childNodes.length; i++) {
|
|
430
398
|
const nextSibling = parent.childNodes[i];
|
|
431
399
|
if (nextSibling) {
|
|
432
|
-
nextSibling.before(newNode
|
|
400
|
+
nextSibling.before(newNode);
|
|
433
401
|
inserted = true;
|
|
434
402
|
break;
|
|
435
403
|
}
|
|
@@ -438,78 +406,53 @@ function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmln
|
|
|
438
406
|
parent.appendChild(newNode);
|
|
439
407
|
}
|
|
440
408
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const newKids = children(newVode);
|
|
444
|
-
if (newKids) {
|
|
409
|
+
const newStart = childrenStart(newVode);
|
|
410
|
+
if (newStart > 0) {
|
|
445
411
|
const childOffset = !!properties ? 2 : 1;
|
|
446
412
|
let indexP = 0;
|
|
447
|
-
for (let i = 0; i <
|
|
448
|
-
const child2 =
|
|
449
|
-
const attached = render(state, newNode, i, indexP, void 0, child2, xmlns ?? null
|
|
413
|
+
for (let i = 0; i < newVode.length - newStart; i++) {
|
|
414
|
+
const child2 = newVode[i + newStart];
|
|
415
|
+
const attached = render(state, newNode, i, indexP, void 0, child2, xmlns ?? null);
|
|
450
416
|
newVode[i + childOffset] = attached;
|
|
451
|
-
if (attached)
|
|
452
|
-
indexP++;
|
|
453
|
-
const childUnmounts = attached.unmountCount || 0;
|
|
454
|
-
totalChildUnmounts += childUnmounts;
|
|
455
|
-
childUnmountStart += childUnmounts;
|
|
456
|
-
}
|
|
417
|
+
if (attached) indexP++;
|
|
457
418
|
}
|
|
458
419
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
420
|
+
newVode._unmountCount = (properties?.onUnmount ? 1 : 0) + sumChildUnmountCounts(newVode);
|
|
421
|
+
if (typeof properties?.onMount === "function") {
|
|
422
|
+
state.patch(properties.onMount(state, newNode));
|
|
423
|
+
}
|
|
462
424
|
return newVode;
|
|
463
425
|
}
|
|
464
426
|
if (!oldIsText && isNode && oldVode[0] === newVode[0]) {
|
|
465
427
|
newVode.node = oldNode;
|
|
466
|
-
const newvode = newVode;
|
|
467
|
-
const oldvode = oldVode;
|
|
468
428
|
const properties = props(newVode);
|
|
469
429
|
const oldProps = props(oldVode);
|
|
470
|
-
if (properties?.xmlns !== void 0)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
newvode[1] = remember(state, newvode[1], oldvode[1]);
|
|
474
|
-
if (prev !== newvode[1]) {
|
|
475
|
-
patchProperties(state, oldNode, oldProps, properties, xmlns);
|
|
476
|
-
}
|
|
477
|
-
} else {
|
|
478
|
-
patchProperties(state, oldNode, oldProps, properties, xmlns);
|
|
479
|
-
}
|
|
430
|
+
if (properties?.xmlns !== void 0)
|
|
431
|
+
xmlns = properties.xmlns;
|
|
432
|
+
patchProperties(state, oldNode, oldProps, properties, xmlns);
|
|
480
433
|
if (!!properties?.catch && oldProps?.catch !== properties.catch) {
|
|
481
434
|
newVode.node["catch"] = null;
|
|
482
435
|
newVode.node.removeAttribute("catch");
|
|
483
436
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const newKids = children(newVode);
|
|
488
|
-
const oldKids = children(oldVode);
|
|
489
|
-
if (newKids) {
|
|
490
|
-
const childOffset = !!properties ? 2 : 1;
|
|
437
|
+
const newStart = childrenStart(newVode);
|
|
438
|
+
const oldStart = childrenStart(oldVode);
|
|
439
|
+
if (newStart > 0) {
|
|
491
440
|
let indexP = 0;
|
|
492
|
-
for (let i = 0; i <
|
|
493
|
-
const child2 =
|
|
494
|
-
const oldChild =
|
|
495
|
-
const attached = render(state, oldNode, i, indexP, oldChild, child2, xmlns
|
|
496
|
-
newVode[i +
|
|
497
|
-
if (attached)
|
|
498
|
-
indexP++;
|
|
499
|
-
const childUnmounts = attached.unmountCount || 0;
|
|
500
|
-
totalChildUnmounts += childUnmounts;
|
|
501
|
-
childUnmountStart += childUnmounts;
|
|
502
|
-
}
|
|
441
|
+
for (let i = 0; i < newVode.length - newStart; i++) {
|
|
442
|
+
const child2 = newVode[i + newStart];
|
|
443
|
+
const oldChild = oldStart > 0 ? oldVode[i + oldStart] : void 0;
|
|
444
|
+
const attached = render(state, oldNode, i, indexP, oldChild, child2, xmlns);
|
|
445
|
+
newVode[i + newStart] = attached;
|
|
446
|
+
if (attached) indexP++;
|
|
503
447
|
}
|
|
504
448
|
}
|
|
505
|
-
if (
|
|
506
|
-
const newKidsCount =
|
|
507
|
-
for (let i =
|
|
508
|
-
render(state, oldNode, i, i,
|
|
449
|
+
if (oldStart > 0) {
|
|
450
|
+
const newKidsCount = newStart > 0 ? newVode.length - newStart : 0;
|
|
451
|
+
for (let i = oldVode.length - 1 - oldStart; i >= newKidsCount; i--) {
|
|
452
|
+
render(state, oldNode, i, i, oldVode[i + oldStart], void 0, xmlns);
|
|
509
453
|
}
|
|
510
454
|
}
|
|
511
|
-
newVode.
|
|
512
|
-
newVode.unmountStart = unmountStart;
|
|
455
|
+
newVode._unmountCount = (properties?.onUnmount ? 1 : 0) + sumChildUnmountCounts(newVode);
|
|
513
456
|
return newVode;
|
|
514
457
|
}
|
|
515
458
|
} catch (error) {
|
|
@@ -523,9 +466,7 @@ function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmln
|
|
|
523
466
|
indexInParent,
|
|
524
467
|
hydrate(newVode?.node || oldVode?.node, true),
|
|
525
468
|
handledVode,
|
|
526
|
-
xmlns
|
|
527
|
-
unmounts,
|
|
528
|
-
unmountStart
|
|
469
|
+
xmlns
|
|
529
470
|
);
|
|
530
471
|
} else {
|
|
531
472
|
throw error;
|
|
@@ -533,6 +474,31 @@ function render(state, parent, childIndex, indexInParent, oldVode, newVode, xmln
|
|
|
533
474
|
}
|
|
534
475
|
return void 0;
|
|
535
476
|
}
|
|
477
|
+
function unmountTree(state, v) {
|
|
478
|
+
if (!v || !Array.isArray(v)) return;
|
|
479
|
+
if ((v._unmountCount | 0) === 0) return;
|
|
480
|
+
const kids = children(v);
|
|
481
|
+
if (kids) {
|
|
482
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
483
|
+
unmountTree(state, kids[i]);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const p = props(v);
|
|
487
|
+
if (typeof p?.onUnmount === "function") {
|
|
488
|
+
state.patch(p.onUnmount(state, v.node));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function sumChildUnmountCounts(v) {
|
|
492
|
+
const kids = children(v);
|
|
493
|
+
if (!kids) return 0;
|
|
494
|
+
let n = 0;
|
|
495
|
+
for (const k of kids) {
|
|
496
|
+
if (k && Array.isArray(k)) {
|
|
497
|
+
n += k._unmountCount | 0;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return n;
|
|
501
|
+
}
|
|
536
502
|
function isNaturalVode(x) {
|
|
537
503
|
return Array.isArray(x) && x.length > 0 && typeof x[0] === "string";
|
|
538
504
|
}
|
|
@@ -540,6 +506,9 @@ function isTextVode(x) {
|
|
|
540
506
|
return typeof x === "string" || x?.nodeType === Node.TEXT_NODE;
|
|
541
507
|
}
|
|
542
508
|
function remember(state, present, past) {
|
|
509
|
+
while (typeof present === "function" && !present.__memo) {
|
|
510
|
+
present = present(state);
|
|
511
|
+
}
|
|
543
512
|
if (typeof present !== "function")
|
|
544
513
|
return present;
|
|
545
514
|
const presentMemo = present?.__memo;
|
|
@@ -554,37 +523,13 @@ function remember(state, present, past) {
|
|
|
554
523
|
}
|
|
555
524
|
if (same) return past;
|
|
556
525
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const resultMemo = result.__memo;
|
|
560
|
-
if (Array.isArray(resultMemo) && Array.isArray(pastMemo) && resultMemo.length === pastMemo.length) {
|
|
561
|
-
let same = true;
|
|
562
|
-
for (let i = 0; i < resultMemo.length; i++) {
|
|
563
|
-
if (resultMemo[i] !== pastMemo[i]) {
|
|
564
|
-
same = false;
|
|
565
|
-
break;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (same) return past;
|
|
569
|
-
}
|
|
570
|
-
const innerRender = result(state);
|
|
571
|
-
if (typeof innerRender === "object") {
|
|
572
|
-
innerRender.__memo = resultMemo;
|
|
573
|
-
}
|
|
574
|
-
return innerRender;
|
|
526
|
+
while (typeof present === "function") {
|
|
527
|
+
present = present(state);
|
|
575
528
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
newRender.__memo = result?.__memo || present?.__memo;
|
|
579
|
-
}
|
|
580
|
-
return newRender;
|
|
581
|
-
}
|
|
582
|
-
function unwrap(c, s) {
|
|
583
|
-
if (typeof c === "function") {
|
|
584
|
-
return unwrap(c(s), s);
|
|
585
|
-
} else {
|
|
586
|
-
return c;
|
|
529
|
+
if (typeof present === "object") {
|
|
530
|
+
present.__memo = presentMemo;
|
|
587
531
|
}
|
|
532
|
+
return present;
|
|
588
533
|
}
|
|
589
534
|
function patchProperties(s, node, oldProps, newProps, xmlns) {
|
|
590
535
|
if (!newProps && !oldProps) return;
|
|
@@ -1001,10 +946,11 @@ var ProxyStateContextImpl = class _ProxyStateContextImpl {
|
|
|
1001
946
|
}
|
|
1002
947
|
raw[keys[i]] = value;
|
|
1003
948
|
} else if (keys.length === 1) {
|
|
1004
|
-
if (typeof target[keys[0]] === "object" && typeof value === "object")
|
|
949
|
+
if (typeof target[keys[0]] === "object" && typeof value === "object" && value !== null) {
|
|
1005
950
|
Object.assign(target[keys[0]], value);
|
|
1006
|
-
else
|
|
951
|
+
} else {
|
|
1007
952
|
target[keys[0]] = value;
|
|
953
|
+
}
|
|
1008
954
|
} else {
|
|
1009
955
|
Object.assign(target, value);
|
|
1010
956
|
}
|
package/package.json
CHANGED
package/src/state-context.ts
CHANGED
|
@@ -41,13 +41,13 @@ export interface SubContext<SubState> {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export type ProxyStateContext<S extends PatchableState, SubState> = StateContext<S, SubState> & {
|
|
44
|
-
[K in keyof SubState]-?: SubState[K] extends object
|
|
44
|
+
[K in keyof SubState]-?: SubState[K] extends object | null
|
|
45
45
|
? ProxyStateContext<S, SubState[K]>
|
|
46
46
|
: StateContext<S, SubState[K]>
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
export type ProxySubContext<SubState> = SubContext<SubState> & {
|
|
50
|
-
[K in keyof SubState]-?: SubState[K] extends object
|
|
50
|
+
[K in keyof SubState]-?: SubState[K] extends object | null
|
|
51
51
|
? ProxySubContext<SubState[K]>
|
|
52
52
|
: SubContext<SubState[K]>
|
|
53
53
|
};
|
|
@@ -107,10 +107,12 @@ class ProxyStateContextImpl<S extends PatchableState, SubState>
|
|
|
107
107
|
}
|
|
108
108
|
raw[keys[i]] = value;
|
|
109
109
|
} else if (keys.length === 1) {
|
|
110
|
-
if (typeof (<any>target)[keys[0]] === "object" && typeof value === "object")
|
|
110
|
+
if (typeof (<any>target)[keys[0]] === "object" && typeof value === "object" && value !== null) {
|
|
111
111
|
Object.assign((<any>target)[keys[0]], value);
|
|
112
|
-
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
113
114
|
(<any>target)[keys[0]] = value;
|
|
115
|
+
}
|
|
114
116
|
} else {
|
|
115
117
|
Object.assign(target, value as DeepPartial<S>);
|
|
116
118
|
}
|