@b9g/crank 0.5.0-debug.0 → 0.5.1
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 +682 -392
- package/crank.cjs.map +1 -1
- package/crank.d.ts +57 -77
- package/crank.js +681 -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 +787 -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,72 @@ 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 = ret.value;
|
|
660
|
+
if (hydrationValue != null) {
|
|
661
|
+
value = ret.value = hydrationValue;
|
|
662
|
+
}
|
|
626
663
|
let props = ret.el.props;
|
|
627
664
|
let copied;
|
|
628
665
|
if (tag !== Portal) {
|
|
666
|
+
if (value == null) {
|
|
667
|
+
// This assumes that renderer.create does not return nullish values.
|
|
668
|
+
value = ret.value = renderer.create(tag, props, scope);
|
|
669
|
+
}
|
|
629
670
|
for (const propName in { ...oldProps, ...props }) {
|
|
630
671
|
const propValue = props[propName];
|
|
631
672
|
if (propValue === Copy) {
|
|
673
|
+
// TODO: The Copy tag doubles as a way to skip the patching of a prop.
|
|
674
|
+
// Not sure about this feature. Should probably be removed.
|
|
632
675
|
(copied = copied || new Set()).add(propName);
|
|
633
676
|
}
|
|
634
677
|
else if (propName !== "children") {
|
|
@@ -643,8 +686,8 @@ function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
|
643
686
|
}
|
|
644
687
|
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
|
|
645
688
|
}
|
|
646
|
-
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.
|
|
647
|
-
ret.
|
|
689
|
+
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
690
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
648
691
|
if (tag === Portal) {
|
|
649
692
|
flush(renderer, ret.value);
|
|
650
693
|
return;
|
|
@@ -691,7 +734,7 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
691
734
|
}
|
|
692
735
|
else if (ret.el.tag === Portal) {
|
|
693
736
|
host = ret;
|
|
694
|
-
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.
|
|
737
|
+
renderer.arrange(Portal, host.value, host.el.props, [], host.el.props, wrap(host.cachedChildValues));
|
|
695
738
|
flush(renderer, host.value);
|
|
696
739
|
}
|
|
697
740
|
else if (ret.el.tag !== Fragment) {
|
|
@@ -715,38 +758,43 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
715
758
|
}
|
|
716
759
|
/*** CONTEXT FLAGS ***/
|
|
717
760
|
/**
|
|
718
|
-
* A flag which is
|
|
719
|
-
*
|
|
720
|
-
*
|
|
761
|
+
* A flag which is true when the component is initialized or updated by an
|
|
762
|
+
* ancestor component or the root render call.
|
|
763
|
+
*
|
|
764
|
+
* Used to determine things like whether the nearest host ancestor needs to be
|
|
765
|
+
* rearranged.
|
|
721
766
|
*/
|
|
722
767
|
const IsUpdating = 1 << 0;
|
|
723
768
|
/**
|
|
724
|
-
* A flag which is
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* overflow or a generator error.
|
|
769
|
+
* A flag which is true when the component is synchronously executing.
|
|
770
|
+
*
|
|
771
|
+
* Used to guard against components triggering stack overflow or generator error.
|
|
728
772
|
*/
|
|
729
|
-
const
|
|
773
|
+
const IsSyncExecuting = 1 << 1;
|
|
730
774
|
/**
|
|
731
|
-
* A flag
|
|
732
|
-
* iterators without a yield.
|
|
775
|
+
* A flag which is true when the component is in a for...of loop.
|
|
733
776
|
*/
|
|
734
|
-
const
|
|
777
|
+
const IsInForOfLoop = 1 << 2;
|
|
735
778
|
/**
|
|
736
|
-
* A flag
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
779
|
+
* A flag which is true when the component is in a for await...of loop.
|
|
780
|
+
*/
|
|
781
|
+
const IsInForAwaitOfLoop = 1 << 3;
|
|
782
|
+
/**
|
|
783
|
+
* A flag which is true when the component starts the render loop but has not
|
|
784
|
+
* yielded yet.
|
|
785
|
+
*
|
|
786
|
+
* Used to make sure that components yield at least once per loop.
|
|
740
787
|
*/
|
|
741
|
-
const
|
|
788
|
+
const NeedsToYield = 1 << 4;
|
|
742
789
|
/**
|
|
743
|
-
* A flag
|
|
744
|
-
*
|
|
745
|
-
*
|
|
790
|
+
* A flag used by async generator components in conjunction with the
|
|
791
|
+
* onAvailable callback to mark whether new props can be pulled via the context
|
|
792
|
+
* async iterator. See the Symbol.asyncIterator method and the
|
|
793
|
+
* resumeCtxIterator function.
|
|
746
794
|
*/
|
|
747
|
-
const
|
|
795
|
+
const PropsAvailable = 1 << 5;
|
|
748
796
|
/**
|
|
749
|
-
* A flag which is set when a
|
|
797
|
+
* A flag which is set when a component errors.
|
|
750
798
|
*
|
|
751
799
|
* NOTE: This is mainly used to prevent some false positives in component
|
|
752
800
|
* yields or returns undefined warnings. The reason we’re using this versus
|
|
@@ -754,28 +802,28 @@ const IsDone = 1 << 4;
|
|
|
754
802
|
* sync generator child) where synchronous code causes a stack overflow error
|
|
755
803
|
* in a non-deterministic way. Deeply disturbing stuff.
|
|
756
804
|
*/
|
|
757
|
-
const IsErrored = 1 <<
|
|
805
|
+
const IsErrored = 1 << 6;
|
|
758
806
|
/**
|
|
759
807
|
* A flag which is set when the component is unmounted. Unmounted components
|
|
760
808
|
* are no longer in the element tree and cannot refresh or rerender.
|
|
761
809
|
*/
|
|
762
|
-
const IsUnmounted = 1 <<
|
|
810
|
+
const IsUnmounted = 1 << 7;
|
|
763
811
|
/**
|
|
764
812
|
* A flag which indicates that the component is a sync generator component.
|
|
765
813
|
*/
|
|
766
|
-
const IsSyncGen = 1 <<
|
|
814
|
+
const IsSyncGen = 1 << 8;
|
|
767
815
|
/**
|
|
768
816
|
* A flag which indicates that the component is an async generator component.
|
|
769
817
|
*/
|
|
770
|
-
const IsAsyncGen = 1 <<
|
|
818
|
+
const IsAsyncGen = 1 << 9;
|
|
771
819
|
/**
|
|
772
820
|
* A flag which is set while schedule callbacks are called.
|
|
773
821
|
*/
|
|
774
|
-
const IsScheduling = 1 <<
|
|
822
|
+
const IsScheduling = 1 << 10;
|
|
775
823
|
/**
|
|
776
824
|
* A flag which is set when a schedule callback calls refresh.
|
|
777
825
|
*/
|
|
778
|
-
const IsSchedulingRefresh = 1 <<
|
|
826
|
+
const IsSchedulingRefresh = 1 << 11;
|
|
779
827
|
const provisionMaps = new WeakMap();
|
|
780
828
|
const scheduleMap = new WeakMap();
|
|
781
829
|
const cleanupMap = new WeakMap();
|
|
@@ -783,12 +831,12 @@ const cleanupMap = new WeakMap();
|
|
|
783
831
|
const flushMaps = new WeakMap();
|
|
784
832
|
/**
|
|
785
833
|
* @internal
|
|
786
|
-
* The internal class which holds
|
|
834
|
+
* The internal class which holds context data.
|
|
787
835
|
*/
|
|
788
836
|
class ContextImpl {
|
|
789
837
|
constructor(renderer, root, host, parent, scope, ret) {
|
|
790
838
|
this.f = 0;
|
|
791
|
-
this.
|
|
839
|
+
this.owner = new Context(this);
|
|
792
840
|
this.renderer = renderer;
|
|
793
841
|
this.root = root;
|
|
794
842
|
this.host = host;
|
|
@@ -800,10 +848,11 @@ class ContextImpl {
|
|
|
800
848
|
this.inflightValue = undefined;
|
|
801
849
|
this.enqueuedBlock = undefined;
|
|
802
850
|
this.enqueuedValue = undefined;
|
|
803
|
-
this.
|
|
851
|
+
this.onProps = undefined;
|
|
852
|
+
this.onPropsRequested = undefined;
|
|
804
853
|
}
|
|
805
854
|
}
|
|
806
|
-
const
|
|
855
|
+
const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
807
856
|
/**
|
|
808
857
|
* A class which is instantiated and passed to every component as its this
|
|
809
858
|
* value. Contexts form a tree just like elements and all components in the
|
|
@@ -817,8 +866,10 @@ const $ContextImpl = Symbol.for("crank.ContextImpl");
|
|
|
817
866
|
* schedule and cleanup callbacks.
|
|
818
867
|
*/
|
|
819
868
|
class Context {
|
|
869
|
+
// TODO: If we could make the constructor function take a nicer value, it
|
|
870
|
+
// would be useful for testing purposes.
|
|
820
871
|
constructor(impl) {
|
|
821
|
-
this[
|
|
872
|
+
this[_ContextImpl] = impl;
|
|
822
873
|
}
|
|
823
874
|
/**
|
|
824
875
|
* The current props of the associated element.
|
|
@@ -828,7 +879,7 @@ class Context {
|
|
|
828
879
|
* plugins or utilities which wrap contexts.
|
|
829
880
|
*/
|
|
830
881
|
get props() {
|
|
831
|
-
return this[
|
|
882
|
+
return this[_ContextImpl].ret.el.props;
|
|
832
883
|
}
|
|
833
884
|
// TODO: Should we rename this???
|
|
834
885
|
/**
|
|
@@ -839,45 +890,64 @@ class Context {
|
|
|
839
890
|
* mainly for plugins or utilities which wrap contexts.
|
|
840
891
|
*/
|
|
841
892
|
get value() {
|
|
842
|
-
return this[
|
|
893
|
+
return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
|
|
843
894
|
}
|
|
844
895
|
*[Symbol.iterator]() {
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
896
|
+
const ctx = this[_ContextImpl];
|
|
897
|
+
try {
|
|
898
|
+
ctx.f |= IsInForOfLoop;
|
|
899
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
900
|
+
if (ctx.f & NeedsToYield) {
|
|
901
|
+
throw new Error("Context iterated twice without a yield");
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
ctx.f |= NeedsToYield;
|
|
905
|
+
}
|
|
906
|
+
yield ctx.ret.el.props;
|
|
852
907
|
}
|
|
853
|
-
|
|
854
|
-
|
|
908
|
+
}
|
|
909
|
+
finally {
|
|
910
|
+
ctx.f &= ~IsInForOfLoop;
|
|
855
911
|
}
|
|
856
912
|
}
|
|
857
913
|
async *[Symbol.asyncIterator]() {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
914
|
+
const ctx = this[_ContextImpl];
|
|
915
|
+
if (ctx.f & IsSyncGen) {
|
|
916
|
+
throw new Error("Use for...of in sync generator components");
|
|
917
|
+
}
|
|
918
|
+
try {
|
|
919
|
+
ctx.f |= IsInForAwaitOfLoop;
|
|
920
|
+
while (!(ctx.f & IsUnmounted)) {
|
|
921
|
+
if (ctx.f & NeedsToYield) {
|
|
922
|
+
throw new Error("Context iterated twice without a yield");
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
ctx.f |= NeedsToYield;
|
|
926
|
+
}
|
|
927
|
+
if (ctx.f & PropsAvailable) {
|
|
928
|
+
ctx.f &= ~PropsAvailable;
|
|
929
|
+
yield ctx.ret.el.props;
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
const props = await new Promise((resolve) => (ctx.onProps = resolve));
|
|
933
|
+
if (ctx.f & IsUnmounted) {
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
yield props;
|
|
877
937
|
}
|
|
938
|
+
if (ctx.onPropsRequested) {
|
|
939
|
+
ctx.onPropsRequested();
|
|
940
|
+
ctx.onPropsRequested = undefined;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
finally {
|
|
945
|
+
ctx.f &= ~IsInForAwaitOfLoop;
|
|
946
|
+
if (ctx.onPropsRequested) {
|
|
947
|
+
ctx.onPropsRequested();
|
|
948
|
+
ctx.onPropsRequested = undefined;
|
|
878
949
|
}
|
|
879
|
-
|
|
880
|
-
} while (!(impl.f & IsDone));
|
|
950
|
+
}
|
|
881
951
|
}
|
|
882
952
|
/**
|
|
883
953
|
* Re-executes a component.
|
|
@@ -892,32 +962,31 @@ class Context {
|
|
|
892
962
|
* async iterator to suspend.
|
|
893
963
|
*/
|
|
894
964
|
refresh() {
|
|
895
|
-
const
|
|
896
|
-
if (
|
|
965
|
+
const ctx = this[_ContextImpl];
|
|
966
|
+
if (ctx.f & IsUnmounted) {
|
|
897
967
|
console.error("Component is unmounted");
|
|
898
|
-
return
|
|
968
|
+
return ctx.renderer.read(undefined);
|
|
899
969
|
}
|
|
900
|
-
else if (
|
|
970
|
+
else if (ctx.f & IsSyncExecuting) {
|
|
901
971
|
console.error("Component is already executing");
|
|
902
972
|
return this.value;
|
|
903
973
|
}
|
|
904
|
-
|
|
905
|
-
const value = runComponent(impl);
|
|
974
|
+
const value = enqueueComponentRun(ctx);
|
|
906
975
|
if (isPromiseLike(value)) {
|
|
907
|
-
return value.then((value) =>
|
|
976
|
+
return value.then((value) => ctx.renderer.read(value));
|
|
908
977
|
}
|
|
909
|
-
return
|
|
978
|
+
return ctx.renderer.read(value);
|
|
910
979
|
}
|
|
911
980
|
/**
|
|
912
981
|
* Registers a callback which fires when the component commits. Will only
|
|
913
982
|
* fire once per callback and update.
|
|
914
983
|
*/
|
|
915
984
|
schedule(callback) {
|
|
916
|
-
const
|
|
917
|
-
let callbacks = scheduleMap.get(
|
|
985
|
+
const ctx = this[_ContextImpl];
|
|
986
|
+
let callbacks = scheduleMap.get(ctx);
|
|
918
987
|
if (!callbacks) {
|
|
919
988
|
callbacks = new Set();
|
|
920
|
-
scheduleMap.set(
|
|
989
|
+
scheduleMap.set(ctx, callbacks);
|
|
921
990
|
}
|
|
922
991
|
callbacks.add(callback);
|
|
923
992
|
}
|
|
@@ -926,19 +995,19 @@ class Context {
|
|
|
926
995
|
* rendered into the root. Will only fire once per callback and render.
|
|
927
996
|
*/
|
|
928
997
|
flush(callback) {
|
|
929
|
-
const
|
|
930
|
-
if (typeof
|
|
998
|
+
const ctx = this[_ContextImpl];
|
|
999
|
+
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
931
1000
|
return;
|
|
932
1001
|
}
|
|
933
|
-
let flushMap = flushMaps.get(
|
|
1002
|
+
let flushMap = flushMaps.get(ctx.root);
|
|
934
1003
|
if (!flushMap) {
|
|
935
1004
|
flushMap = new Map();
|
|
936
|
-
flushMaps.set(
|
|
1005
|
+
flushMaps.set(ctx.root, flushMap);
|
|
937
1006
|
}
|
|
938
|
-
let callbacks = flushMap.get(
|
|
1007
|
+
let callbacks = flushMap.get(ctx);
|
|
939
1008
|
if (!callbacks) {
|
|
940
1009
|
callbacks = new Set();
|
|
941
|
-
flushMap.set(
|
|
1010
|
+
flushMap.set(ctx, callbacks);
|
|
942
1011
|
}
|
|
943
1012
|
callbacks.add(callback);
|
|
944
1013
|
}
|
|
@@ -947,45 +1016,45 @@ class Context {
|
|
|
947
1016
|
* fire once per callback.
|
|
948
1017
|
*/
|
|
949
1018
|
cleanup(callback) {
|
|
950
|
-
const
|
|
951
|
-
let callbacks = cleanupMap.get(
|
|
1019
|
+
const ctx = this[_ContextImpl];
|
|
1020
|
+
let callbacks = cleanupMap.get(ctx);
|
|
952
1021
|
if (!callbacks) {
|
|
953
1022
|
callbacks = new Set();
|
|
954
|
-
cleanupMap.set(
|
|
1023
|
+
cleanupMap.set(ctx, callbacks);
|
|
955
1024
|
}
|
|
956
1025
|
callbacks.add(callback);
|
|
957
1026
|
}
|
|
958
1027
|
consume(key) {
|
|
959
|
-
for (let
|
|
960
|
-
const provisions = provisionMaps.get(
|
|
1028
|
+
for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
1029
|
+
const provisions = provisionMaps.get(ctx);
|
|
961
1030
|
if (provisions && provisions.has(key)) {
|
|
962
1031
|
return provisions.get(key);
|
|
963
1032
|
}
|
|
964
1033
|
}
|
|
965
1034
|
}
|
|
966
1035
|
provide(key, value) {
|
|
967
|
-
const
|
|
968
|
-
let provisions = provisionMaps.get(
|
|
1036
|
+
const ctx = this[_ContextImpl];
|
|
1037
|
+
let provisions = provisionMaps.get(ctx);
|
|
969
1038
|
if (!provisions) {
|
|
970
1039
|
provisions = new Map();
|
|
971
|
-
provisionMaps.set(
|
|
1040
|
+
provisionMaps.set(ctx, provisions);
|
|
972
1041
|
}
|
|
973
1042
|
provisions.set(key, value);
|
|
974
1043
|
}
|
|
975
1044
|
addEventListener(type, listener, options) {
|
|
976
|
-
const
|
|
1045
|
+
const ctx = this[_ContextImpl];
|
|
977
1046
|
let listeners;
|
|
978
1047
|
if (!isListenerOrListenerObject(listener)) {
|
|
979
1048
|
return;
|
|
980
1049
|
}
|
|
981
1050
|
else {
|
|
982
|
-
const listeners1 = listenersMap.get(
|
|
1051
|
+
const listeners1 = listenersMap.get(ctx);
|
|
983
1052
|
if (listeners1) {
|
|
984
1053
|
listeners = listeners1;
|
|
985
1054
|
}
|
|
986
1055
|
else {
|
|
987
1056
|
listeners = [];
|
|
988
|
-
listenersMap.set(
|
|
1057
|
+
listenersMap.set(ctx, listeners);
|
|
989
1058
|
}
|
|
990
1059
|
}
|
|
991
1060
|
options = normalizeListenerOptions(options);
|
|
@@ -996,7 +1065,7 @@ class Context {
|
|
|
996
1065
|
else {
|
|
997
1066
|
callback = listener;
|
|
998
1067
|
}
|
|
999
|
-
const record = { type,
|
|
1068
|
+
const record = { type, listener, callback, options };
|
|
1000
1069
|
if (options.once) {
|
|
1001
1070
|
record.callback = function () {
|
|
1002
1071
|
const i = listeners.indexOf(record);
|
|
@@ -1013,15 +1082,15 @@ class Context {
|
|
|
1013
1082
|
}
|
|
1014
1083
|
listeners.push(record);
|
|
1015
1084
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1016
|
-
for (const value of getChildValues(
|
|
1085
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1017
1086
|
if (isEventTarget(value)) {
|
|
1018
1087
|
value.addEventListener(record.type, record.callback, record.options);
|
|
1019
1088
|
}
|
|
1020
1089
|
}
|
|
1021
1090
|
}
|
|
1022
1091
|
removeEventListener(type, listener, options) {
|
|
1023
|
-
const
|
|
1024
|
-
const listeners = listenersMap.get(
|
|
1092
|
+
const ctx = this[_ContextImpl];
|
|
1093
|
+
const listeners = listenersMap.get(ctx);
|
|
1025
1094
|
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1026
1095
|
return;
|
|
1027
1096
|
}
|
|
@@ -1035,16 +1104,16 @@ class Context {
|
|
|
1035
1104
|
const record = listeners[i];
|
|
1036
1105
|
listeners.splice(i, 1);
|
|
1037
1106
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1038
|
-
for (const value of getChildValues(
|
|
1107
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1039
1108
|
if (isEventTarget(value)) {
|
|
1040
1109
|
value.removeEventListener(record.type, record.callback, record.options);
|
|
1041
1110
|
}
|
|
1042
1111
|
}
|
|
1043
1112
|
}
|
|
1044
1113
|
dispatchEvent(ev) {
|
|
1045
|
-
const
|
|
1114
|
+
const ctx = this[_ContextImpl];
|
|
1046
1115
|
const path = [];
|
|
1047
|
-
for (let parent =
|
|
1116
|
+
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1048
1117
|
path.push(parent);
|
|
1049
1118
|
}
|
|
1050
1119
|
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
@@ -1056,7 +1125,7 @@ class Context {
|
|
|
1056
1125
|
immediateCancelBubble = true;
|
|
1057
1126
|
return stopImmediatePropagation.call(ev);
|
|
1058
1127
|
});
|
|
1059
|
-
setEventProperty(ev, "target",
|
|
1128
|
+
setEventProperty(ev, "target", ctx.owner);
|
|
1060
1129
|
// The only possible errors in this block are errors thrown by callbacks,
|
|
1061
1130
|
// and dispatchEvent will only log these errors rather than throwing
|
|
1062
1131
|
// them. Therefore, we place all code in a try block, log errors in the
|
|
@@ -1065,18 +1134,21 @@ class Context {
|
|
|
1065
1134
|
// Each early return within the try block returns true because while the
|
|
1066
1135
|
// return value is overridden in the finally block, TypeScript
|
|
1067
1136
|
// (justifiably) does not recognize the unsafe return statement.
|
|
1068
|
-
//
|
|
1069
|
-
// TODO: Run all callbacks even if one of them errors
|
|
1070
1137
|
try {
|
|
1071
1138
|
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1072
1139
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
1073
1140
|
const target = path[i];
|
|
1074
1141
|
const listeners = listenersMap.get(target);
|
|
1075
1142
|
if (listeners) {
|
|
1076
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1143
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1077
1144
|
for (const record of listeners) {
|
|
1078
1145
|
if (record.type === ev.type && record.options.capture) {
|
|
1079
|
-
|
|
1146
|
+
try {
|
|
1147
|
+
record.callback.call(target.owner, ev);
|
|
1148
|
+
}
|
|
1149
|
+
catch (err) {
|
|
1150
|
+
console.error(err);
|
|
1151
|
+
}
|
|
1080
1152
|
if (immediateCancelBubble) {
|
|
1081
1153
|
return true;
|
|
1082
1154
|
}
|
|
@@ -1088,13 +1160,25 @@ class Context {
|
|
|
1088
1160
|
}
|
|
1089
1161
|
}
|
|
1090
1162
|
{
|
|
1091
|
-
|
|
1163
|
+
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1164
|
+
setEventProperty(ev, "currentTarget", ctx.owner);
|
|
1165
|
+
const propCallback = ctx.ret.el.props["on" + ev.type];
|
|
1166
|
+
if (propCallback != null) {
|
|
1167
|
+
propCallback(ev);
|
|
1168
|
+
if (immediateCancelBubble || ev.cancelBubble) {
|
|
1169
|
+
return true;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
const listeners = listenersMap.get(ctx);
|
|
1092
1173
|
if (listeners) {
|
|
1093
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1094
|
-
setEventProperty(ev, "currentTarget", impl.ctx);
|
|
1095
1174
|
for (const record of listeners) {
|
|
1096
1175
|
if (record.type === ev.type) {
|
|
1097
|
-
|
|
1176
|
+
try {
|
|
1177
|
+
record.callback.call(ctx.owner, ev);
|
|
1178
|
+
}
|
|
1179
|
+
catch (err) {
|
|
1180
|
+
console.error(err);
|
|
1181
|
+
}
|
|
1098
1182
|
if (immediateCancelBubble) {
|
|
1099
1183
|
return true;
|
|
1100
1184
|
}
|
|
@@ -1111,10 +1195,15 @@ class Context {
|
|
|
1111
1195
|
const target = path[i];
|
|
1112
1196
|
const listeners = listenersMap.get(target);
|
|
1113
1197
|
if (listeners) {
|
|
1114
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1198
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1115
1199
|
for (const record of listeners) {
|
|
1116
1200
|
if (record.type === ev.type && !record.options.capture) {
|
|
1117
|
-
|
|
1201
|
+
try {
|
|
1202
|
+
record.callback.call(target.owner, ev);
|
|
1203
|
+
}
|
|
1204
|
+
catch (err) {
|
|
1205
|
+
console.error(err);
|
|
1206
|
+
}
|
|
1118
1207
|
if (immediateCancelBubble) {
|
|
1119
1208
|
return true;
|
|
1120
1209
|
}
|
|
@@ -1127,10 +1216,6 @@ class Context {
|
|
|
1127
1216
|
}
|
|
1128
1217
|
}
|
|
1129
1218
|
}
|
|
1130
|
-
catch (err) {
|
|
1131
|
-
// TODO: Use setTimeout to rethrow the error.
|
|
1132
|
-
console.error(err);
|
|
1133
|
-
}
|
|
1134
1219
|
finally {
|
|
1135
1220
|
setEventProperty(ev, "eventPhase", NONE);
|
|
1136
1221
|
setEventProperty(ev, "currentTarget", null);
|
|
@@ -1148,42 +1233,47 @@ function ctxContains(parent, child) {
|
|
|
1148
1233
|
}
|
|
1149
1234
|
return false;
|
|
1150
1235
|
}
|
|
1151
|
-
function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
|
|
1236
|
+
function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
|
|
1152
1237
|
let ctx;
|
|
1153
1238
|
if (oldProps) {
|
|
1154
1239
|
ctx = ret.ctx;
|
|
1155
|
-
if (ctx.f &
|
|
1240
|
+
if (ctx.f & IsSyncExecuting) {
|
|
1156
1241
|
console.error("Component is already executing");
|
|
1157
|
-
return ret.
|
|
1242
|
+
return ret.cachedChildValues;
|
|
1158
1243
|
}
|
|
1159
1244
|
}
|
|
1160
1245
|
else {
|
|
1161
1246
|
ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
|
|
1162
1247
|
}
|
|
1163
1248
|
ctx.f |= IsUpdating;
|
|
1164
|
-
|
|
1165
|
-
return runComponent(ctx);
|
|
1249
|
+
return enqueueComponentRun(ctx, hydrationData);
|
|
1166
1250
|
}
|
|
1167
|
-
function updateComponentChildren(ctx, children) {
|
|
1168
|
-
if (ctx.f & IsUnmounted
|
|
1251
|
+
function updateComponentChildren(ctx, children, hydrationData) {
|
|
1252
|
+
if (ctx.f & IsUnmounted) {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
else if (ctx.f & IsErrored) {
|
|
1256
|
+
// This branch is necessary for some race conditions where this function is
|
|
1257
|
+
// called after iterator.throw() in async generator components.
|
|
1169
1258
|
return;
|
|
1170
1259
|
}
|
|
1171
1260
|
else if (children === undefined) {
|
|
1172
1261
|
console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
|
|
1173
1262
|
}
|
|
1174
1263
|
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
1264
|
try {
|
|
1179
|
-
|
|
1265
|
+
// TODO: WAT
|
|
1266
|
+
// We set the isExecuting flag in case a child component dispatches an event
|
|
1267
|
+
// which bubbles to this component and causes a synchronous refresh().
|
|
1268
|
+
ctx.f |= IsSyncExecuting;
|
|
1269
|
+
childValues = diffChildren(ctx.renderer, ctx.root, ctx.host, ctx, ctx.scope, ctx.ret, narrow(children), hydrationData);
|
|
1180
1270
|
}
|
|
1181
1271
|
finally {
|
|
1182
|
-
ctx.f &= ~
|
|
1272
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1183
1273
|
}
|
|
1184
1274
|
if (isPromiseLike(childValues)) {
|
|
1185
|
-
ctx.ret.
|
|
1186
|
-
return ctx.ret.
|
|
1275
|
+
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1276
|
+
return ctx.ret.inflightValue;
|
|
1187
1277
|
}
|
|
1188
1278
|
return commitComponent(ctx, childValues);
|
|
1189
1279
|
}
|
|
@@ -1203,8 +1293,8 @@ function commitComponent(ctx, values) {
|
|
|
1203
1293
|
}
|
|
1204
1294
|
}
|
|
1205
1295
|
}
|
|
1206
|
-
const oldValues = wrap(ctx.ret.
|
|
1207
|
-
let value = (ctx.ret.
|
|
1296
|
+
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1297
|
+
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1208
1298
|
if (ctx.f & IsScheduling) {
|
|
1209
1299
|
ctx.f |= IsSchedulingRefresh;
|
|
1210
1300
|
}
|
|
@@ -1212,7 +1302,7 @@ function commitComponent(ctx, values) {
|
|
|
1212
1302
|
// If we’re not updating the component, which happens when components are
|
|
1213
1303
|
// refreshed, or when async generator components iterate, we have to do a
|
|
1214
1304
|
// little bit housekeeping when a component’s child values have changed.
|
|
1215
|
-
if (!
|
|
1305
|
+
if (!arrayEqual(oldValues, values)) {
|
|
1216
1306
|
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1217
1307
|
if (records.length) {
|
|
1218
1308
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -1227,7 +1317,7 @@ function commitComponent(ctx, values) {
|
|
|
1227
1317
|
}
|
|
1228
1318
|
// rearranging the nearest ancestor host element
|
|
1229
1319
|
const host = ctx.host;
|
|
1230
|
-
const oldHostValues = wrap(host.
|
|
1320
|
+
const oldHostValues = wrap(host.cachedChildValues);
|
|
1231
1321
|
invalidate(ctx, host);
|
|
1232
1322
|
const hostValues = getChildValues(host);
|
|
1233
1323
|
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
@@ -1256,50 +1346,79 @@ function commitComponent(ctx, values) {
|
|
|
1256
1346
|
}
|
|
1257
1347
|
function invalidate(ctx, host) {
|
|
1258
1348
|
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1259
|
-
parent.ret.
|
|
1349
|
+
parent.ret.cachedChildValues = undefined;
|
|
1260
1350
|
}
|
|
1261
|
-
host.
|
|
1351
|
+
host.cachedChildValues = undefined;
|
|
1262
1352
|
}
|
|
1263
|
-
function
|
|
1264
|
-
if (
|
|
1353
|
+
function arrayEqual(arr1, arr2) {
|
|
1354
|
+
if (arr1.length !== arr2.length) {
|
|
1265
1355
|
return false;
|
|
1266
1356
|
}
|
|
1267
|
-
for (let i = 0; i <
|
|
1268
|
-
const value1 =
|
|
1269
|
-
const value2 =
|
|
1357
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
1358
|
+
const value1 = arr1[i];
|
|
1359
|
+
const value2 = arr2[i];
|
|
1270
1360
|
if (value1 !== value2) {
|
|
1271
1361
|
return false;
|
|
1272
1362
|
}
|
|
1273
1363
|
}
|
|
1274
1364
|
return true;
|
|
1275
1365
|
}
|
|
1276
|
-
/**
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1366
|
+
/** Enqueues and executes the component associated with the context. */
|
|
1367
|
+
function enqueueComponentRun(ctx, hydrationData) {
|
|
1368
|
+
if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
|
|
1369
|
+
if (hydrationData !== undefined) {
|
|
1370
|
+
throw new Error("Hydration error");
|
|
1371
|
+
}
|
|
1372
|
+
// This branch will run for non-initial renders of async generator
|
|
1373
|
+
// components when they are not in for...of loops. When in a for...of loop,
|
|
1374
|
+
// async generator components will behave normally.
|
|
1375
|
+
//
|
|
1376
|
+
// Async gen componennts can be in one of three states:
|
|
1377
|
+
//
|
|
1378
|
+
// 1. propsAvailable flag is true: "available"
|
|
1379
|
+
//
|
|
1380
|
+
// The component is suspended somewhere in the loop. When the component
|
|
1381
|
+
// reaches the bottom of the loop, it will run again with the next props.
|
|
1382
|
+
//
|
|
1383
|
+
// 2. onAvailable callback is defined: "suspended"
|
|
1384
|
+
//
|
|
1385
|
+
// The component has suspended at the bottom of the loop and is waiting
|
|
1386
|
+
// for new props.
|
|
1387
|
+
//
|
|
1388
|
+
// 3. neither 1 or 2: "Running"
|
|
1389
|
+
//
|
|
1390
|
+
// The component is suspended somewhere in the loop. When the component
|
|
1391
|
+
// reaches the bottom of the loop, it will suspend.
|
|
1392
|
+
//
|
|
1393
|
+
// Components will never be both available and suspended at
|
|
1394
|
+
// the same time.
|
|
1395
|
+
//
|
|
1396
|
+
// If the component is at the loop bottom, this means that the next value
|
|
1397
|
+
// produced by the component will have the most up to date props, so we can
|
|
1398
|
+
// simply return the current inflight value. Otherwise, we have to wait for
|
|
1399
|
+
// the bottom of the loop to be reached before returning the inflight
|
|
1400
|
+
// value.
|
|
1401
|
+
const isAtLoopbottom = ctx.f & IsInForAwaitOfLoop && !ctx.onProps;
|
|
1402
|
+
resumePropsIterator(ctx);
|
|
1403
|
+
if (isAtLoopbottom) {
|
|
1404
|
+
if (ctx.inflightBlock == null) {
|
|
1405
|
+
ctx.inflightBlock = new Promise((resolve) => (ctx.onPropsRequested = resolve));
|
|
1406
|
+
}
|
|
1407
|
+
return ctx.inflightBlock.then(() => {
|
|
1408
|
+
ctx.inflightBlock = undefined;
|
|
1409
|
+
return ctx.inflightValue;
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
return ctx.inflightValue;
|
|
1413
|
+
}
|
|
1414
|
+
else if (!ctx.inflightBlock) {
|
|
1294
1415
|
try {
|
|
1295
|
-
const [block, value] =
|
|
1416
|
+
const [block, value] = runComponent(ctx, hydrationData);
|
|
1296
1417
|
if (block) {
|
|
1297
1418
|
ctx.inflightBlock = block
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
}
|
|
1302
|
-
})
|
|
1419
|
+
// TODO: there is some fuckery going on here related to async
|
|
1420
|
+
// generator components resuming when they’re meant to be returned.
|
|
1421
|
+
.then((v) => v)
|
|
1303
1422
|
.finally(() => advanceComponent(ctx));
|
|
1304
1423
|
// stepComponent will only return a block if the value is asynchronous
|
|
1305
1424
|
ctx.inflightValue = value;
|
|
@@ -1313,38 +1432,46 @@ function runComponent(ctx) {
|
|
|
1313
1432
|
throw err;
|
|
1314
1433
|
}
|
|
1315
1434
|
}
|
|
1316
|
-
else if (ctx.f & IsAsyncGen) {
|
|
1317
|
-
return ctx.inflightValue;
|
|
1318
|
-
}
|
|
1319
1435
|
else if (!ctx.enqueuedBlock) {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1436
|
+
if (hydrationData !== undefined) {
|
|
1437
|
+
throw new Error("Hydration error");
|
|
1438
|
+
}
|
|
1439
|
+
// We need to assign enqueuedBlock and enqueuedValue synchronously, hence
|
|
1440
|
+
// the Promise constructor call here.
|
|
1441
|
+
let resolveEnqueuedBlock;
|
|
1442
|
+
ctx.enqueuedBlock = new Promise((resolve) => (resolveEnqueuedBlock = resolve));
|
|
1443
|
+
ctx.enqueuedValue = ctx.inflightBlock.then(() => {
|
|
1323
1444
|
try {
|
|
1324
|
-
const [block, value] =
|
|
1325
|
-
resolve(value);
|
|
1445
|
+
const [block, value] = runComponent(ctx);
|
|
1326
1446
|
if (block) {
|
|
1327
|
-
|
|
1328
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1329
|
-
return propagateError(ctx.parent, err);
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1447
|
+
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1332
1448
|
}
|
|
1449
|
+
return value;
|
|
1333
1450
|
}
|
|
1334
1451
|
catch (err) {
|
|
1335
1452
|
if (!(ctx.f & IsUpdating)) {
|
|
1336
1453
|
return propagateError(ctx.parent, err);
|
|
1337
1454
|
}
|
|
1455
|
+
throw err;
|
|
1338
1456
|
}
|
|
1339
|
-
})
|
|
1340
|
-
.finally(() => advanceComponent(ctx));
|
|
1341
|
-
ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
|
|
1457
|
+
});
|
|
1342
1458
|
}
|
|
1343
1459
|
return ctx.enqueuedValue;
|
|
1344
1460
|
}
|
|
1461
|
+
/** Called when the inflight block promise settles. */
|
|
1462
|
+
function advanceComponent(ctx) {
|
|
1463
|
+
if (ctx.f & IsAsyncGen && !(ctx.f & IsInForOfLoop)) {
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1467
|
+
ctx.inflightValue = ctx.enqueuedValue;
|
|
1468
|
+
ctx.enqueuedBlock = undefined;
|
|
1469
|
+
ctx.enqueuedValue = undefined;
|
|
1470
|
+
}
|
|
1345
1471
|
/**
|
|
1346
1472
|
* This function is responsible for executing the component and handling all
|
|
1347
|
-
* the different component types.
|
|
1473
|
+
* the different component types. We cannot identify whether a component is a
|
|
1474
|
+
* generator or async without calling it and inspecting the return value.
|
|
1348
1475
|
*
|
|
1349
1476
|
* @returns {[block, value]} A tuple where
|
|
1350
1477
|
* block - A possible promise which represents the duration during which the
|
|
@@ -1359,25 +1486,23 @@ function runComponent(ctx) {
|
|
|
1359
1486
|
* - Sync generator components block while any children are executing, because
|
|
1360
1487
|
* they are expected to only resume when they’ve actually rendered.
|
|
1361
1488
|
*/
|
|
1362
|
-
function
|
|
1489
|
+
function runComponent(ctx, hydrationData) {
|
|
1363
1490
|
const ret = ctx.ret;
|
|
1364
|
-
if (ctx.f & IsDone) {
|
|
1365
|
-
return [undefined, getValue(ret)];
|
|
1366
|
-
}
|
|
1367
1491
|
const initial = !ctx.iterator;
|
|
1368
1492
|
if (initial) {
|
|
1369
|
-
ctx
|
|
1493
|
+
resumePropsIterator(ctx);
|
|
1494
|
+
ctx.f |= IsSyncExecuting;
|
|
1370
1495
|
clearEventListeners(ctx);
|
|
1371
1496
|
let result;
|
|
1372
1497
|
try {
|
|
1373
|
-
result = ret.el.tag.call(ctx.
|
|
1498
|
+
result = ret.el.tag.call(ctx.owner, ret.el.props);
|
|
1374
1499
|
}
|
|
1375
1500
|
catch (err) {
|
|
1376
1501
|
ctx.f |= IsErrored;
|
|
1377
1502
|
throw err;
|
|
1378
1503
|
}
|
|
1379
1504
|
finally {
|
|
1380
|
-
ctx.f &= ~
|
|
1505
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1381
1506
|
}
|
|
1382
1507
|
if (isIteratorLike(result)) {
|
|
1383
1508
|
ctx.iterator = result;
|
|
@@ -1385,124 +1510,241 @@ function stepComponent(ctx) {
|
|
|
1385
1510
|
else if (isPromiseLike(result)) {
|
|
1386
1511
|
// async function component
|
|
1387
1512
|
const result1 = result instanceof Promise ? result : Promise.resolve(result);
|
|
1388
|
-
const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
|
|
1513
|
+
const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
|
|
1389
1514
|
ctx.f |= IsErrored;
|
|
1390
1515
|
throw err;
|
|
1391
1516
|
});
|
|
1392
|
-
return [result1, value];
|
|
1517
|
+
return [result1.catch(NOOP), value];
|
|
1393
1518
|
}
|
|
1394
1519
|
else {
|
|
1395
1520
|
// sync function component
|
|
1396
|
-
return [
|
|
1521
|
+
return [
|
|
1522
|
+
undefined,
|
|
1523
|
+
updateComponentChildren(ctx, result, hydrationData),
|
|
1524
|
+
];
|
|
1397
1525
|
}
|
|
1398
1526
|
}
|
|
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));
|
|
1527
|
+
else if (hydrationData !== undefined) {
|
|
1528
|
+
throw new Error("Hydration error");
|
|
1413
1529
|
}
|
|
1414
1530
|
let iteration;
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1531
|
+
if (initial) {
|
|
1532
|
+
try {
|
|
1533
|
+
ctx.f |= IsSyncExecuting;
|
|
1534
|
+
iteration = ctx.iterator.next();
|
|
1535
|
+
}
|
|
1536
|
+
catch (err) {
|
|
1537
|
+
ctx.f |= IsErrored;
|
|
1538
|
+
throw err;
|
|
1539
|
+
}
|
|
1540
|
+
finally {
|
|
1541
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1542
|
+
}
|
|
1543
|
+
if (isPromiseLike(iteration)) {
|
|
1544
|
+
ctx.f |= IsAsyncGen;
|
|
1545
|
+
}
|
|
1546
|
+
else {
|
|
1547
|
+
ctx.f |= IsSyncGen;
|
|
1548
|
+
}
|
|
1422
1549
|
}
|
|
1423
|
-
|
|
1424
|
-
ctx.f &= ~
|
|
1550
|
+
if (ctx.f & IsSyncGen) {
|
|
1551
|
+
ctx.f &= ~NeedsToYield;
|
|
1552
|
+
// sync generator component
|
|
1553
|
+
if (!initial) {
|
|
1554
|
+
try {
|
|
1555
|
+
ctx.f |= IsSyncExecuting;
|
|
1556
|
+
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1557
|
+
}
|
|
1558
|
+
catch (err) {
|
|
1559
|
+
ctx.f |= IsErrored;
|
|
1560
|
+
throw err;
|
|
1561
|
+
}
|
|
1562
|
+
finally {
|
|
1563
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (isPromiseLike(iteration)) {
|
|
1567
|
+
throw new Error("Mixed generator component");
|
|
1568
|
+
}
|
|
1569
|
+
if (iteration.done) {
|
|
1570
|
+
ctx.f &= ~IsSyncGen;
|
|
1571
|
+
ctx.iterator = undefined;
|
|
1572
|
+
}
|
|
1573
|
+
let value;
|
|
1574
|
+
try {
|
|
1575
|
+
value = updateComponentChildren(ctx,
|
|
1576
|
+
// Children can be void so we eliminate that here
|
|
1577
|
+
iteration.value, hydrationData);
|
|
1578
|
+
if (isPromiseLike(value)) {
|
|
1579
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
catch (err) {
|
|
1583
|
+
value = handleChildError(ctx, err);
|
|
1584
|
+
}
|
|
1585
|
+
const block = isPromiseLike(value) ? value.catch(NOOP) : undefined;
|
|
1586
|
+
return [block, value];
|
|
1425
1587
|
}
|
|
1426
|
-
if (
|
|
1427
|
-
//
|
|
1428
|
-
|
|
1429
|
-
|
|
1588
|
+
else if (ctx.f & IsInForOfLoop) {
|
|
1589
|
+
// TODO: does this need to be done async?
|
|
1590
|
+
ctx.f &= ~NeedsToYield;
|
|
1591
|
+
// we are in a for...of loop for async generator
|
|
1592
|
+
if (!initial) {
|
|
1593
|
+
try {
|
|
1594
|
+
ctx.f |= IsSyncExecuting;
|
|
1595
|
+
iteration = ctx.iterator.next(ctx.renderer.read(getValue(ret)));
|
|
1596
|
+
}
|
|
1597
|
+
catch (err) {
|
|
1598
|
+
ctx.f |= IsErrored;
|
|
1599
|
+
throw err;
|
|
1600
|
+
}
|
|
1601
|
+
finally {
|
|
1602
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (!isPromiseLike(iteration)) {
|
|
1606
|
+
throw new Error("Mixed generator component");
|
|
1430
1607
|
}
|
|
1608
|
+
const block = iteration.catch(NOOP);
|
|
1431
1609
|
const value = iteration.then((iteration) => {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
ctx.f &= ~IsIterating;
|
|
1436
|
-
if (iteration.done) {
|
|
1437
|
-
ctx.f |= IsDone;
|
|
1610
|
+
let value;
|
|
1611
|
+
if (!(ctx.f & IsInForOfLoop)) {
|
|
1612
|
+
runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
|
|
1438
1613
|
}
|
|
1439
1614
|
try {
|
|
1440
|
-
|
|
1615
|
+
value = updateComponentChildren(ctx,
|
|
1616
|
+
// Children can be void so we eliminate that here
|
|
1617
|
+
iteration.value, hydrationData);
|
|
1441
1618
|
if (isPromiseLike(value)) {
|
|
1442
|
-
|
|
1619
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1443
1620
|
}
|
|
1444
|
-
return value;
|
|
1445
1621
|
}
|
|
1446
1622
|
catch (err) {
|
|
1447
|
-
|
|
1623
|
+
value = handleChildError(ctx, err);
|
|
1448
1624
|
}
|
|
1625
|
+
return value;
|
|
1449
1626
|
}, (err) => {
|
|
1450
|
-
ctx.f |=
|
|
1627
|
+
ctx.f |= IsErrored;
|
|
1451
1628
|
throw err;
|
|
1452
1629
|
});
|
|
1453
|
-
return [
|
|
1454
|
-
}
|
|
1455
|
-
// sync generator component
|
|
1456
|
-
if (initial) {
|
|
1457
|
-
ctx.f |= IsSyncGen;
|
|
1630
|
+
return [block, value];
|
|
1458
1631
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1632
|
+
else {
|
|
1633
|
+
runAsyncGenComponent(ctx, iteration, hydrationData);
|
|
1634
|
+
// async generator component
|
|
1635
|
+
return [ctx.inflightBlock, ctx.inflightValue];
|
|
1462
1636
|
}
|
|
1463
|
-
|
|
1637
|
+
}
|
|
1638
|
+
async function runAsyncGenComponent(ctx, iterationP, hydrationData) {
|
|
1639
|
+
let done = false;
|
|
1464
1640
|
try {
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1641
|
+
while (!done) {
|
|
1642
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1643
|
+
break;
|
|
1644
|
+
}
|
|
1645
|
+
// inflightValue must be set synchronously.
|
|
1646
|
+
let onValue;
|
|
1647
|
+
ctx.inflightValue = new Promise((resolve) => (onValue = resolve));
|
|
1648
|
+
if (ctx.f & IsUpdating) {
|
|
1649
|
+
// We should not swallow unhandled promise rejections if the component is
|
|
1650
|
+
// updating independently.
|
|
1651
|
+
// TODO: Does this handle this.refresh() calls?
|
|
1652
|
+
ctx.inflightValue.catch(NOOP);
|
|
1653
|
+
}
|
|
1654
|
+
let iteration;
|
|
1655
|
+
try {
|
|
1656
|
+
iteration = await iterationP;
|
|
1657
|
+
}
|
|
1658
|
+
catch (err) {
|
|
1659
|
+
done = true;
|
|
1660
|
+
ctx.f |= IsErrored;
|
|
1661
|
+
onValue(Promise.reject(err));
|
|
1662
|
+
break;
|
|
1663
|
+
}
|
|
1664
|
+
finally {
|
|
1665
|
+
ctx.f &= ~NeedsToYield;
|
|
1666
|
+
if (!(ctx.f & IsInForAwaitOfLoop)) {
|
|
1667
|
+
ctx.f &= ~PropsAvailable;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
done = !!iteration.done;
|
|
1671
|
+
let value;
|
|
1672
|
+
try {
|
|
1673
|
+
value = updateComponentChildren(ctx, iteration.value, hydrationData);
|
|
1674
|
+
hydrationData = undefined;
|
|
1675
|
+
if (isPromiseLike(value)) {
|
|
1676
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
catch (err) {
|
|
1680
|
+
// Do we need to catch potential errors here in the case of unhandled
|
|
1681
|
+
// promise rejections?
|
|
1682
|
+
value = handleChildError(ctx, err);
|
|
1683
|
+
}
|
|
1684
|
+
finally {
|
|
1685
|
+
onValue(value);
|
|
1686
|
+
}
|
|
1687
|
+
// TODO: this can be done more elegantly
|
|
1688
|
+
let oldValue;
|
|
1689
|
+
if (ctx.ret.inflightValue) {
|
|
1690
|
+
// The value passed back into the generator as the argument to the next
|
|
1691
|
+
// method is a promise if an async generator component has async
|
|
1692
|
+
// children. Sync generator components only resume when their children
|
|
1693
|
+
// have fulfilled so the element’s inflight child values will never be
|
|
1694
|
+
// defined.
|
|
1695
|
+
oldValue = ctx.ret.inflightValue.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1696
|
+
}
|
|
1697
|
+
else {
|
|
1698
|
+
oldValue = ctx.renderer.read(getValue(ctx.ret));
|
|
1699
|
+
}
|
|
1700
|
+
if (ctx.f & IsUnmounted) {
|
|
1701
|
+
if (ctx.f & IsInForAwaitOfLoop) {
|
|
1702
|
+
try {
|
|
1703
|
+
ctx.f |= IsSyncExecuting;
|
|
1704
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1705
|
+
}
|
|
1706
|
+
finally {
|
|
1707
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
else {
|
|
1711
|
+
returnComponent(ctx);
|
|
1712
|
+
break;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
else if (!done && !(ctx.f & IsInForOfLoop)) {
|
|
1716
|
+
try {
|
|
1717
|
+
ctx.f |= IsSyncExecuting;
|
|
1718
|
+
iterationP = ctx.iterator.next(oldValue);
|
|
1719
|
+
}
|
|
1720
|
+
finally {
|
|
1721
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1468
1724
|
}
|
|
1469
1725
|
}
|
|
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);
|
|
1726
|
+
finally {
|
|
1727
|
+
if (done) {
|
|
1728
|
+
ctx.f &= ~IsAsyncGen;
|
|
1729
|
+
ctx.iterator = undefined;
|
|
1730
|
+
}
|
|
1488
1731
|
}
|
|
1489
1732
|
}
|
|
1490
1733
|
/**
|
|
1491
|
-
* Called to
|
|
1492
|
-
* generator components.
|
|
1734
|
+
* Called to resume the props async iterator for async generator components.
|
|
1493
1735
|
*/
|
|
1494
|
-
function
|
|
1495
|
-
if (ctx.
|
|
1496
|
-
ctx.
|
|
1497
|
-
ctx.
|
|
1736
|
+
function resumePropsIterator(ctx) {
|
|
1737
|
+
if (ctx.onProps) {
|
|
1738
|
+
ctx.onProps(ctx.ret.el.props);
|
|
1739
|
+
ctx.onProps = undefined;
|
|
1740
|
+
ctx.f &= ~PropsAvailable;
|
|
1498
1741
|
}
|
|
1499
1742
|
else {
|
|
1500
|
-
ctx.f |=
|
|
1743
|
+
ctx.f |= PropsAvailable;
|
|
1501
1744
|
}
|
|
1502
1745
|
}
|
|
1503
1746
|
// TODO: async unmounting
|
|
1504
1747
|
function unmountComponent(ctx) {
|
|
1505
|
-
ctx.f |= IsUnmounted;
|
|
1506
1748
|
clearEventListeners(ctx);
|
|
1507
1749
|
const callbacks = cleanupMap.get(ctx);
|
|
1508
1750
|
if (callbacks) {
|
|
@@ -1512,23 +1754,71 @@ function unmountComponent(ctx) {
|
|
|
1512
1754
|
callback(value);
|
|
1513
1755
|
}
|
|
1514
1756
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
ctx.f
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1757
|
+
ctx.f |= IsUnmounted;
|
|
1758
|
+
if (ctx.iterator) {
|
|
1759
|
+
if (ctx.f & IsSyncGen) {
|
|
1760
|
+
let value;
|
|
1761
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1762
|
+
value = enqueueComponentRun(ctx);
|
|
1763
|
+
}
|
|
1764
|
+
if (isPromiseLike(value)) {
|
|
1765
|
+
value.then(() => {
|
|
1766
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1767
|
+
unmountComponent(ctx);
|
|
1768
|
+
}
|
|
1769
|
+
else {
|
|
1770
|
+
returnComponent(ctx);
|
|
1771
|
+
}
|
|
1772
|
+
}, (err) => {
|
|
1773
|
+
propagateError(ctx.parent, err);
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
else {
|
|
1777
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1778
|
+
unmountComponent(ctx);
|
|
1779
|
+
}
|
|
1780
|
+
else {
|
|
1781
|
+
returnComponent(ctx);
|
|
1524
1782
|
}
|
|
1525
1783
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1784
|
+
}
|
|
1785
|
+
else if (ctx.f & IsAsyncGen) {
|
|
1786
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1787
|
+
const value = enqueueComponentRun(ctx);
|
|
1788
|
+
value.then(() => {
|
|
1789
|
+
if (ctx.f & IsInForOfLoop) {
|
|
1790
|
+
unmountComponent(ctx);
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
returnComponent(ctx);
|
|
1794
|
+
}
|
|
1795
|
+
}, (err) => {
|
|
1796
|
+
propagateError(ctx.parent, err);
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
// The logic for unmounting async generator components is in the
|
|
1801
|
+
// runAsyncGenComponent function.
|
|
1802
|
+
resumePropsIterator(ctx);
|
|
1528
1803
|
}
|
|
1529
1804
|
}
|
|
1530
1805
|
}
|
|
1531
1806
|
}
|
|
1807
|
+
function returnComponent(ctx) {
|
|
1808
|
+
resumePropsIterator(ctx);
|
|
1809
|
+
if (ctx.iterator && typeof ctx.iterator.return === "function") {
|
|
1810
|
+
try {
|
|
1811
|
+
ctx.f |= IsSyncExecuting;
|
|
1812
|
+
const iteration = ctx.iterator.return();
|
|
1813
|
+
if (isPromiseLike(iteration)) {
|
|
1814
|
+
iteration.catch((err) => propagateError(ctx.parent, err));
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
finally {
|
|
1818
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1532
1822
|
/*** EVENT TARGET UTILITIES ***/
|
|
1533
1823
|
// EVENT PHASE CONSTANTS
|
|
1534
1824
|
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
@@ -1597,39 +1887,39 @@ function clearEventListeners(ctx) {
|
|
|
1597
1887
|
}
|
|
1598
1888
|
}
|
|
1599
1889
|
/*** ERROR HANDLING UTILITIES ***/
|
|
1600
|
-
// TODO: generator components which throw errors should be recoverable
|
|
1601
1890
|
function handleChildError(ctx, err) {
|
|
1602
|
-
if (ctx.
|
|
1603
|
-
!ctx.iterator ||
|
|
1604
|
-
typeof ctx.iterator.throw !== "function") {
|
|
1891
|
+
if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
|
|
1605
1892
|
throw err;
|
|
1606
1893
|
}
|
|
1607
|
-
|
|
1894
|
+
resumePropsIterator(ctx);
|
|
1608
1895
|
let iteration;
|
|
1609
1896
|
try {
|
|
1610
|
-
ctx.f |=
|
|
1897
|
+
ctx.f |= IsSyncExecuting;
|
|
1611
1898
|
iteration = ctx.iterator.throw(err);
|
|
1612
1899
|
}
|
|
1613
1900
|
catch (err) {
|
|
1614
|
-
ctx.f |=
|
|
1901
|
+
ctx.f |= IsErrored;
|
|
1615
1902
|
throw err;
|
|
1616
1903
|
}
|
|
1617
1904
|
finally {
|
|
1618
|
-
ctx.f &= ~
|
|
1905
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1619
1906
|
}
|
|
1620
1907
|
if (isPromiseLike(iteration)) {
|
|
1621
1908
|
return iteration.then((iteration) => {
|
|
1622
1909
|
if (iteration.done) {
|
|
1623
|
-
ctx.f
|
|
1910
|
+
ctx.f &= ~IsAsyncGen;
|
|
1911
|
+
ctx.iterator = undefined;
|
|
1624
1912
|
}
|
|
1625
1913
|
return updateComponentChildren(ctx, iteration.value);
|
|
1626
1914
|
}, (err) => {
|
|
1627
|
-
ctx.f |=
|
|
1915
|
+
ctx.f |= IsErrored;
|
|
1628
1916
|
throw err;
|
|
1629
1917
|
});
|
|
1630
1918
|
}
|
|
1631
1919
|
if (iteration.done) {
|
|
1632
|
-
ctx.f
|
|
1920
|
+
ctx.f &= ~IsSyncGen;
|
|
1921
|
+
ctx.f &= ~IsAsyncGen;
|
|
1922
|
+
ctx.iterator = undefined;
|
|
1633
1923
|
}
|
|
1634
1924
|
return updateComponentChildren(ctx, iteration.value);
|
|
1635
1925
|
}
|