@b9g/crank 0.5.0-debug.0 → 0.5.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.
- package/README.md +6 -17
- package/crank.cjs +679 -392
- package/crank.cjs.map +1 -1
- package/crank.d.ts +57 -77
- package/crank.js +678 -391
- package/crank.js.map +1 -1
- package/dom.cjs +103 -27
- package/dom.cjs.map +1 -1
- package/dom.d.ts +1 -0
- package/dom.js +103 -25
- package/dom.js.map +1 -1
- package/html.cjs +1 -3
- package/html.cjs.map +1 -1
- package/html.d.ts +2 -1
- package/html.js +1 -1
- package/html.js.map +1 -1
- package/jsx-runtime.cjs +18 -0
- package/jsx-runtime.cjs.map +1 -0
- package/jsx-runtime.d.ts +5 -0
- package/jsx-runtime.js +15 -0
- package/jsx-runtime.js.map +1 -0
- package/{xm.cjs → jsx-tag.cjs} +8 -10
- package/jsx-tag.cjs.map +1 -0
- package/jsx-tag.d.ts +2 -0
- package/{xm.js → jsx-tag.js} +9 -9
- package/jsx-tag.js.map +1 -0
- package/package.json +41 -33
- package/{mod.cjs → standalone.cjs} +3 -5
- package/standalone.cjs.map +1 -0
- package/standalone.d.ts +2 -0
- package/{mod.js → standalone.js} +3 -3
- package/standalone.js.map +1 -0
- package/umd.js +784 -421
- package/umd.js.map +1 -1
- package/mod.cjs.map +0 -1
- package/mod.d.ts +0 -2
- package/mod.js.map +0 -1
- package/xm.cjs.map +0 -1
- package/xm.d.ts +0 -2
- package/xm.js.map +0 -1
package/crank.js
CHANGED
|
@@ -22,7 +22,8 @@ function arrayify(value) {
|
|
|
22
22
|
: typeof value === "string" ||
|
|
23
23
|
typeof value[Symbol.iterator] !== "function"
|
|
24
24
|
? [value]
|
|
25
|
-
:
|
|
25
|
+
: // TODO: inference broke in TypeScript 3.9.
|
|
26
|
+
[...value];
|
|
26
27
|
}
|
|
27
28
|
function isIteratorLike(value) {
|
|
28
29
|
return value != null && typeof value.next === "function";
|
|
@@ -71,8 +72,7 @@ const Copy = Symbol.for("crank.Copy");
|
|
|
71
72
|
/**
|
|
72
73
|
* A special tag for injecting raw nodes or strings via a value prop.
|
|
73
74
|
*
|
|
74
|
-
*
|
|
75
|
-
* the string and the result will be set as the element’s value.
|
|
75
|
+
* Renderer.prototype.raw() is called with the value prop.
|
|
76
76
|
*/
|
|
77
77
|
const Raw = Symbol.for("crank.Raw");
|
|
78
78
|
const ElementSymbol = Symbol.for("crank.Element");
|
|
@@ -158,22 +158,6 @@ function createElement(tag, props, ...children) {
|
|
|
158
158
|
else if (children.length === 1) {
|
|
159
159
|
props1.children = children[0];
|
|
160
160
|
}
|
|
161
|
-
// string aliases for the special tags
|
|
162
|
-
// TODO: Does this logic belong here, or in the Element constructor
|
|
163
|
-
switch (tag) {
|
|
164
|
-
case "$FRAGMENT":
|
|
165
|
-
tag = Fragment;
|
|
166
|
-
break;
|
|
167
|
-
case "$PORTAL":
|
|
168
|
-
tag = Portal;
|
|
169
|
-
break;
|
|
170
|
-
case "$COPY":
|
|
171
|
-
tag = Copy;
|
|
172
|
-
break;
|
|
173
|
-
case "$RAW":
|
|
174
|
-
tag = Raw;
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
161
|
return new Element(tag, props1, key, ref, static_);
|
|
178
162
|
}
|
|
179
163
|
/** Clones a given element, shallowly copying the props object. */
|
|
@@ -252,13 +236,13 @@ function normalize(values) {
|
|
|
252
236
|
class Retainer {
|
|
253
237
|
constructor(el) {
|
|
254
238
|
this.el = el;
|
|
255
|
-
this.value = undefined;
|
|
256
239
|
this.ctx = undefined;
|
|
257
240
|
this.children = undefined;
|
|
258
|
-
this.
|
|
259
|
-
this.
|
|
260
|
-
this.
|
|
261
|
-
this.
|
|
241
|
+
this.value = undefined;
|
|
242
|
+
this.cachedChildValues = undefined;
|
|
243
|
+
this.fallbackValue = undefined;
|
|
244
|
+
this.inflightValue = undefined;
|
|
245
|
+
this.onNextValues = undefined;
|
|
262
246
|
}
|
|
263
247
|
}
|
|
264
248
|
/**
|
|
@@ -267,10 +251,10 @@ class Retainer {
|
|
|
267
251
|
* @returns The value of the element.
|
|
268
252
|
*/
|
|
269
253
|
function getValue(ret) {
|
|
270
|
-
if (typeof ret.
|
|
271
|
-
return typeof ret.
|
|
272
|
-
? getValue(ret.
|
|
273
|
-
: ret.
|
|
254
|
+
if (typeof ret.fallbackValue !== "undefined") {
|
|
255
|
+
return typeof ret.fallbackValue === "object"
|
|
256
|
+
? getValue(ret.fallbackValue)
|
|
257
|
+
: ret.fallbackValue;
|
|
274
258
|
}
|
|
275
259
|
else if (ret.el.tag === Portal) {
|
|
276
260
|
return;
|
|
@@ -286,8 +270,8 @@ function getValue(ret) {
|
|
|
286
270
|
* @returns A normalized array of nodes and strings.
|
|
287
271
|
*/
|
|
288
272
|
function getChildValues(ret) {
|
|
289
|
-
if (ret.
|
|
290
|
-
return wrap(ret.
|
|
273
|
+
if (ret.cachedChildValues) {
|
|
274
|
+
return wrap(ret.cachedChildValues);
|
|
291
275
|
}
|
|
292
276
|
const values = [];
|
|
293
277
|
const children = wrap(ret.children);
|
|
@@ -300,7 +284,7 @@ function getChildValues(ret) {
|
|
|
300
284
|
const values1 = normalize(values);
|
|
301
285
|
const tag = ret.el.tag;
|
|
302
286
|
if (typeof tag === "function" || (tag !== Fragment && tag !== Raw)) {
|
|
303
|
-
ret.
|
|
287
|
+
ret.cachedChildValues = unwrap(values1);
|
|
304
288
|
}
|
|
305
289
|
return values1;
|
|
306
290
|
}
|
|
@@ -308,16 +292,19 @@ const defaultRendererImpl = {
|
|
|
308
292
|
create() {
|
|
309
293
|
throw new Error("Not implemented");
|
|
310
294
|
},
|
|
295
|
+
hydrate() {
|
|
296
|
+
throw new Error("Not implemented");
|
|
297
|
+
},
|
|
311
298
|
scope: IDENTITY,
|
|
312
299
|
read: IDENTITY,
|
|
313
|
-
|
|
314
|
-
|
|
300
|
+
text: IDENTITY,
|
|
301
|
+
raw: IDENTITY,
|
|
315
302
|
patch: NOOP,
|
|
316
303
|
arrange: NOOP,
|
|
317
304
|
dispose: NOOP,
|
|
318
305
|
flush: NOOP,
|
|
319
306
|
};
|
|
320
|
-
const
|
|
307
|
+
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
321
308
|
/**
|
|
322
309
|
* An abstract class which is subclassed to render to different target
|
|
323
310
|
* environments. This class is responsible for kicking off the rendering
|
|
@@ -331,7 +318,7 @@ const $RendererImpl = Symbol.for("crank.RendererImpl");
|
|
|
331
318
|
class Renderer {
|
|
332
319
|
constructor(impl) {
|
|
333
320
|
this.cache = new WeakMap();
|
|
334
|
-
this[
|
|
321
|
+
this[_RendererImpl] = {
|
|
335
322
|
...defaultRendererImpl,
|
|
336
323
|
...impl,
|
|
337
324
|
};
|
|
@@ -343,7 +330,7 @@ class Renderer {
|
|
|
343
330
|
* used root to delete the previously rendered element tree from the cache.
|
|
344
331
|
* @param root - The node to be rendered into. The renderer will cache
|
|
345
332
|
* element trees per root.
|
|
346
|
-
* @param
|
|
333
|
+
* @param bridge - An optional context that will be the ancestor context of all
|
|
347
334
|
* elements in the tree. Useful for connecting different renderers so that
|
|
348
335
|
* events/provisions properly propagate. The context for a given root must be
|
|
349
336
|
* the same or an error will be thrown.
|
|
@@ -353,7 +340,7 @@ class Renderer {
|
|
|
353
340
|
*/
|
|
354
341
|
render(children, root, bridge) {
|
|
355
342
|
let ret;
|
|
356
|
-
const ctx = bridge && bridge[
|
|
343
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
357
344
|
if (typeof root === "object" && root !== null) {
|
|
358
345
|
ret = this.cache.get(root);
|
|
359
346
|
}
|
|
@@ -376,8 +363,32 @@ class Renderer {
|
|
|
376
363
|
this.cache.delete(root);
|
|
377
364
|
}
|
|
378
365
|
}
|
|
379
|
-
const impl = this[
|
|
380
|
-
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
|
|
366
|
+
const impl = this[_RendererImpl];
|
|
367
|
+
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, undefined);
|
|
368
|
+
// We return the child values of the portal because portal elements
|
|
369
|
+
// themselves have no readable value.
|
|
370
|
+
if (isPromiseLike(childValues)) {
|
|
371
|
+
return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
|
|
372
|
+
}
|
|
373
|
+
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
374
|
+
}
|
|
375
|
+
hydrate(children, root, bridge) {
|
|
376
|
+
const impl = this[_RendererImpl];
|
|
377
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
378
|
+
let ret;
|
|
379
|
+
ret = this.cache.get(root);
|
|
380
|
+
if (ret !== undefined) {
|
|
381
|
+
// If there is a retainer for the root, hydration is not necessary.
|
|
382
|
+
return this.render(children, root, bridge);
|
|
383
|
+
}
|
|
384
|
+
let oldProps;
|
|
385
|
+
ret = new Retainer(createElement(Portal, { children, root }));
|
|
386
|
+
ret.value = root;
|
|
387
|
+
if (typeof root === "object" && root !== null && children != null) {
|
|
388
|
+
this.cache.set(root, ret);
|
|
389
|
+
}
|
|
390
|
+
const hydrationData = impl.hydrate(Portal, root, {});
|
|
391
|
+
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, hydrationData);
|
|
381
392
|
// We return the child values of the portal because portal elements
|
|
382
393
|
// themselves have no readable value.
|
|
383
394
|
if (isPromiseLike(childValues)) {
|
|
@@ -389,17 +400,17 @@ class Renderer {
|
|
|
389
400
|
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
390
401
|
function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
|
|
391
402
|
// element is a host or portal element
|
|
392
|
-
if (root
|
|
393
|
-
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.
|
|
403
|
+
if (root != null) {
|
|
404
|
+
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
394
405
|
flush(renderer, root);
|
|
395
406
|
}
|
|
396
|
-
ret.
|
|
407
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
397
408
|
if (root == null) {
|
|
398
409
|
unmount(renderer, ret, ctx, ret);
|
|
399
410
|
}
|
|
400
|
-
return renderer.read(ret.
|
|
411
|
+
return renderer.read(ret.cachedChildValues);
|
|
401
412
|
}
|
|
402
|
-
function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
413
|
+
function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrationData) {
|
|
403
414
|
const oldRetained = wrap(parent.children);
|
|
404
415
|
const newRetained = [];
|
|
405
416
|
const newChildren = arrayify(children);
|
|
@@ -408,14 +419,15 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
408
419
|
let childrenByKey;
|
|
409
420
|
let seenKeys;
|
|
410
421
|
let isAsync = false;
|
|
411
|
-
let
|
|
422
|
+
let hydrationBlock;
|
|
423
|
+
let oi = 0;
|
|
424
|
+
let oldLength = oldRetained.length;
|
|
412
425
|
for (let ni = 0, newLength = newChildren.length; ni < newLength; ni++) {
|
|
413
|
-
//
|
|
414
|
-
// deoptimizations.
|
|
426
|
+
// length checks to prevent index out of bounds deoptimizations.
|
|
415
427
|
let ret = oi >= oldLength ? undefined : oldRetained[oi];
|
|
416
428
|
let child = narrow(newChildren[ni]);
|
|
417
429
|
{
|
|
418
|
-
//
|
|
430
|
+
// aligning new children with old retainers
|
|
419
431
|
let oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
420
432
|
let newKey = typeof child === "object" ? child.key : undefined;
|
|
421
433
|
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
|
|
@@ -450,18 +462,19 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
450
462
|
// Updating
|
|
451
463
|
let value;
|
|
452
464
|
if (typeof child === "object") {
|
|
453
|
-
if (
|
|
454
|
-
ret.el = child;
|
|
455
|
-
value = getInflightValue(ret);
|
|
456
|
-
}
|
|
457
|
-
else if (child.tag === Copy) {
|
|
465
|
+
if (child.tag === Copy) {
|
|
458
466
|
value = getInflightValue(ret);
|
|
459
467
|
}
|
|
460
468
|
else {
|
|
461
469
|
let oldProps;
|
|
470
|
+
let static_ = false;
|
|
462
471
|
if (typeof ret === "object" && ret.el.tag === child.tag) {
|
|
463
472
|
oldProps = ret.el.props;
|
|
464
473
|
ret.el = child;
|
|
474
|
+
if (child.static_) {
|
|
475
|
+
value = getInflightValue(ret);
|
|
476
|
+
static_ = true;
|
|
477
|
+
}
|
|
465
478
|
}
|
|
466
479
|
else {
|
|
467
480
|
if (typeof ret === "object") {
|
|
@@ -469,19 +482,28 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
469
482
|
}
|
|
470
483
|
const fallback = ret;
|
|
471
484
|
ret = new Retainer(child);
|
|
472
|
-
ret.
|
|
485
|
+
ret.fallbackValue = fallback;
|
|
473
486
|
}
|
|
474
|
-
if (
|
|
475
|
-
|
|
487
|
+
if (static_) ;
|
|
488
|
+
else if (child.tag === Raw) {
|
|
489
|
+
value = hydrationBlock
|
|
490
|
+
? hydrationBlock.then(() => updateRaw(renderer, ret, scope, oldProps, hydrationData))
|
|
491
|
+
: updateRaw(renderer, ret, scope, oldProps, hydrationData);
|
|
476
492
|
}
|
|
477
493
|
else if (child.tag === Fragment) {
|
|
478
|
-
value =
|
|
494
|
+
value = hydrationBlock
|
|
495
|
+
? hydrationBlock.then(() => updateFragment(renderer, root, host, ctx, scope, ret, hydrationData))
|
|
496
|
+
: updateFragment(renderer, root, host, ctx, scope, ret, hydrationData);
|
|
479
497
|
}
|
|
480
498
|
else if (typeof child.tag === "function") {
|
|
481
|
-
value =
|
|
499
|
+
value = hydrationBlock
|
|
500
|
+
? hydrationBlock.then(() => updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData))
|
|
501
|
+
: updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData);
|
|
482
502
|
}
|
|
483
503
|
else {
|
|
484
|
-
value =
|
|
504
|
+
value = hydrationBlock
|
|
505
|
+
? hydrationBlock.then(() => updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData))
|
|
506
|
+
: updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData);
|
|
485
507
|
}
|
|
486
508
|
}
|
|
487
509
|
const ref = child.ref;
|
|
@@ -493,9 +515,14 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
493
515
|
return value;
|
|
494
516
|
});
|
|
495
517
|
}
|
|
518
|
+
if (hydrationData !== undefined) {
|
|
519
|
+
hydrationBlock = value;
|
|
520
|
+
}
|
|
496
521
|
}
|
|
497
|
-
else
|
|
498
|
-
ref
|
|
522
|
+
else {
|
|
523
|
+
if (typeof ref === "function") {
|
|
524
|
+
ref(renderer.read(value));
|
|
525
|
+
}
|
|
499
526
|
}
|
|
500
527
|
}
|
|
501
528
|
else {
|
|
@@ -504,7 +531,7 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
504
531
|
(graveyard = graveyard || []).push(ret);
|
|
505
532
|
}
|
|
506
533
|
if (typeof child === "string") {
|
|
507
|
-
value = ret = renderer.
|
|
534
|
+
value = ret = renderer.text(child, scope, hydrationData);
|
|
508
535
|
}
|
|
509
536
|
else {
|
|
510
537
|
ret = undefined;
|
|
@@ -537,27 +564,29 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
537
564
|
childValues1,
|
|
538
565
|
new Promise((resolve) => (onChildValues = resolve)),
|
|
539
566
|
]);
|
|
540
|
-
if (parent.
|
|
541
|
-
parent.
|
|
567
|
+
if (parent.onNextValues) {
|
|
568
|
+
parent.onNextValues(childValues1);
|
|
542
569
|
}
|
|
543
|
-
parent.
|
|
570
|
+
parent.onNextValues = onChildValues;
|
|
544
571
|
return childValues1.then((childValues) => {
|
|
545
|
-
parent.
|
|
572
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
546
573
|
return normalize(childValues);
|
|
547
574
|
});
|
|
548
575
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
576
|
+
else {
|
|
577
|
+
if (graveyard) {
|
|
578
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
579
|
+
unmount(renderer, host, ctx, graveyard[i]);
|
|
580
|
+
}
|
|
552
581
|
}
|
|
582
|
+
if (parent.onNextValues) {
|
|
583
|
+
parent.onNextValues(values);
|
|
584
|
+
parent.onNextValues = undefined;
|
|
585
|
+
}
|
|
586
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
587
|
+
// We can assert there are no promises in the array because isAsync is false
|
|
588
|
+
return normalize(values);
|
|
553
589
|
}
|
|
554
|
-
if (parent.onCommit) {
|
|
555
|
-
parent.onCommit(values);
|
|
556
|
-
parent.onCommit = undefined;
|
|
557
|
-
}
|
|
558
|
-
parent.inflight = parent.fallback = undefined;
|
|
559
|
-
// We can assert there are no promises in the array because isAsync is false
|
|
560
|
-
return normalize(values);
|
|
561
590
|
}
|
|
562
591
|
function createChildrenByKey(children, offset) {
|
|
563
592
|
const childrenByKey = new Map();
|
|
@@ -577,58 +606,69 @@ function getInflightValue(child) {
|
|
|
577
606
|
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
578
607
|
return ctx.inflightValue;
|
|
579
608
|
}
|
|
580
|
-
else if (child.
|
|
581
|
-
return child.
|
|
609
|
+
else if (child.inflightValue) {
|
|
610
|
+
return child.inflightValue;
|
|
582
611
|
}
|
|
583
612
|
return getValue(child);
|
|
584
613
|
}
|
|
585
|
-
function updateRaw(renderer, ret, scope, oldProps) {
|
|
614
|
+
function updateRaw(renderer, ret, scope, oldProps, hydrationData) {
|
|
586
615
|
const props = ret.el.props;
|
|
587
|
-
if (
|
|
588
|
-
|
|
589
|
-
ret.value = renderer.parse(props.value, scope);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
ret.value = props.value;
|
|
616
|
+
if (!oldProps || oldProps.value !== props.value) {
|
|
617
|
+
ret.value = renderer.raw(props.value, scope, hydrationData);
|
|
594
618
|
}
|
|
595
619
|
return ret.value;
|
|
596
620
|
}
|
|
597
|
-
function updateFragment(renderer, root, host, ctx, scope, ret) {
|
|
598
|
-
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children);
|
|
621
|
+
function updateFragment(renderer, root, host, ctx, scope, ret, hydrationData) {
|
|
622
|
+
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children, hydrationData);
|
|
599
623
|
if (isPromiseLike(childValues)) {
|
|
600
|
-
ret.
|
|
601
|
-
return ret.
|
|
624
|
+
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
625
|
+
return ret.inflightValue;
|
|
602
626
|
}
|
|
603
627
|
return unwrap(childValues);
|
|
604
628
|
}
|
|
605
|
-
function updateHost(renderer, root, ctx, scope, ret, oldProps) {
|
|
629
|
+
function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
|
|
606
630
|
const el = ret.el;
|
|
607
631
|
const tag = el.tag;
|
|
632
|
+
let hydrationValue;
|
|
608
633
|
if (el.tag === Portal) {
|
|
609
634
|
root = ret.value = el.props.root;
|
|
610
635
|
}
|
|
611
|
-
else
|
|
612
|
-
|
|
613
|
-
|
|
636
|
+
else {
|
|
637
|
+
if (hydrationData !== undefined) {
|
|
638
|
+
const value = hydrationData.children.shift();
|
|
639
|
+
hydrationValue = value;
|
|
640
|
+
}
|
|
614
641
|
}
|
|
615
642
|
scope = renderer.scope(scope, tag, el.props);
|
|
616
|
-
|
|
643
|
+
let childHydrationData;
|
|
644
|
+
if (hydrationValue != null && typeof hydrationValue !== "string") {
|
|
645
|
+
childHydrationData = renderer.hydrate(tag, hydrationValue, el.props);
|
|
646
|
+
if (childHydrationData === undefined) {
|
|
647
|
+
hydrationValue = undefined;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children, childHydrationData);
|
|
617
651
|
if (isPromiseLike(childValues)) {
|
|
618
|
-
ret.
|
|
619
|
-
return ret.
|
|
652
|
+
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
|
|
653
|
+
return ret.inflightValue;
|
|
620
654
|
}
|
|
621
|
-
return commitHost(renderer, scope, ret, childValues, oldProps);
|
|
655
|
+
return commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue);
|
|
622
656
|
}
|
|
623
|
-
function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
657
|
+
function commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue) {
|
|
624
658
|
const tag = ret.el.tag;
|
|
625
|
-
|
|
659
|
+
let value = hydrationValue || ret.value;
|
|
626
660
|
let props = ret.el.props;
|
|
627
661
|
let copied;
|
|
628
662
|
if (tag !== Portal) {
|
|
663
|
+
if (value == null) {
|
|
664
|
+
// This assumes that renderer.create does not return nullish values.
|
|
665
|
+
value = ret.value = renderer.create(tag, props, scope);
|
|
666
|
+
}
|
|
629
667
|
for (const propName in { ...oldProps, ...props }) {
|
|
630
668
|
const propValue = props[propName];
|
|
631
669
|
if (propValue === Copy) {
|
|
670
|
+
// TODO: The Copy tag doubles as a way to skip the patching of a prop.
|
|
671
|
+
// Not sure about this feature. Should probably be removed.
|
|
632
672
|
(copied = copied || new Set()).add(propName);
|
|
633
673
|
}
|
|
634
674
|
else if (propName !== "children") {
|
|
@@ -643,8 +683,8 @@ function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
|
643
683
|
}
|
|
644
684
|
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
|
|
645
685
|
}
|
|
646
|
-
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.
|
|
647
|
-
ret.
|
|
686
|
+
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
687
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
648
688
|
if (tag === Portal) {
|
|
649
689
|
flush(renderer, ret.value);
|
|
650
690
|
return;
|
|
@@ -691,7 +731,7 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
691
731
|
}
|
|
692
732
|
else if (ret.el.tag === Portal) {
|
|
693
733
|
host = ret;
|
|
694
|
-
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.
|
|
734
|
+
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
|
|
695
735
|
flush(renderer, host.value);
|
|
696
736
|
}
|
|
697
737
|
else if (ret.el.tag !== Fragment) {
|
|
@@ -715,38 +755,43 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
715
755
|
}
|
|
716
756
|
/*** CONTEXT FLAGS ***/
|
|
717
757
|
/**
|
|
718
|
-
* A flag which is
|
|
719
|
-
*
|
|
720
|
-
*
|
|
758
|
+
* A flag which is true when the component is initialized or updated by an
|
|
759
|
+
* ancestor component or the root render call.
|
|
760
|
+
*
|
|
761
|
+
* Used to determine things like whether the nearest host ancestor needs to be
|
|
762
|
+
* rearranged.
|
|
721
763
|
*/
|
|
722
764
|
const IsUpdating = 1 << 0;
|
|
723
765
|
/**
|
|
724
|
-
* A flag which is
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* overflow or a generator error.
|
|
766
|
+
* A flag which is true when the component is synchronously executing.
|
|
767
|
+
*
|
|
768
|
+
* Used to guard against components triggering stack overflow or generator error.
|
|
728
769
|
*/
|
|
729
|
-
const
|
|
770
|
+
const IsSyncExecuting = 1 << 1;
|
|
730
771
|
/**
|
|
731
|
-
* A flag
|
|
732
|
-
* iterators without a yield.
|
|
772
|
+
* A flag which is true when the component is in a for...of loop.
|
|
733
773
|
*/
|
|
734
|
-
const
|
|
774
|
+
const IsInForOfLoop = 1 << 2;
|
|
735
775
|
/**
|
|
736
|
-
* A flag
|
|
737
|
-
* onavailable (_oa) callback to mark whether new props can be pulled via the
|
|
738
|
-
* context async iterator. See the Symbol.asyncIterator method and the
|
|
739
|
-
* resumeCtxIterator function.
|
|
776
|
+
* A flag which is true when the component is in a for await...of loop.
|
|
740
777
|
*/
|
|
741
|
-
const
|
|
778
|
+
const IsInForAwaitOfLoop = 1 << 3;
|
|
742
779
|
/**
|
|
743
|
-
* A flag which is
|
|
744
|
-
*
|
|
745
|
-
*
|
|
780
|
+
* A flag which is true when the component starts the render loop but has not
|
|
781
|
+
* yielded yet.
|
|
782
|
+
*
|
|
783
|
+
* Used to make sure that components yield at least once per loop.
|
|
784
|
+
*/
|
|
785
|
+
const NeedsToYield = 1 << 4;
|
|
786
|
+
/**
|
|
787
|
+
* A flag used by async generator components in conjunction with the
|
|
788
|
+
* onAvailable callback to mark whether new props can be pulled via the context
|
|
789
|
+
* async iterator. See the Symbol.asyncIterator method and the
|
|
790
|
+
* resumeCtxIterator function.
|
|
746
791
|
*/
|
|
747
|
-
const
|
|
792
|
+
const PropsAvailable = 1 << 5;
|
|
748
793
|
/**
|
|
749
|
-
* A flag which is set when a
|
|
794
|
+
* A flag which is set when a component errors.
|
|
750
795
|
*
|
|
751
796
|
* NOTE: This is mainly used to prevent some false positives in component
|
|
752
797
|
* yields or returns undefined warnings. The reason we’re using this versus
|
|
@@ -754,28 +799,28 @@ const IsDone = 1 << 4;
|
|
|
754
799
|
* sync generator child) where synchronous code causes a stack overflow error
|
|
755
800
|
* in a non-deterministic way. Deeply disturbing stuff.
|
|
756
801
|
*/
|
|
757
|
-
const IsErrored = 1 <<
|
|
802
|
+
const IsErrored = 1 << 6;
|
|
758
803
|
/**
|
|
759
804
|
* A flag which is set when the component is unmounted. Unmounted components
|
|
760
805
|
* are no longer in the element tree and cannot refresh or rerender.
|
|
761
806
|
*/
|
|
762
|
-
const IsUnmounted = 1 <<
|
|
807
|
+
const IsUnmounted = 1 << 7;
|
|
763
808
|
/**
|
|
764
809
|
* A flag which indicates that the component is a sync generator component.
|
|
765
810
|
*/
|
|
766
|
-
const IsSyncGen = 1 <<
|
|
811
|
+
const IsSyncGen = 1 << 8;
|
|
767
812
|
/**
|
|
768
813
|
* A flag which indicates that the component is an async generator component.
|
|
769
814
|
*/
|
|
770
|
-
const IsAsyncGen = 1 <<
|
|
815
|
+
const IsAsyncGen = 1 << 9;
|
|
771
816
|
/**
|
|
772
817
|
* A flag which is set while schedule callbacks are called.
|
|
773
818
|
*/
|
|
774
|
-
const IsScheduling = 1 <<
|
|
819
|
+
const IsScheduling = 1 << 10;
|
|
775
820
|
/**
|
|
776
821
|
* A flag which is set when a schedule callback calls refresh.
|
|
777
822
|
*/
|
|
778
|
-
const IsSchedulingRefresh = 1 <<
|
|
823
|
+
const IsSchedulingRefresh = 1 << 11;
|
|
779
824
|
const provisionMaps = new WeakMap();
|
|
780
825
|
const scheduleMap = new WeakMap();
|
|
781
826
|
const cleanupMap = new WeakMap();
|
|
@@ -783,12 +828,12 @@ const cleanupMap = new WeakMap();
|
|
|
783
828
|
const flushMaps = new WeakMap();
|
|
784
829
|
/**
|
|
785
830
|
* @internal
|
|
786
|
-
* The internal class which holds
|
|
831
|
+
* The internal class which holds context data.
|
|
787
832
|
*/
|
|
788
833
|
class ContextImpl {
|
|
789
834
|
constructor(renderer, root, host, parent, scope, ret) {
|
|
790
835
|
this.f = 0;
|
|
791
|
-
this.
|
|
836
|
+
this.owner = new Context(this);
|
|
792
837
|
this.renderer = renderer;
|
|
793
838
|
this.root = root;
|
|
794
839
|
this.host = host;
|
|
@@ -800,10 +845,11 @@ class ContextImpl {
|
|
|
800
845
|
this.inflightValue = undefined;
|
|
801
846
|
this.enqueuedBlock = undefined;
|
|
802
847
|
this.enqueuedValue = undefined;
|
|
803
|
-
this.
|
|
848
|
+
this.onProps = undefined;
|
|
849
|
+
this.onPropsRequested = undefined;
|
|
804
850
|
}
|
|
805
851
|
}
|
|
806
|
-
const
|
|
852
|
+
const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
807
853
|
/**
|
|
808
854
|
* A class which is instantiated and passed to every component as its this
|
|
809
855
|
* value. Contexts form a tree just like elements and all components in the
|
|
@@ -817,8 +863,10 @@ const $ContextImpl = Symbol.for("crank.ContextImpl");
|
|
|
817
863
|
* schedule and cleanup callbacks.
|
|
818
864
|
*/
|
|
819
865
|
class Context {
|
|
866
|
+
// TODO: If we could make the constructor function take a nicer value, it
|
|
867
|
+
// would be useful for testing purposes.
|
|
820
868
|
constructor(impl) {
|
|
821
|
-
this[
|
|
869
|
+
this[_ContextImpl] = impl;
|
|
822
870
|
}
|
|
823
871
|
/**
|
|
824
872
|
* The current props of the associated element.
|
|
@@ -828,7 +876,7 @@ class Context {
|
|
|
828
876
|
* plugins or utilities which wrap contexts.
|
|
829
877
|
*/
|
|
830
878
|
get props() {
|
|
831
|
-
return this[
|
|
879
|
+
return this[_ContextImpl].ret.el.props;
|
|
832
880
|
}
|
|
833
881
|
// TODO: Should we rename this???
|
|
834
882
|
/**
|
|
@@ -839,45 +887,64 @@ class Context {
|
|
|
839
887
|
* mainly for plugins or utilities which wrap contexts.
|
|
840
888
|
*/
|
|
841
889
|
get value() {
|
|
842
|
-
return this[
|
|
890
|
+
return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
|
|
843
891
|
}
|
|
844
892
|
*[Symbol.iterator]() {
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
893
|
+
const ctx = this[_ContextImpl];
|
|
894
|
+
try {
|
|
895
|
+
ctx.f |= IsInForOfLoop;
|
|
896
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
897
|
+
if (ctx.f & NeedsToYield) {
|
|
898
|
+
throw new Error("Context iterated twice without a yield");
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
ctx.f |= NeedsToYield;
|
|
902
|
+
}
|
|
903
|
+
yield ctx.ret.el.props;
|
|
852
904
|
}
|
|
853
|
-
|
|
854
|
-
|
|
905
|
+
}
|
|
906
|
+
finally {
|
|
907
|
+
ctx.f &= ~IsInForOfLoop;
|
|
855
908
|
}
|
|
856
909
|
}
|
|
857
910
|
async *[Symbol.asyncIterator]() {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
await new Promise((resolve) => (impl.onAvailable = resolve));
|
|
875
|
-
if (impl.f & IsDone) {
|
|
876
|
-
break;
|
|
911
|
+
const ctx = this[_ContextImpl];
|
|
912
|
+
if (ctx.f & IsSyncGen) {
|
|
913
|
+
throw new Error("Use for...of in sync generator components");
|
|
914
|
+
}
|
|
915
|
+
try {
|
|
916
|
+
ctx.f |= IsInForAwaitOfLoop;
|
|
917
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
918
|
+
if (ctx.f & NeedsToYield) {
|
|
919
|
+
throw new Error("Context iterated twice without a yield");
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
ctx.f |= NeedsToYield;
|
|
923
|
+
}
|
|
924
|
+
if (ctx.f & PropsAvailable) {
|
|
925
|
+
ctx.f &= ~PropsAvailable;
|
|
926
|
+
yield ctx.ret.el.props;
|
|
877
927
|
}
|
|
928
|
+
else {
|
|
929
|
+
const props = await new Promise((resolve) => (ctx.onProps = resolve));
|
|
930
|
+
if (ctx.f & IsUnmounted) {
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
yield props;
|
|
934
|
+
}
|
|
935
|
+
if (ctx.onPropsRequested) {
|
|
936
|
+
ctx.onPropsRequested();
|
|
937
|
+
ctx.onPropsRequested = undefined;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
finally {
|
|
942
|
+
ctx.f &= ~IsInForAwaitOfLoop;
|
|
943
|
+
if (ctx.onPropsRequested) {
|
|
944
|
+
ctx.onPropsRequested();
|
|
945
|
+
ctx.onPropsRequested = undefined;
|
|
878
946
|
}
|
|
879
|
-
|
|
880
|
-
} while (!(impl.f & IsDone));
|
|
947
|
+
}
|
|
881
948
|
}
|
|
882
949
|
/**
|
|
883
950
|
* Re-executes a component.
|
|
@@ -892,32 +959,31 @@ class Context {
|
|
|
892
959
|
* async iterator to suspend.
|
|
893
960
|
*/
|
|
894
961
|
refresh() {
|
|
895
|
-
const
|
|
896
|
-
if (
|
|
962
|
+
const ctx = this[_ContextImpl];
|
|
963
|
+
if (ctx.f & IsUnmounted) {
|
|
897
964
|
console.error("Component is unmounted");
|
|
898
|
-
return
|
|
965
|
+
return ctx.renderer.read(undefined);
|
|
899
966
|
}
|
|
900
|
-
else if (
|
|
967
|
+
else if (ctx.f & IsSyncExecuting) {
|
|
901
968
|
console.error("Component is already executing");
|
|
902
969
|
return this.value;
|
|
903
970
|
}
|
|
904
|
-
|
|
905
|
-
const value = runComponent(impl);
|
|
971
|
+
const value = enqueueComponentRun(ctx);
|
|
906
972
|
if (isPromiseLike(value)) {
|
|
907
|
-
return value.then((value) =>
|
|
973
|
+
return value.then((value) => ctx.renderer.read(value));
|
|
908
974
|
}
|
|
909
|
-
return
|
|
975
|
+
return ctx.renderer.read(value);
|
|
910
976
|
}
|
|
911
977
|
/**
|
|
912
978
|
* Registers a callback which fires when the component commits. Will only
|
|
913
979
|
* fire once per callback and update.
|
|
914
980
|
*/
|
|
915
981
|
schedule(callback) {
|
|
916
|
-
const
|
|
917
|
-
let callbacks = scheduleMap.get(
|
|
982
|
+
const ctx = this[_ContextImpl];
|
|
983
|
+
let callbacks = scheduleMap.get(ctx);
|
|
918
984
|
if (!callbacks) {
|
|
919
985
|
callbacks = new Set();
|
|
920
|
-
scheduleMap.set(
|
|
986
|
+
scheduleMap.set(ctx, callbacks);
|
|
921
987
|
}
|
|
922
988
|
callbacks.add(callback);
|
|
923
989
|
}
|
|
@@ -926,19 +992,19 @@ class Context {
|
|
|
926
992
|
* rendered into the root. Will only fire once per callback and render.
|
|
927
993
|
*/
|
|
928
994
|
flush(callback) {
|
|
929
|
-
const
|
|
930
|
-
if (typeof
|
|
995
|
+
const ctx = this[_ContextImpl];
|
|
996
|
+
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
931
997
|
return;
|
|
932
998
|
}
|
|
933
|
-
let flushMap = flushMaps.get(
|
|
999
|
+
let flushMap = flushMaps.get(ctx.root);
|
|
934
1000
|
if (!flushMap) {
|
|
935
1001
|
flushMap = new Map();
|
|
936
|
-
flushMaps.set(
|
|
1002
|
+
flushMaps.set(ctx.root, flushMap);
|
|
937
1003
|
}
|
|
938
|
-
let callbacks = flushMap.get(
|
|
1004
|
+
let callbacks = flushMap.get(ctx);
|
|
939
1005
|
if (!callbacks) {
|
|
940
1006
|
callbacks = new Set();
|
|
941
|
-
flushMap.set(
|
|
1007
|
+
flushMap.set(ctx, callbacks);
|
|
942
1008
|
}
|
|
943
1009
|
callbacks.add(callback);
|
|
944
1010
|
}
|
|
@@ -947,45 +1013,45 @@ class Context {
|
|
|
947
1013
|
* fire once per callback.
|
|
948
1014
|
*/
|
|
949
1015
|
cleanup(callback) {
|
|
950
|
-
const
|
|
951
|
-
let callbacks = cleanupMap.get(
|
|
1016
|
+
const ctx = this[_ContextImpl];
|
|
1017
|
+
let callbacks = cleanupMap.get(ctx);
|
|
952
1018
|
if (!callbacks) {
|
|
953
1019
|
callbacks = new Set();
|
|
954
|
-
cleanupMap.set(
|
|
1020
|
+
cleanupMap.set(ctx, callbacks);
|
|
955
1021
|
}
|
|
956
1022
|
callbacks.add(callback);
|
|
957
1023
|
}
|
|
958
1024
|
consume(key) {
|
|
959
|
-
for (let
|
|
960
|
-
const provisions = provisionMaps.get(
|
|
1025
|
+
for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
1026
|
+
const provisions = provisionMaps.get(ctx);
|
|
961
1027
|
if (provisions && provisions.has(key)) {
|
|
962
1028
|
return provisions.get(key);
|
|
963
1029
|
}
|
|
964
1030
|
}
|
|
965
1031
|
}
|
|
966
1032
|
provide(key, value) {
|
|
967
|
-
const
|
|
968
|
-
let provisions = provisionMaps.get(
|
|
1033
|
+
const ctx = this[_ContextImpl];
|
|
1034
|
+
let provisions = provisionMaps.get(ctx);
|
|
969
1035
|
if (!provisions) {
|
|
970
1036
|
provisions = new Map();
|
|
971
|
-
provisionMaps.set(
|
|
1037
|
+
provisionMaps.set(ctx, provisions);
|
|
972
1038
|
}
|
|
973
1039
|
provisions.set(key, value);
|
|
974
1040
|
}
|
|
975
1041
|
addEventListener(type, listener, options) {
|
|
976
|
-
const
|
|
1042
|
+
const ctx = this[_ContextImpl];
|
|
977
1043
|
let listeners;
|
|
978
1044
|
if (!isListenerOrListenerObject(listener)) {
|
|
979
1045
|
return;
|
|
980
1046
|
}
|
|
981
1047
|
else {
|
|
982
|
-
const listeners1 = listenersMap.get(
|
|
1048
|
+
const listeners1 = listenersMap.get(ctx);
|
|
983
1049
|
if (listeners1) {
|
|
984
1050
|
listeners = listeners1;
|
|
985
1051
|
}
|
|
986
1052
|
else {
|
|
987
1053
|
listeners = [];
|
|
988
|
-
listenersMap.set(
|
|
1054
|
+
listenersMap.set(ctx, listeners);
|
|
989
1055
|
}
|
|
990
1056
|
}
|
|
991
1057
|
options = normalizeListenerOptions(options);
|
|
@@ -996,7 +1062,7 @@ class Context {
|
|
|
996
1062
|
else {
|
|
997
1063
|
callback = listener;
|
|
998
1064
|
}
|
|
999
|
-
const record = { type,
|
|
1065
|
+
const record = { type, listener, callback, options };
|
|
1000
1066
|
if (options.once) {
|
|
1001
1067
|
record.callback = function () {
|
|
1002
1068
|
const i = listeners.indexOf(record);
|
|
@@ -1013,15 +1079,15 @@ class Context {
|
|
|
1013
1079
|
}
|
|
1014
1080
|
listeners.push(record);
|
|
1015
1081
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1016
|
-
for (const value of getChildValues(
|
|
1082
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1017
1083
|
if (isEventTarget(value)) {
|
|
1018
1084
|
value.addEventListener(record.type, record.callback, record.options);
|
|
1019
1085
|
}
|
|
1020
1086
|
}
|
|
1021
1087
|
}
|
|
1022
1088
|
removeEventListener(type, listener, options) {
|
|
1023
|
-
const
|
|
1024
|
-
const listeners = listenersMap.get(
|
|
1089
|
+
const ctx = this[_ContextImpl];
|
|
1090
|
+
const listeners = listenersMap.get(ctx);
|
|
1025
1091
|
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1026
1092
|
return;
|
|
1027
1093
|
}
|
|
@@ -1035,16 +1101,16 @@ class Context {
|
|
|
1035
1101
|
const record = listeners[i];
|
|
1036
1102
|
listeners.splice(i, 1);
|
|
1037
1103
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1038
|
-
for (const value of getChildValues(
|
|
1104
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1039
1105
|
if (isEventTarget(value)) {
|
|
1040
1106
|
value.removeEventListener(record.type, record.callback, record.options);
|
|
1041
1107
|
}
|
|
1042
1108
|
}
|
|
1043
1109
|
}
|
|
1044
1110
|
dispatchEvent(ev) {
|
|
1045
|
-
const
|
|
1111
|
+
const ctx = this[_ContextImpl];
|
|
1046
1112
|
const path = [];
|
|
1047
|
-
for (let parent =
|
|
1113
|
+
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1048
1114
|
path.push(parent);
|
|
1049
1115
|
}
|
|
1050
1116
|
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
@@ -1056,7 +1122,7 @@ class Context {
|
|
|
1056
1122
|
immediateCancelBubble = true;
|
|
1057
1123
|
return stopImmediatePropagation.call(ev);
|
|
1058
1124
|
});
|
|
1059
|
-
setEventProperty(ev, "target",
|
|
1125
|
+
setEventProperty(ev, "target", ctx.owner);
|
|
1060
1126
|
// The only possible errors in this block are errors thrown by callbacks,
|
|
1061
1127
|
// and dispatchEvent will only log these errors rather than throwing
|
|
1062
1128
|
// them. Therefore, we place all code in a try block, log errors in the
|
|
@@ -1065,18 +1131,21 @@ class Context {
|
|
|
1065
1131
|
// Each early return within the try block returns true because while the
|
|
1066
1132
|
// return value is overridden in the finally block, TypeScript
|
|
1067
1133
|
// (justifiably) does not recognize the unsafe return statement.
|
|
1068
|
-
//
|
|
1069
|
-
// TODO: Run all callbacks even if one of them errors
|
|
1070
1134
|
try {
|
|
1071
1135
|
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1072
1136
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
1073
1137
|
const target = path[i];
|
|
1074
1138
|
const listeners = listenersMap.get(target);
|
|
1075
1139
|
if (listeners) {
|
|
1076
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1140
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1077
1141
|
for (const record of listeners) {
|
|
1078
1142
|
if (record.type === ev.type && record.options.capture) {
|
|
1079
|
-
|
|
1143
|
+
try {
|
|
1144
|
+
record.callback.call(target.owner, ev);
|
|
1145
|
+
}
|
|
1146
|
+
catch (err) {
|
|
1147
|
+
console.error(err);
|
|
1148
|
+
}
|
|
1080
1149
|
if (immediateCancelBubble) {
|
|
1081
1150
|
return true;
|
|
1082
1151
|
}
|
|
@@ -1088,13 +1157,25 @@ class Context {
|
|
|
1088
1157
|
}
|
|
1089
1158
|
}
|
|
1090
1159
|
{
|
|
1091
|
-
|
|
1160
|
+
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1161
|
+
setEventProperty(ev, "currentTarget", ctx.owner);
|
|
1162
|
+
const propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1163
|
+
if (propCallback != null) {
|
|
1164
|
+
propCallback(ev);
|
|
1165
|
+
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1166
|
+
return true;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
const listeners = listenersMap.get(ctx);
|
|
1092
1170
|
if (listeners) {
|
|
1093
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1094
|
-
setEventProperty(ev, "currentTarget", impl.ctx);
|
|
1095
1171
|
for (const record of listeners) {
|
|
1096
1172
|
if (record.type === ev.type) {
|
|
1097
|
-
|
|
1173
|
+
try {
|
|
1174
|
+
record.callback.call(ctx.owner, ev);
|
|
1175
|
+
}
|
|
1176
|
+
catch (err) {
|
|
1177
|
+
console.error(err);
|
|
1178
|
+
}
|
|
1098
1179
|
if (immediateCancelBubble) {
|
|
1099
1180
|
return true;
|
|
1100
1181
|
}
|
|
@@ -1111,10 +1192,15 @@ class Context {
|
|
|
1111
1192
|
const target = path[i];
|
|
1112
1193
|
const listeners = listenersMap.get(target);
|
|
1113
1194
|
if (listeners) {
|
|
1114
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1195
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1115
1196
|
for (const record of listeners) {
|
|
1116
1197
|
if (record.type === ev.type && !record.options.capture) {
|
|
1117
|
-
|
|
1198
|
+
try {
|
|
1199
|
+
record.callback.call(target.owner, ev);
|
|
1200
|
+
}
|
|
1201
|
+
catch (err) {
|
|
1202
|
+
console.error(err);
|
|
1203
|
+
}
|
|
1118
1204
|
if (immediateCancelBubble) {
|
|
1119
1205
|
return true;
|
|
1120
1206
|
}
|
|
@@ -1127,10 +1213,6 @@ class Context {
|
|
|
1127
1213
|
}
|
|
1128
1214
|
}
|
|
1129
1215
|
}
|
|
1130
|
-
catch (err) {
|
|
1131
|
-
// TODO: Use setTimeout to rethrow the error.
|
|
1132
|
-
console.error(err);
|
|
1133
|
-
}
|
|
1134
1216
|
finally {
|
|
1135
1217
|
setEventProperty(ev, "eventPhase", NONE);
|
|
1136
1218
|
setEventProperty(ev, "currentTarget", null);
|
|
@@ -1148,42 +1230,47 @@ function ctxContains(parent, child) {
|
|
|
1148
1230
|
}
|
|
1149
1231
|
return false;
|
|
1150
1232
|
}
|
|
1151
|
-
function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
|
|
1233
|
+
function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
|
|
1152
1234
|
let ctx;
|
|
1153
1235
|
if (oldProps) {
|
|
1154
1236
|
ctx = ret.ctx;
|
|
1155
|
-
if (ctx.f &
|
|
1237
|
+
if (ctx.f & IsSyncExecuting) {
|
|
1156
1238
|
console.error("Component is already executing");
|
|
1157
|
-
return ret.
|
|
1239
|
+
return ret.cachedChildValues;
|
|
1158
1240
|
}
|
|
1159
1241
|
}
|
|
1160
1242
|
else {
|
|
1161
1243
|
ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
|
|
1162
1244
|
}
|
|
1163
1245
|
ctx.f |= IsUpdating;
|
|
1164
|
-
|
|
1165
|
-
return runComponent(ctx);
|
|
1246
|
+
return enqueueComponentRun(ctx, hydrationData);
|
|
1166
1247
|
}
|
|
1167
|
-
function updateComponentChildren(ctx, children) {
|
|
1168
|
-
if (ctx.f & IsUnmounted
|
|
1248
|
+
function updateComponentChildren(ctx, children, hydrationData) {
|
|
1249
|
+
if (ctx.f & IsUnmounted) {
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
else if (ctx.f & IsErrored) {
|
|
1253
|
+
// This branch is necessary for some race conditions where this function is
|
|
1254
|
+
// called after iterator.throw() in async generator components.
|
|
1169
1255
|
return;
|
|
1170
1256
|
}
|
|
1171
1257
|
else if (children === undefined) {
|
|
1172
1258
|
console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
|
|
1173
1259
|
}
|
|
1174
1260
|
let childValues;
|
|
1175
|
-
// We set the isExecuting flag in case a child component dispatches an event
|
|
1176
|
-
// which bubbles to this component and causes a synchronous refresh().
|
|
1177
|
-
ctx.f |= IsExecuting;
|
|
1178
1261
|
try {
|
|
1179
|
-
|
|
1262
|
+
// TODO: WAT
|
|
1263
|
+
// We set the isExecuting flag in case a child component dispatches an event
|
|
1264
|
+
// which bubbles to this component and causes a synchronous refresh().
|
|
1265
|
+
ctx.f |= IsSyncExecuting;
|
|
1266
|
+
childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children), hydrationData);
|
|
1180
1267
|
}
|
|
1181
1268
|
finally {
|
|
1182
|
-
ctx.f &= ~
|
|
1269
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1183
1270
|
}
|
|
1184
1271
|
if (isPromiseLike(childValues)) {
|
|
1185
|
-
ctx.ret.
|
|
1186
|
-
return ctx.ret.
|
|
1272
|
+
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1273
|
+
return ctx.ret.inflightValue;
|
|
1187
1274
|
}
|
|
1188
1275
|
return commitComponent(ctx, childValues);
|
|
1189
1276
|
}
|
|
@@ -1203,8 +1290,8 @@ function commitComponent(ctx, values) {
|
|
|
1203
1290
|
}
|
|
1204
1291
|
}
|
|
1205
1292
|
}
|
|
1206
|
-
const oldValues = wrap(ctx.ret.
|
|
1207
|
-
let value = (ctx.ret.
|
|
1293
|
+
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1294
|
+
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1208
1295
|
if (ctx.f & IsScheduling) {
|
|
1209
1296
|
ctx.f |= IsSchedulingRefresh;
|
|
1210
1297
|
}
|
|
@@ -1212,7 +1299,7 @@ function commitComponent(ctx, values) {
|
|
|
1212
1299
|
// If we’re not updating the component, which happens when components are
|
|
1213
1300
|
// refreshed, or when async generator components iterate, we have to do a
|
|
1214
1301
|
// little bit housekeeping when a component’s child values have changed.
|
|
1215
|
-
if (!
|
|
1302
|
+
if (!arrayEqual(oldValues, values)) {
|
|
1216
1303
|
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1217
1304
|
if (records.length) {
|
|
1218
1305
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -1227,7 +1314,7 @@ function commitComponent(ctx, values) {
|
|
|
1227
1314
|
}
|
|
1228
1315
|
// rearranging the nearest ancestor host element
|
|
1229
1316
|
const host = ctx.host;
|
|
1230
|
-
const oldHostValues = wrap(host.
|
|
1317
|
+
const oldHostValues = wrap(host.cachedChildValues);
|
|
1231
1318
|
invalidate(ctx, host);
|
|
1232
1319
|
const hostValues = getChildValues(host);
|
|
1233
1320
|
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
@@ -1256,50 +1343,79 @@ function commitComponent(ctx, values) {
|
|
|
1256
1343
|
}
|
|
1257
1344
|
function invalidate(ctx, host) {
|
|
1258
1345
|
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1259
|
-
parent.ret.
|
|
1346
|
+
parent.ret.cachedChildValues = undefined;
|
|
1260
1347
|
}
|
|
1261
|
-
host.
|
|
1348
|
+
host.cachedChildValues = undefined;
|
|
1262
1349
|
}
|
|
1263
|
-
function
|
|
1264
|
-
if (
|
|
1350
|
+
function arrayEqual(arr1, arr2) {
|
|
1351
|
+
if (arr1.length !== arr2.length) {
|
|
1265
1352
|
return false;
|
|
1266
1353
|
}
|
|
1267
|
-
for (let i = 0; i <
|
|
1268
|
-
const value1 =
|
|
1269
|
-
const value2 =
|
|
1354
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
1355
|
+
const value1 = arr1[i];
|
|
1356
|
+
const value2 = arr2[i];
|
|
1270
1357
|
if (value1 !== value2) {
|
|
1271
1358
|
return false;
|
|
1272
1359
|
}
|
|
1273
1360
|
}
|
|
1274
1361
|
return true;
|
|
1275
1362
|
}
|
|
1276
|
-
/**
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1363
|
+
/** Enqueues and executes the component associated with the context. */
|
|
1364
|
+
function enqueueComponentRun(ctx, hydrationData) {
|
|
1365
|
+
if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
|
|
1366
|
+
if (hydrationData !== undefined) {
|
|
1367
|
+
throw new Error("Hydration error");
|
|
1368
|
+
}
|
|
1369
|
+
// This branch will run for non-initial renders of async generator
|
|
1370
|
+
// components when they are not in for...of loops. When in a for...of loop,
|
|
1371
|
+
// async generator components will behave normally.
|
|
1372
|
+
//
|
|
1373
|
+
// Async gen componennts can be in one of three states:
|
|
1374
|
+
//
|
|
1375
|
+
// 1. propsAvailable flag is true: "available"
|
|
1376
|
+
//
|
|
1377
|
+
// The component is suspended somewhere in the loop. When the component
|
|
1378
|
+
// reaches the bottom of the loop, it will run again with the next props.
|
|
1379
|
+
//
|
|
1380
|
+
// 2. onAvailable callback is defined: "suspended"
|
|
1381
|
+
//
|
|
1382
|
+
// The component has suspended at the bottom of the loop and is waiting
|
|
1383
|
+
// for new props.
|
|
1384
|
+
//
|
|
1385
|
+
// 3. neither 1 or 2: "Running"
|
|
1386
|
+
//
|
|
1387
|
+
// The component is suspended somewhere in the loop. When the component
|
|
1388
|
+
// reaches the bottom of the loop, it will suspend.
|
|
1389
|
+
//
|
|
1390
|
+
// Components will never be both available and suspended at
|
|
1391
|
+
// the same time.
|
|
1392
|
+
//
|
|
1393
|
+
// If the component is at the loop bottom, this means that the next value
|
|
1394
|
+
// produced by the component will have the most up to date props, so we can
|
|
1395
|
+
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1396
|
+
// the bottom of the loop to be reached before returning the inflight
|
|
1397
|
+
// value.
|
|
1398
|
+
const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
|
|
1399
|
+
resumePropsIterator(ctx);
|
|
1400
|
+
if (isAtLoopbottom) {
|
|
1401
|
+
if (ctx.inflightBlock == null) {
|
|
1402
|
+
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1403
|
+
}
|
|
1404
|
+
return ctx.inflightBlock.then(() => {
|
|
1405
|
+
ctx.inflightBlock = undefined;
|
|
1406
|
+
return ctx.inflightValue;
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
return ctx.inflightValue;
|
|
1410
|
+
}
|
|
1411
|
+
else if (!ctx.inflightBlock) {
|
|
1294
1412
|
try {
|
|
1295
|
-
const [block, value] =
|
|
1413
|
+
const [block, value] = runComponent(ctx, hydrationData);
|
|
1296
1414
|
if (block) {
|
|
1297
1415
|
ctx.inflightBlock = block
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
}
|
|
1302
|
-
})
|
|
1416
|
+
// TODO: there is some fuckery going on here related to async
|
|
1417
|
+
// generator components resuming when they’re meant to be returned.
|
|
1418
|
+
.then((v) => v)
|
|
1303
1419
|
.finally(() => advanceComponent(ctx));
|
|
1304
1420
|
// stepComponent will only return a block if the value is asynchronous
|
|
1305
1421
|
ctx.inflightValue = value;
|
|
@@ -1313,38 +1429,46 @@ function runComponent(ctx) {
|
|
|
1313
1429
|
throw err;
|
|
1314
1430
|
}
|
|
1315
1431
|
}
|
|
1316
|
-
else if (ctx.f & IsAsyncGen) {
|
|
1317
|
-
return ctx.inflightValue;
|
|
1318
|
-
}
|
|
1319
1432
|
else if (!ctx.enqueuedBlock) {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1433
|
+
if (hydrationData !== undefined) {
|
|
1434
|
+
throw new Error("Hydration error");
|
|
1435
|
+
}
|
|
1436
|
+
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1437
|
+
// the Promise constructor call here.
|
|
1438
|
+
let resolveEnqueuedBlock;
|
|
1439
|
+
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1440
|
+
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1323
1441
|
try {
|
|
1324
|
-
const [block, value] =
|
|
1325
|
-
resolve(value);
|
|
1442
|
+
const [block, value] = runComponent(ctx);
|
|
1326
1443
|
if (block) {
|
|
1327
|
-
|
|
1328
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1329
|
-
return propagateError(ctx.parent, err);
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1444
|
+
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1332
1445
|
}
|
|
1446
|
+
return value;
|
|
1333
1447
|
}
|
|
1334
1448
|
catch (err) {
|
|
1335
1449
|
if (!(ctx.f & IsUpdating)) {
|
|
1336
1450
|
return propagateError(ctx.parent, err);
|
|
1337
1451
|
}
|
|
1452
|
+
throw err;
|
|
1338
1453
|
}
|
|
1339
|
-
})
|
|
1340
|
-
.finally(() => advanceComponent(ctx));
|
|
1341
|
-
ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
|
|
1454
|
+
});
|
|
1342
1455
|
}
|
|
1343
1456
|
return ctx.enqueuedValue;
|
|
1344
1457
|
}
|
|
1458
|
+
/** Called when the inflight block promise settles. */
|
|
1459
|
+
function advanceComponent(ctx) {
|
|
1460
|
+
if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1464
|
+
ctx.inflightValue = ctx.enqueuedValue;
|
|
1465
|
+
ctx.enqueuedBlock = undefined;
|
|
1466
|
+
ctx.enqueuedValue = undefined;
|
|
1467
|
+
}
|
|
1345
1468
|
/**
|
|
1346
1469
|
* This function is responsible for executing the component and handling all
|
|
1347
|
-
* the different component types.
|
|
1470
|
+
* the different component types. We cannot identify whether a component is a
|
|
1471
|
+
* generator or async without calling it and inspecting the return value.
|
|
1348
1472
|
*
|
|
1349
1473
|
* @returns {[block, value]} A tuple where
|
|
1350
1474
|
* block - A possible promise which represents the duration during which the
|
|
@@ -1359,25 +1483,23 @@ function runComponent(ctx) {
|
|
|
1359
1483
|
* - Sync generator components block while any children are executing, because
|
|
1360
1484
|
* they are expected to only resume when they’ve actually rendered.
|
|
1361
1485
|
*/
|
|
1362
|
-
function
|
|
1486
|
+
function runComponent(ctx, hydrationData) {
|
|
1363
1487
|
const ret = ctx.ret;
|
|
1364
|
-
if (ctx.f & IsDone) {
|
|
1365
|
-
return [undefined, getValue(ret)];
|
|
1366
|
-
}
|
|
1367
1488
|
const initial = !ctx.iterator;
|
|
1368
1489
|
if (initial) {
|
|
1369
|
-
ctx
|
|
1490
|
+
resumePropsIterator(ctx);
|
|
1491
|
+
ctx.f |= IsSyncExecuting;
|
|
1370
1492
|
clearEventListeners(ctx);
|
|
1371
1493
|
let result;
|
|
1372
1494
|
try {
|
|
1373
|
-
result = ret.el.tag.call(ctx.
|
|
1495
|
+
result = ret.el.tag.call(ctx.owner, ret.el.props);
|
|
1374
1496
|
}
|
|
1375
1497
|
catch (err) {
|
|
1376
1498
|
ctx.f |= IsErrored;
|
|
1377
1499
|
throw err;
|
|
1378
1500
|
}
|
|
1379
1501
|
finally {
|
|
1380
|
-
ctx.f &= ~
|
|
1502
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1381
1503
|
}
|
|
1382
1504
|
if (isIteratorLike(result)) {
|
|
1383
1505
|
ctx.iterator = result;
|
|
@@ -1385,124 +1507,241 @@ function stepComponent(ctx) {
|
|
|
1385
1507
|
else if (isPromiseLike(result)) {
|
|
1386
1508
|
// async function component
|
|
1387
1509
|
const result1 = result instanceof Promise ? result : Promise.resolve(result);
|
|
1388
|
-
const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
|
|
1510
|
+
const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
|
|
1389
1511
|
ctx.f |= IsErrored;
|
|
1390
1512
|
throw err;
|
|
1391
1513
|
});
|
|
1392
|
-
return [result1, value];
|
|
1514
|
+
return [result1.catch(NOOP), value];
|
|
1393
1515
|
}
|
|
1394
1516
|
else {
|
|
1395
1517
|
// sync function component
|
|
1396
|
-
return [
|
|
1518
|
+
return [
|
|
1519
|
+
undefined,
|
|
1520
|
+
updateComponentChildren(ctx, result, hydrationData),
|
|
1521
|
+
];
|
|
1397
1522
|
}
|
|
1398
1523
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
// The argument passed to the first call to next is ignored.
|
|
1402
|
-
oldValue = undefined;
|
|
1403
|
-
}
|
|
1404
|
-
else if (ctx.ret.inflight) {
|
|
1405
|
-
// The value passed back into the generator as the argument to the next
|
|
1406
|
-
// method is a promise if an async generator component has async children.
|
|
1407
|
-
// Sync generator components only resume when their children have fulfilled
|
|
1408
|
-
// so the element’s inflight child values will never be defined.
|
|
1409
|
-
oldValue = ctx.ret.inflight.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1410
|
-
}
|
|
1411
|
-
else {
|
|
1412
|
-
oldValue = ctx.renderer.read(getValue(ret));
|
|
1524
|
+
else if (hydrationData !== undefined) {
|
|
1525
|
+
throw new Error("Hydration error");
|
|
1413
1526
|
}
|
|
1414
1527
|
let iteration;
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1528
|
+
if (initial) {
|
|
1529
|
+
try {
|
|
1530
|
+
ctx.f |= IsSyncExecuting;
|
|
1531
|
+
iteration = ctx.iterator.next();
|
|
1532
|
+
}
|
|
1533
|
+
catch (err) {
|
|
1534
|
+
ctx.f |= IsErrored;
|
|
1535
|
+
throw err;
|
|
1536
|
+
}
|
|
1537
|
+
finally {
|
|
1538
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1539
|
+
}
|
|
1540
|
+
if (isPromiseLike(iteration)) {
|
|
1541
|
+
ctx.f |= IsAsyncGen;
|
|
1542
|
+
}
|
|
1543
|
+
else {
|
|
1544
|
+
ctx.f |= IsSyncGen;
|
|
1545
|
+
}
|
|
1422
1546
|
}
|
|
1423
|
-
|
|
1424
|
-
ctx.f &= ~
|
|
1547
|
+
if (ctx.f & IsSyncGen) {
|
|
1548
|
+
ctx.f &= ~NeedsToYield;
|
|
1549
|
+
// sync generator component
|
|
1550
|
+
if (!initial) {
|
|
1551
|
+
try {
|
|
1552
|
+
ctx.f |= IsSyncExecuting;
|
|
1553
|
+
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1554
|
+
}
|
|
1555
|
+
catch (err) {
|
|
1556
|
+
ctx.f |= IsErrored;
|
|
1557
|
+
throw err;
|
|
1558
|
+
}
|
|
1559
|
+
finally {
|
|
1560
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (isPromiseLike(iteration)) {
|
|
1564
|
+
throw new Error("Mixed generator component");
|
|
1565
|
+
}
|
|
1566
|
+
if (iteration.done) {
|
|
1567
|
+
ctx.f &= ~IsSyncGen;
|
|
1568
|
+
ctx.iterator = undefined;
|
|
1569
|
+
}
|
|
1570
|
+
let value;
|
|
1571
|
+
try {
|
|
1572
|
+
value = updateComponentChildren(ctx,
|
|
1573
|
+
// Children can be void so we eliminate that here
|
|
1574
|
+
iteration.value, hydrationData);
|
|
1575
|
+
if (isPromiseLike(value)) {
|
|
1576
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
catch (err) {
|
|
1580
|
+
value = handleChildError(ctx, err);
|
|
1581
|
+
}
|
|
1582
|
+
const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
|
|
1583
|
+
return [block, value];
|
|
1425
1584
|
}
|
|
1426
|
-
if (
|
|
1427
|
-
//
|
|
1428
|
-
|
|
1429
|
-
|
|
1585
|
+
else if (ctx.f & IsInForOfLoop) {
|
|
1586
|
+
// TODO: does this need to be done async?
|
|
1587
|
+
ctx.f &= ~NeedsToYield;
|
|
1588
|
+
// we are in a for...of loop for async generator
|
|
1589
|
+
if (!initial) {
|
|
1590
|
+
try {
|
|
1591
|
+
ctx.f |= IsSyncExecuting;
|
|
1592
|
+
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1593
|
+
}
|
|
1594
|
+
catch (err) {
|
|
1595
|
+
ctx.f |= IsErrored;
|
|
1596
|
+
throw err;
|
|
1597
|
+
}
|
|
1598
|
+
finally {
|
|
1599
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
if (!isPromiseLike(iteration)) {
|
|
1603
|
+
throw new Error("Mixed generator component");
|
|
1430
1604
|
}
|
|
1605
|
+
const block = iteration.catch(NOOP);
|
|
1431
1606
|
const value = iteration.then((iteration) => {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
ctx.f &= ~IsIterating;
|
|
1436
|
-
if (iteration.done) {
|
|
1437
|
-
ctx.f |= IsDone;
|
|
1607
|
+
let value;
|
|
1608
|
+
if (!(ctx.f & IsInForOfLoop)) {
|
|
1609
|
+
runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
|
|
1438
1610
|
}
|
|
1439
1611
|
try {
|
|
1440
|
-
|
|
1612
|
+
value = updateComponentChildren(ctx,
|
|
1613
|
+
// Children can be void so we eliminate that here
|
|
1614
|
+
iteration.value, hydrationData);
|
|
1441
1615
|
if (isPromiseLike(value)) {
|
|
1442
|
-
|
|
1616
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1443
1617
|
}
|
|
1444
|
-
return value;
|
|
1445
1618
|
}
|
|
1446
1619
|
catch (err) {
|
|
1447
|
-
|
|
1620
|
+
value = handleChildError(ctx, err);
|
|
1448
1621
|
}
|
|
1622
|
+
return value;
|
|
1449
1623
|
}, (err) => {
|
|
1450
|
-
ctx.f |=
|
|
1624
|
+
ctx.f |= IsErrored;
|
|
1451
1625
|
throw err;
|
|
1452
1626
|
});
|
|
1453
|
-
return [
|
|
1454
|
-
}
|
|
1455
|
-
// sync generator component
|
|
1456
|
-
if (initial) {
|
|
1457
|
-
ctx.f |= IsSyncGen;
|
|
1627
|
+
return [block, value];
|
|
1458
1628
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1629
|
+
else {
|
|
1630
|
+
runAsyncGenComponent(ctx, iteration, hydrationData);
|
|
1631
|
+
// async generator component
|
|
1632
|
+
return [ctx.inflightBlock, ctx.inflightValue];
|
|
1462
1633
|
}
|
|
1463
|
-
|
|
1634
|
+
}
|
|
1635
|
+
async function runAsyncGenComponent(ctx, iterationP, hydrationData) {
|
|
1636
|
+
let done = false;
|
|
1464
1637
|
try {
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1638
|
+
while (!done) {
|
|
1639
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
1642
|
+
// inflightValue must be set synchronously.
|
|
1643
|
+
let onValue;
|
|
1644
|
+
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1645
|
+
if (ctx.f & IsUpdating) {
|
|
1646
|
+
// We should not swallow unhandled promise rejections if the component is
|
|
1647
|
+
// updating independently.
|
|
1648
|
+
// TODO: Does this handle this.refresh() calls?
|
|
1649
|
+
ctx.inflightValue.catch(NOOP);
|
|
1650
|
+
}
|
|
1651
|
+
let iteration;
|
|
1652
|
+
try {
|
|
1653
|
+
iteration = await iterationP;
|
|
1654
|
+
}
|
|
1655
|
+
catch (err) {
|
|
1656
|
+
done = true;
|
|
1657
|
+
ctx.f |= IsErrored;
|
|
1658
|
+
onValue(Promise.reject(err));
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
finally {
|
|
1662
|
+
ctx.f &= ~NeedsToYield;
|
|
1663
|
+
if (!(ctx.f & IsInForAwaitOfLoop)) {
|
|
1664
|
+
ctx.f &= ~PropsAvailable;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
done = !!iteration.done;
|
|
1668
|
+
let value;
|
|
1669
|
+
try {
|
|
1670
|
+
value = updateComponentChildren(ctx, iteration.value, hydrationData);
|
|
1671
|
+
hydrationData = undefined;
|
|
1672
|
+
if (isPromiseLike(value)) {
|
|
1673
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
catch (err) {
|
|
1677
|
+
// Do we need to catch potential errors here in the case of unhandled
|
|
1678
|
+
// promise rejections?
|
|
1679
|
+
value = handleChildError(ctx, err);
|
|
1680
|
+
}
|
|
1681
|
+
finally {
|
|
1682
|
+
onValue(value);
|
|
1683
|
+
}
|
|
1684
|
+
// TODO: this can be done more elegantly
|
|
1685
|
+
let oldValue;
|
|
1686
|
+
if (ctx.ret.inflightValue) {
|
|
1687
|
+
// The value passed back into the generator as the argument to the next
|
|
1688
|
+
// method is a promise if an async generator component has async
|
|
1689
|
+
// children. Sync generator components only resume when their children
|
|
1690
|
+
// have fulfilled so the element’s inflight child values will never be
|
|
1691
|
+
// defined.
|
|
1692
|
+
oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1693
|
+
}
|
|
1694
|
+
else {
|
|
1695
|
+
oldValue = ctx.renderer.read(getValue(ctx.ret));
|
|
1696
|
+
}
|
|
1697
|
+
if (ctx.f & IsUnmounted) {
|
|
1698
|
+
if (ctx.f & IsInForAwaitOfLoop) {
|
|
1699
|
+
try {
|
|
1700
|
+
ctx.f |= IsSyncExecuting;
|
|
1701
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1702
|
+
}
|
|
1703
|
+
finally {
|
|
1704
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
else {
|
|
1708
|
+
returnComponent(ctx);
|
|
1709
|
+
break;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
else if (!done && !(ctx.f & IsInForOfLoop)) {
|
|
1713
|
+
try {
|
|
1714
|
+
ctx.f |= IsSyncExecuting;
|
|
1715
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1716
|
+
}
|
|
1717
|
+
finally {
|
|
1718
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1468
1721
|
}
|
|
1469
1722
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
}
|
|
1476
|
-
return [undefined, value];
|
|
1477
|
-
}
|
|
1478
|
-
/**
|
|
1479
|
-
* Called when the inflight block promise settles.
|
|
1480
|
-
*/
|
|
1481
|
-
function advanceComponent(ctx) {
|
|
1482
|
-
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1483
|
-
ctx.inflightValue = ctx.enqueuedValue;
|
|
1484
|
-
ctx.enqueuedBlock = undefined;
|
|
1485
|
-
ctx.enqueuedValue = undefined;
|
|
1486
|
-
if (ctx.f & IsAsyncGen && !(ctx.f & IsDone) && !(ctx.f & IsUnmounted)) {
|
|
1487
|
-
runComponent(ctx);
|
|
1723
|
+
finally {
|
|
1724
|
+
if (done) {
|
|
1725
|
+
ctx.f &= ~IsAsyncGen;
|
|
1726
|
+
ctx.iterator = undefined;
|
|
1727
|
+
}
|
|
1488
1728
|
}
|
|
1489
1729
|
}
|
|
1490
1730
|
/**
|
|
1491
|
-
* Called to
|
|
1492
|
-
* generator components.
|
|
1731
|
+
* Called to resume the props async iterator for async generator components.
|
|
1493
1732
|
*/
|
|
1494
|
-
function
|
|
1495
|
-
if (ctx.
|
|
1496
|
-
ctx.
|
|
1497
|
-
ctx.
|
|
1733
|
+
function resumePropsIterator(ctx) {
|
|
1734
|
+
if (ctx.onProps) {
|
|
1735
|
+
ctx.onProps(ctx.ret.el.props);
|
|
1736
|
+
ctx.onProps = undefined;
|
|
1737
|
+
ctx.f &= ~PropsAvailable;
|
|
1498
1738
|
}
|
|
1499
1739
|
else {
|
|
1500
|
-
ctx.f |=
|
|
1740
|
+
ctx.f |= PropsAvailable;
|
|
1501
1741
|
}
|
|
1502
1742
|
}
|
|
1503
1743
|
// TODO: async unmounting
|
|
1504
1744
|
function unmountComponent(ctx) {
|
|
1505
|
-
ctx.f |= IsUnmounted;
|
|
1506
1745
|
clearEventListeners(ctx);
|
|
1507
1746
|
const callbacks = cleanupMap.get(ctx);
|
|
1508
1747
|
if (callbacks) {
|
|
@@ -1512,23 +1751,71 @@ function unmountComponent(ctx) {
|
|
|
1512
1751
|
callback(value);
|
|
1513
1752
|
}
|
|
1514
1753
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
ctx.f
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1754
|
+
ctx.f |= IsUnmounted;
|
|
1755
|
+
if (ctx.iterator) {
|
|
1756
|
+
if (ctx.f & IsSyncGen) {
|
|
1757
|
+
let value;
|
|
1758
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1759
|
+
value = enqueueComponentRun(ctx);
|
|
1760
|
+
}
|
|
1761
|
+
if (isPromiseLike(value)) {
|
|
1762
|
+
value.then(() => {
|
|
1763
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1764
|
+
unmountComponent(ctx);
|
|
1765
|
+
}
|
|
1766
|
+
else {
|
|
1767
|
+
returnComponent(ctx);
|
|
1768
|
+
}
|
|
1769
|
+
}, (err) => {
|
|
1770
|
+
propagateError(ctx.parent, err);
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1775
|
+
unmountComponent(ctx);
|
|
1776
|
+
}
|
|
1777
|
+
else {
|
|
1778
|
+
returnComponent(ctx);
|
|
1524
1779
|
}
|
|
1525
1780
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1781
|
+
}
|
|
1782
|
+
else if (ctx.f & IsAsyncGen) {
|
|
1783
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1784
|
+
const value = enqueueComponentRun(ctx);
|
|
1785
|
+
value.then(() => {
|
|
1786
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1787
|
+
unmountComponent(ctx);
|
|
1788
|
+
}
|
|
1789
|
+
else {
|
|
1790
|
+
returnComponent(ctx);
|
|
1791
|
+
}
|
|
1792
|
+
}, (err) => {
|
|
1793
|
+
propagateError(ctx.parent, err);
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
// The logic for unmounting async generator components is in the
|
|
1798
|
+
// runAsyncGenComponent function.
|
|
1799
|
+
resumePropsIterator(ctx);
|
|
1528
1800
|
}
|
|
1529
1801
|
}
|
|
1530
1802
|
}
|
|
1531
1803
|
}
|
|
1804
|
+
function returnComponent(ctx) {
|
|
1805
|
+
resumePropsIterator(ctx);
|
|
1806
|
+
if (ctx.iterator && typeof ctx.iterator.return === "function") {
|
|
1807
|
+
try {
|
|
1808
|
+
ctx.f |= IsSyncExecuting;
|
|
1809
|
+
const iteration = ctx.iterator.return();
|
|
1810
|
+
if (isPromiseLike(iteration)) {
|
|
1811
|
+
iteration.catch((err) => propagateError(ctx.parent, err));
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
finally {
|
|
1815
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1532
1819
|
/*** EVENT TARGET UTILITIES ***/
|
|
1533
1820
|
// EVENT PHASE CONSTANTS
|
|
1534
1821
|
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
@@ -1597,39 +1884,39 @@ function clearEventListeners(ctx) {
|
|
|
1597
1884
|
}
|
|
1598
1885
|
}
|
|
1599
1886
|
/*** ERROR HANDLING UTILITIES ***/
|
|
1600
|
-
// TODO: generator components which throw errors should be recoverable
|
|
1601
1887
|
function handleChildError(ctx, err) {
|
|
1602
|
-
if (ctx.
|
|
1603
|
-
!ctx.iterator ||
|
|
1604
|
-
typeof ctx.iterator.throw !== "function") {
|
|
1888
|
+
if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
|
|
1605
1889
|
throw err;
|
|
1606
1890
|
}
|
|
1607
|
-
|
|
1891
|
+
resumePropsIterator(ctx);
|
|
1608
1892
|
let iteration;
|
|
1609
1893
|
try {
|
|
1610
|
-
ctx.f |=
|
|
1894
|
+
ctx.f |= IsSyncExecuting;
|
|
1611
1895
|
iteration = ctx.iterator.throw(err);
|
|
1612
1896
|
}
|
|
1613
1897
|
catch (err) {
|
|
1614
|
-
ctx.f |=
|
|
1898
|
+
ctx.f |= IsErrored;
|
|
1615
1899
|
throw err;
|
|
1616
1900
|
}
|
|
1617
1901
|
finally {
|
|
1618
|
-
ctx.f &= ~
|
|
1902
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1619
1903
|
}
|
|
1620
1904
|
if (isPromiseLike(iteration)) {
|
|
1621
1905
|
return iteration.then((iteration) => {
|
|
1622
1906
|
if (iteration.done) {
|
|
1623
|
-
ctx.f
|
|
1907
|
+
ctx.f &= ~IsAsyncGen;
|
|
1908
|
+
ctx.iterator = undefined;
|
|
1624
1909
|
}
|
|
1625
1910
|
return updateComponentChildren(ctx, iteration.value);
|
|
1626
1911
|
}, (err) => {
|
|
1627
|
-
ctx.f |=
|
|
1912
|
+
ctx.f |= IsErrored;
|
|
1628
1913
|
throw err;
|
|
1629
1914
|
});
|
|
1630
1915
|
}
|
|
1631
1916
|
if (iteration.done) {
|
|
1632
|
-
ctx.f
|
|
1917
|
+
ctx.f &= ~IsSyncGen;
|
|
1918
|
+
ctx.f &= ~IsAsyncGen;
|
|
1919
|
+
ctx.iterator = undefined;
|
|
1633
1920
|
}
|
|
1634
1921
|
return updateComponentChildren(ctx, iteration.value);
|
|
1635
1922
|
}
|