@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.cjs
CHANGED
|
@@ -25,7 +25,8 @@ function arrayify(value) {
|
|
|
25
25
|
: typeof value === "string" ||
|
|
26
26
|
typeof value[Symbol.iterator] !== "function"
|
|
27
27
|
? [value]
|
|
28
|
-
:
|
|
28
|
+
: // TODO: inference broke in TypeScript 3.9.
|
|
29
|
+
[...value];
|
|
29
30
|
}
|
|
30
31
|
function isIteratorLike(value) {
|
|
31
32
|
return value != null && typeof value.next === "function";
|
|
@@ -74,8 +75,7 @@ const Copy = Symbol.for("crank.Copy");
|
|
|
74
75
|
/**
|
|
75
76
|
* A special tag for injecting raw nodes or strings via a value prop.
|
|
76
77
|
*
|
|
77
|
-
*
|
|
78
|
-
* the string and the result will be set as the element’s value.
|
|
78
|
+
* Renderer.prototype.raw() is called with the value prop.
|
|
79
79
|
*/
|
|
80
80
|
const Raw = Symbol.for("crank.Raw");
|
|
81
81
|
const ElementSymbol = Symbol.for("crank.Element");
|
|
@@ -161,22 +161,6 @@ function createElement(tag, props, ...children) {
|
|
|
161
161
|
else if (children.length === 1) {
|
|
162
162
|
props1.children = children[0];
|
|
163
163
|
}
|
|
164
|
-
// string aliases for the special tags
|
|
165
|
-
// TODO: Does this logic belong here, or in the Element constructor
|
|
166
|
-
switch (tag) {
|
|
167
|
-
case "$FRAGMENT":
|
|
168
|
-
tag = Fragment;
|
|
169
|
-
break;
|
|
170
|
-
case "$PORTAL":
|
|
171
|
-
tag = Portal;
|
|
172
|
-
break;
|
|
173
|
-
case "$COPY":
|
|
174
|
-
tag = Copy;
|
|
175
|
-
break;
|
|
176
|
-
case "$RAW":
|
|
177
|
-
tag = Raw;
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
164
|
return new Element(tag, props1, key, ref, static_);
|
|
181
165
|
}
|
|
182
166
|
/** Clones a given element, shallowly copying the props object. */
|
|
@@ -255,13 +239,13 @@ function normalize(values) {
|
|
|
255
239
|
class Retainer {
|
|
256
240
|
constructor(el) {
|
|
257
241
|
this.el = el;
|
|
258
|
-
this.value = undefined;
|
|
259
242
|
this.ctx = undefined;
|
|
260
243
|
this.children = undefined;
|
|
261
|
-
this.
|
|
262
|
-
this.
|
|
263
|
-
this.
|
|
264
|
-
this.
|
|
244
|
+
this.value = undefined;
|
|
245
|
+
this.cachedChildValues = undefined;
|
|
246
|
+
this.fallbackValue = undefined;
|
|
247
|
+
this.inflightValue = undefined;
|
|
248
|
+
this.onNextValues = undefined;
|
|
265
249
|
}
|
|
266
250
|
}
|
|
267
251
|
/**
|
|
@@ -270,10 +254,10 @@ class Retainer {
|
|
|
270
254
|
* @returns The value of the element.
|
|
271
255
|
*/
|
|
272
256
|
function getValue(ret) {
|
|
273
|
-
if (typeof ret.
|
|
274
|
-
return typeof ret.
|
|
275
|
-
? getValue(ret.
|
|
276
|
-
: ret.
|
|
257
|
+
if (typeof ret.fallbackValue !== "undefined") {
|
|
258
|
+
return typeof ret.fallbackValue === "object"
|
|
259
|
+
? getValue(ret.fallbackValue)
|
|
260
|
+
: ret.fallbackValue;
|
|
277
261
|
}
|
|
278
262
|
else if (ret.el.tag === Portal) {
|
|
279
263
|
return;
|
|
@@ -289,8 +273,8 @@ function getValue(ret) {
|
|
|
289
273
|
* @returns A normalized array of nodes and strings.
|
|
290
274
|
*/
|
|
291
275
|
function getChildValues(ret) {
|
|
292
|
-
if (ret.
|
|
293
|
-
return wrap(ret.
|
|
276
|
+
if (ret.cachedChildValues) {
|
|
277
|
+
return wrap(ret.cachedChildValues);
|
|
294
278
|
}
|
|
295
279
|
const values = [];
|
|
296
280
|
const children = wrap(ret.children);
|
|
@@ -303,7 +287,7 @@ function getChildValues(ret) {
|
|
|
303
287
|
const values1 = normalize(values);
|
|
304
288
|
const tag = ret.el.tag;
|
|
305
289
|
if (typeof tag === "function" || (tag !== Fragment && tag !== Raw)) {
|
|
306
|
-
ret.
|
|
290
|
+
ret.cachedChildValues = unwrap(values1);
|
|
307
291
|
}
|
|
308
292
|
return values1;
|
|
309
293
|
}
|
|
@@ -311,16 +295,19 @@ const defaultRendererImpl = {
|
|
|
311
295
|
create() {
|
|
312
296
|
throw new Error("Not implemented");
|
|
313
297
|
},
|
|
298
|
+
hydrate() {
|
|
299
|
+
throw new Error("Not implemented");
|
|
300
|
+
},
|
|
314
301
|
scope: IDENTITY,
|
|
315
302
|
read: IDENTITY,
|
|
316
|
-
|
|
317
|
-
|
|
303
|
+
text: IDENTITY,
|
|
304
|
+
raw: IDENTITY,
|
|
318
305
|
patch: NOOP,
|
|
319
306
|
arrange: NOOP,
|
|
320
307
|
dispose: NOOP,
|
|
321
308
|
flush: NOOP,
|
|
322
309
|
};
|
|
323
|
-
const
|
|
310
|
+
const _RendererImpl = Symbol.for("crank.RendererImpl");
|
|
324
311
|
/**
|
|
325
312
|
* An abstract class which is subclassed to render to different target
|
|
326
313
|
* environments. This class is responsible for kicking off the rendering
|
|
@@ -334,7 +321,7 @@ const $RendererImpl = Symbol.for("crank.RendererImpl");
|
|
|
334
321
|
class Renderer {
|
|
335
322
|
constructor(impl) {
|
|
336
323
|
this.cache = new WeakMap();
|
|
337
|
-
this[
|
|
324
|
+
this[_RendererImpl] = {
|
|
338
325
|
...defaultRendererImpl,
|
|
339
326
|
...impl,
|
|
340
327
|
};
|
|
@@ -346,7 +333,7 @@ class Renderer {
|
|
|
346
333
|
* used root to delete the previously rendered element tree from the cache.
|
|
347
334
|
* @param root - The node to be rendered into. The renderer will cache
|
|
348
335
|
* element trees per root.
|
|
349
|
-
* @param
|
|
336
|
+
* @param bridge - An optional context that will be the ancestor context of all
|
|
350
337
|
* elements in the tree. Useful for connecting different renderers so that
|
|
351
338
|
* events/provisions properly propagate. The context for a given root must be
|
|
352
339
|
* the same or an error will be thrown.
|
|
@@ -356,7 +343,7 @@ class Renderer {
|
|
|
356
343
|
*/
|
|
357
344
|
render(children, root, bridge) {
|
|
358
345
|
let ret;
|
|
359
|
-
const ctx = bridge && bridge[
|
|
346
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
360
347
|
if (typeof root === "object" && root !== null) {
|
|
361
348
|
ret = this.cache.get(root);
|
|
362
349
|
}
|
|
@@ -379,8 +366,32 @@ class Renderer {
|
|
|
379
366
|
this.cache.delete(root);
|
|
380
367
|
}
|
|
381
368
|
}
|
|
382
|
-
const impl = this[
|
|
383
|
-
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children);
|
|
369
|
+
const impl = this[_RendererImpl];
|
|
370
|
+
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, undefined);
|
|
371
|
+
// We return the child values of the portal because portal elements
|
|
372
|
+
// themselves have no readable value.
|
|
373
|
+
if (isPromiseLike(childValues)) {
|
|
374
|
+
return childValues.then((childValues) => commitRootRender(impl, root, ctx, ret, childValues, oldProps));
|
|
375
|
+
}
|
|
376
|
+
return commitRootRender(impl, root, ctx, ret, childValues, oldProps);
|
|
377
|
+
}
|
|
378
|
+
hydrate(children, root, bridge) {
|
|
379
|
+
const impl = this[_RendererImpl];
|
|
380
|
+
const ctx = bridge && bridge[_ContextImpl];
|
|
381
|
+
let ret;
|
|
382
|
+
ret = this.cache.get(root);
|
|
383
|
+
if (ret !== undefined) {
|
|
384
|
+
// If there is a retainer for the root, hydration is not necessary.
|
|
385
|
+
return this.render(children, root, bridge);
|
|
386
|
+
}
|
|
387
|
+
let oldProps;
|
|
388
|
+
ret = new Retainer(createElement(Portal, { children, root }));
|
|
389
|
+
ret.value = root;
|
|
390
|
+
if (typeof root === "object" && root !== null && children != null) {
|
|
391
|
+
this.cache.set(root, ret);
|
|
392
|
+
}
|
|
393
|
+
const hydrationData = impl.hydrate(Portal, root, {});
|
|
394
|
+
const childValues = diffChildren(impl, root, ret, ctx, impl.scope(undefined, Portal, ret.el.props), ret, children, hydrationData);
|
|
384
395
|
// We return the child values of the portal because portal elements
|
|
385
396
|
// themselves have no readable value.
|
|
386
397
|
if (isPromiseLike(childValues)) {
|
|
@@ -392,17 +403,17 @@ class Renderer {
|
|
|
392
403
|
/*** PRIVATE RENDERER FUNCTIONS ***/
|
|
393
404
|
function commitRootRender(renderer, root, ctx, ret, childValues, oldProps) {
|
|
394
405
|
// element is a host or portal element
|
|
395
|
-
if (root
|
|
396
|
-
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.
|
|
406
|
+
if (root != null) {
|
|
407
|
+
renderer.arrange(Portal, root, ret.el.props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
397
408
|
flush(renderer, root);
|
|
398
409
|
}
|
|
399
|
-
ret.
|
|
410
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
400
411
|
if (root == null) {
|
|
401
412
|
unmount(renderer, ret, ctx, ret);
|
|
402
413
|
}
|
|
403
|
-
return renderer.read(ret.
|
|
414
|
+
return renderer.read(ret.cachedChildValues);
|
|
404
415
|
}
|
|
405
|
-
function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
416
|
+
function diffChildren(renderer, root, host, ctx, scope, parent, children, hydrationData) {
|
|
406
417
|
const oldRetained = wrap(parent.children);
|
|
407
418
|
const newRetained = [];
|
|
408
419
|
const newChildren = arrayify(children);
|
|
@@ -411,14 +422,15 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
411
422
|
let childrenByKey;
|
|
412
423
|
let seenKeys;
|
|
413
424
|
let isAsync = false;
|
|
414
|
-
let
|
|
425
|
+
let hydrationBlock;
|
|
426
|
+
let oi = 0;
|
|
427
|
+
let oldLength = oldRetained.length;
|
|
415
428
|
for (let ni = 0, newLength = newChildren.length; ni < newLength; ni++) {
|
|
416
|
-
//
|
|
417
|
-
// deoptimizations.
|
|
429
|
+
// length checks to prevent index out of bounds deoptimizations.
|
|
418
430
|
let ret = oi >= oldLength ? undefined : oldRetained[oi];
|
|
419
431
|
let child = narrow(newChildren[ni]);
|
|
420
432
|
{
|
|
421
|
-
//
|
|
433
|
+
// aligning new children with old retainers
|
|
422
434
|
let oldKey = typeof ret === "object" ? ret.el.key : undefined;
|
|
423
435
|
let newKey = typeof child === "object" ? child.key : undefined;
|
|
424
436
|
if (newKey !== undefined && seenKeys && seenKeys.has(newKey)) {
|
|
@@ -453,18 +465,19 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
453
465
|
// Updating
|
|
454
466
|
let value;
|
|
455
467
|
if (typeof child === "object") {
|
|
456
|
-
if (
|
|
457
|
-
ret.el = child;
|
|
458
|
-
value = getInflightValue(ret);
|
|
459
|
-
}
|
|
460
|
-
else if (child.tag === Copy) {
|
|
468
|
+
if (child.tag === Copy) {
|
|
461
469
|
value = getInflightValue(ret);
|
|
462
470
|
}
|
|
463
471
|
else {
|
|
464
472
|
let oldProps;
|
|
473
|
+
let static_ = false;
|
|
465
474
|
if (typeof ret === "object" && ret.el.tag === child.tag) {
|
|
466
475
|
oldProps = ret.el.props;
|
|
467
476
|
ret.el = child;
|
|
477
|
+
if (child.static_) {
|
|
478
|
+
value = getInflightValue(ret);
|
|
479
|
+
static_ = true;
|
|
480
|
+
}
|
|
468
481
|
}
|
|
469
482
|
else {
|
|
470
483
|
if (typeof ret === "object") {
|
|
@@ -472,19 +485,28 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
472
485
|
}
|
|
473
486
|
const fallback = ret;
|
|
474
487
|
ret = new Retainer(child);
|
|
475
|
-
ret.
|
|
488
|
+
ret.fallbackValue = fallback;
|
|
476
489
|
}
|
|
477
|
-
if (
|
|
478
|
-
|
|
490
|
+
if (static_) ;
|
|
491
|
+
else if (child.tag === Raw) {
|
|
492
|
+
value = hydrationBlock
|
|
493
|
+
? hydrationBlock.then(() => updateRaw(renderer, ret, scope, oldProps, hydrationData))
|
|
494
|
+
: updateRaw(renderer, ret, scope, oldProps, hydrationData);
|
|
479
495
|
}
|
|
480
496
|
else if (child.tag === Fragment) {
|
|
481
|
-
value =
|
|
497
|
+
value = hydrationBlock
|
|
498
|
+
? hydrationBlock.then(() => updateFragment(renderer, root, host, ctx, scope, ret, hydrationData))
|
|
499
|
+
: updateFragment(renderer, root, host, ctx, scope, ret, hydrationData);
|
|
482
500
|
}
|
|
483
501
|
else if (typeof child.tag === "function") {
|
|
484
|
-
value =
|
|
502
|
+
value = hydrationBlock
|
|
503
|
+
? hydrationBlock.then(() => updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData))
|
|
504
|
+
: updateComponent(renderer, root, host, ctx, scope, ret, oldProps, hydrationData);
|
|
485
505
|
}
|
|
486
506
|
else {
|
|
487
|
-
value =
|
|
507
|
+
value = hydrationBlock
|
|
508
|
+
? hydrationBlock.then(() => updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData))
|
|
509
|
+
: updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData);
|
|
488
510
|
}
|
|
489
511
|
}
|
|
490
512
|
const ref = child.ref;
|
|
@@ -496,9 +518,14 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
496
518
|
return value;
|
|
497
519
|
});
|
|
498
520
|
}
|
|
521
|
+
if (hydrationData !== undefined) {
|
|
522
|
+
hydrationBlock = value;
|
|
523
|
+
}
|
|
499
524
|
}
|
|
500
|
-
else
|
|
501
|
-
ref
|
|
525
|
+
else {
|
|
526
|
+
if (typeof ref === "function") {
|
|
527
|
+
ref(renderer.read(value));
|
|
528
|
+
}
|
|
502
529
|
}
|
|
503
530
|
}
|
|
504
531
|
else {
|
|
@@ -507,7 +534,7 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
507
534
|
(graveyard = graveyard || []).push(ret);
|
|
508
535
|
}
|
|
509
536
|
if (typeof child === "string") {
|
|
510
|
-
value = ret = renderer.
|
|
537
|
+
value = ret = renderer.text(child, scope, hydrationData);
|
|
511
538
|
}
|
|
512
539
|
else {
|
|
513
540
|
ret = undefined;
|
|
@@ -540,27 +567,29 @@ function diffChildren(renderer, root, host, ctx, scope, parent, children) {
|
|
|
540
567
|
childValues1,
|
|
541
568
|
new Promise((resolve) => (onChildValues = resolve)),
|
|
542
569
|
]);
|
|
543
|
-
if (parent.
|
|
544
|
-
parent.
|
|
570
|
+
if (parent.onNextValues) {
|
|
571
|
+
parent.onNextValues(childValues1);
|
|
545
572
|
}
|
|
546
|
-
parent.
|
|
573
|
+
parent.onNextValues = onChildValues;
|
|
547
574
|
return childValues1.then((childValues) => {
|
|
548
|
-
parent.
|
|
575
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
549
576
|
return normalize(childValues);
|
|
550
577
|
});
|
|
551
578
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
579
|
+
else {
|
|
580
|
+
if (graveyard) {
|
|
581
|
+
for (let i = 0; i < graveyard.length; i++) {
|
|
582
|
+
unmount(renderer, host, ctx, graveyard[i]);
|
|
583
|
+
}
|
|
555
584
|
}
|
|
585
|
+
if (parent.onNextValues) {
|
|
586
|
+
parent.onNextValues(values);
|
|
587
|
+
parent.onNextValues = undefined;
|
|
588
|
+
}
|
|
589
|
+
parent.inflightValue = parent.fallbackValue = undefined;
|
|
590
|
+
// We can assert there are no promises in the array because isAsync is false
|
|
591
|
+
return normalize(values);
|
|
556
592
|
}
|
|
557
|
-
if (parent.onCommit) {
|
|
558
|
-
parent.onCommit(values);
|
|
559
|
-
parent.onCommit = undefined;
|
|
560
|
-
}
|
|
561
|
-
parent.inflight = parent.fallback = undefined;
|
|
562
|
-
// We can assert there are no promises in the array because isAsync is false
|
|
563
|
-
return normalize(values);
|
|
564
593
|
}
|
|
565
594
|
function createChildrenByKey(children, offset) {
|
|
566
595
|
const childrenByKey = new Map();
|
|
@@ -580,58 +609,69 @@ function getInflightValue(child) {
|
|
|
580
609
|
if (ctx && ctx.f & IsUpdating && ctx.inflightValue) {
|
|
581
610
|
return ctx.inflightValue;
|
|
582
611
|
}
|
|
583
|
-
else if (child.
|
|
584
|
-
return child.
|
|
612
|
+
else if (child.inflightValue) {
|
|
613
|
+
return child.inflightValue;
|
|
585
614
|
}
|
|
586
615
|
return getValue(child);
|
|
587
616
|
}
|
|
588
|
-
function updateRaw(renderer, ret, scope, oldProps) {
|
|
617
|
+
function updateRaw(renderer, ret, scope, oldProps, hydrationData) {
|
|
589
618
|
const props = ret.el.props;
|
|
590
|
-
if (
|
|
591
|
-
|
|
592
|
-
ret.value = renderer.parse(props.value, scope);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
ret.value = props.value;
|
|
619
|
+
if (!oldProps || oldProps.value !== props.value) {
|
|
620
|
+
ret.value = renderer.raw(props.value, scope, hydrationData);
|
|
597
621
|
}
|
|
598
622
|
return ret.value;
|
|
599
623
|
}
|
|
600
|
-
function updateFragment(renderer, root, host, ctx, scope, ret) {
|
|
601
|
-
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children);
|
|
624
|
+
function updateFragment(renderer, root, host, ctx, scope, ret, hydrationData) {
|
|
625
|
+
const childValues = diffChildren(renderer, root, host, ctx, scope, ret, ret.el.props.children, hydrationData);
|
|
602
626
|
if (isPromiseLike(childValues)) {
|
|
603
|
-
ret.
|
|
604
|
-
return ret.
|
|
627
|
+
ret.inflightValue = childValues.then((childValues) => unwrap(childValues));
|
|
628
|
+
return ret.inflightValue;
|
|
605
629
|
}
|
|
606
630
|
return unwrap(childValues);
|
|
607
631
|
}
|
|
608
|
-
function updateHost(renderer, root, ctx, scope, ret, oldProps) {
|
|
632
|
+
function updateHost(renderer, root, ctx, scope, ret, oldProps, hydrationData) {
|
|
609
633
|
const el = ret.el;
|
|
610
634
|
const tag = el.tag;
|
|
635
|
+
let hydrationValue;
|
|
611
636
|
if (el.tag === Portal) {
|
|
612
637
|
root = ret.value = el.props.root;
|
|
613
638
|
}
|
|
614
|
-
else
|
|
615
|
-
|
|
616
|
-
|
|
639
|
+
else {
|
|
640
|
+
if (hydrationData !== undefined) {
|
|
641
|
+
const value = hydrationData.children.shift();
|
|
642
|
+
hydrationValue = value;
|
|
643
|
+
}
|
|
617
644
|
}
|
|
618
645
|
scope = renderer.scope(scope, tag, el.props);
|
|
619
|
-
|
|
646
|
+
let childHydrationData;
|
|
647
|
+
if (hydrationValue != null && typeof hydrationValue !== "string") {
|
|
648
|
+
childHydrationData = renderer.hydrate(tag, hydrationValue, el.props);
|
|
649
|
+
if (childHydrationData === undefined) {
|
|
650
|
+
hydrationValue = undefined;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const childValues = diffChildren(renderer, root, ret, ctx, scope, ret, ret.el.props.children, childHydrationData);
|
|
620
654
|
if (isPromiseLike(childValues)) {
|
|
621
|
-
ret.
|
|
622
|
-
return ret.
|
|
655
|
+
ret.inflightValue = childValues.then((childValues) => commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue));
|
|
656
|
+
return ret.inflightValue;
|
|
623
657
|
}
|
|
624
|
-
return commitHost(renderer, scope, ret, childValues, oldProps);
|
|
658
|
+
return commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue);
|
|
625
659
|
}
|
|
626
|
-
function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
660
|
+
function commitHost(renderer, scope, ret, childValues, oldProps, hydrationValue) {
|
|
627
661
|
const tag = ret.el.tag;
|
|
628
|
-
|
|
662
|
+
let value = hydrationValue || ret.value;
|
|
629
663
|
let props = ret.el.props;
|
|
630
664
|
let copied;
|
|
631
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
|
+
}
|
|
632
670
|
for (const propName in { ...oldProps, ...props }) {
|
|
633
671
|
const propValue = props[propName];
|
|
634
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.
|
|
635
675
|
(copied = copied || new Set()).add(propName);
|
|
636
676
|
}
|
|
637
677
|
else if (propName !== "children") {
|
|
@@ -646,8 +686,8 @@ function commitHost(renderer, scope, ret, childValues, oldProps) {
|
|
|
646
686
|
}
|
|
647
687
|
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
|
|
648
688
|
}
|
|
649
|
-
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.
|
|
650
|
-
ret.
|
|
689
|
+
renderer.arrange(tag, value, props, childValues, oldProps, wrap(ret.cachedChildValues));
|
|
690
|
+
ret.cachedChildValues = unwrap(childValues);
|
|
651
691
|
if (tag === Portal) {
|
|
652
692
|
flush(renderer, ret.value);
|
|
653
693
|
return;
|
|
@@ -694,7 +734,7 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
694
734
|
}
|
|
695
735
|
else if (ret.el.tag === Portal) {
|
|
696
736
|
host = ret;
|
|
697
|
-
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));
|
|
698
738
|
flush(renderer, host.value);
|
|
699
739
|
}
|
|
700
740
|
else if (ret.el.tag !== Fragment) {
|
|
@@ -718,38 +758,43 @@ function unmount(renderer, host, ctx, ret) {
|
|
|
718
758
|
}
|
|
719
759
|
/*** CONTEXT FLAGS ***/
|
|
720
760
|
/**
|
|
721
|
-
* A flag which is
|
|
722
|
-
*
|
|
723
|
-
*
|
|
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.
|
|
724
766
|
*/
|
|
725
767
|
const IsUpdating = 1 << 0;
|
|
726
768
|
/**
|
|
727
|
-
* A flag which is
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
* 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.
|
|
731
772
|
*/
|
|
732
|
-
const
|
|
773
|
+
const IsSyncExecuting = 1 << 1;
|
|
733
774
|
/**
|
|
734
|
-
* A flag
|
|
735
|
-
* iterators without a yield.
|
|
775
|
+
* A flag which is true when the component is in a for...of loop.
|
|
736
776
|
*/
|
|
737
|
-
const
|
|
777
|
+
const IsInForOfLoop = 1 << 2;
|
|
738
778
|
/**
|
|
739
|
-
* A flag
|
|
740
|
-
* onavailable (_oa) callback to mark whether new props can be pulled via the
|
|
741
|
-
* context async iterator. See the Symbol.asyncIterator method and the
|
|
742
|
-
* resumeCtxIterator function.
|
|
779
|
+
* A flag which is true when the component is in a for await...of loop.
|
|
743
780
|
*/
|
|
744
|
-
const
|
|
781
|
+
const IsInForAwaitOfLoop = 1 << 3;
|
|
745
782
|
/**
|
|
746
|
-
* A flag which is
|
|
747
|
-
*
|
|
748
|
-
*
|
|
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.
|
|
787
|
+
*/
|
|
788
|
+
const NeedsToYield = 1 << 4;
|
|
789
|
+
/**
|
|
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.
|
|
749
794
|
*/
|
|
750
|
-
const
|
|
795
|
+
const PropsAvailable = 1 << 5;
|
|
751
796
|
/**
|
|
752
|
-
* A flag which is set when a
|
|
797
|
+
* A flag which is set when a component errors.
|
|
753
798
|
*
|
|
754
799
|
* NOTE: This is mainly used to prevent some false positives in component
|
|
755
800
|
* yields or returns undefined warnings. The reason we’re using this versus
|
|
@@ -757,28 +802,28 @@ const IsDone = 1 << 4;
|
|
|
757
802
|
* sync generator child) where synchronous code causes a stack overflow error
|
|
758
803
|
* in a non-deterministic way. Deeply disturbing stuff.
|
|
759
804
|
*/
|
|
760
|
-
const IsErrored = 1 <<
|
|
805
|
+
const IsErrored = 1 << 6;
|
|
761
806
|
/**
|
|
762
807
|
* A flag which is set when the component is unmounted. Unmounted components
|
|
763
808
|
* are no longer in the element tree and cannot refresh or rerender.
|
|
764
809
|
*/
|
|
765
|
-
const IsUnmounted = 1 <<
|
|
810
|
+
const IsUnmounted = 1 << 7;
|
|
766
811
|
/**
|
|
767
812
|
* A flag which indicates that the component is a sync generator component.
|
|
768
813
|
*/
|
|
769
|
-
const IsSyncGen = 1 <<
|
|
814
|
+
const IsSyncGen = 1 << 8;
|
|
770
815
|
/**
|
|
771
816
|
* A flag which indicates that the component is an async generator component.
|
|
772
817
|
*/
|
|
773
|
-
const IsAsyncGen = 1 <<
|
|
818
|
+
const IsAsyncGen = 1 << 9;
|
|
774
819
|
/**
|
|
775
820
|
* A flag which is set while schedule callbacks are called.
|
|
776
821
|
*/
|
|
777
|
-
const IsScheduling = 1 <<
|
|
822
|
+
const IsScheduling = 1 << 10;
|
|
778
823
|
/**
|
|
779
824
|
* A flag which is set when a schedule callback calls refresh.
|
|
780
825
|
*/
|
|
781
|
-
const IsSchedulingRefresh = 1 <<
|
|
826
|
+
const IsSchedulingRefresh = 1 << 11;
|
|
782
827
|
const provisionMaps = new WeakMap();
|
|
783
828
|
const scheduleMap = new WeakMap();
|
|
784
829
|
const cleanupMap = new WeakMap();
|
|
@@ -786,12 +831,12 @@ const cleanupMap = new WeakMap();
|
|
|
786
831
|
const flushMaps = new WeakMap();
|
|
787
832
|
/**
|
|
788
833
|
* @internal
|
|
789
|
-
* The internal class which holds
|
|
834
|
+
* The internal class which holds context data.
|
|
790
835
|
*/
|
|
791
836
|
class ContextImpl {
|
|
792
837
|
constructor(renderer, root, host, parent, scope, ret) {
|
|
793
838
|
this.f = 0;
|
|
794
|
-
this.
|
|
839
|
+
this.owner = new Context(this);
|
|
795
840
|
this.renderer = renderer;
|
|
796
841
|
this.root = root;
|
|
797
842
|
this.host = host;
|
|
@@ -803,10 +848,11 @@ class ContextImpl {
|
|
|
803
848
|
this.inflightValue = undefined;
|
|
804
849
|
this.enqueuedBlock = undefined;
|
|
805
850
|
this.enqueuedValue = undefined;
|
|
806
|
-
this.
|
|
851
|
+
this.onProps = undefined;
|
|
852
|
+
this.onPropsRequested = undefined;
|
|
807
853
|
}
|
|
808
854
|
}
|
|
809
|
-
const
|
|
855
|
+
const _ContextImpl = Symbol.for("crank.ContextImpl");
|
|
810
856
|
/**
|
|
811
857
|
* A class which is instantiated and passed to every component as its this
|
|
812
858
|
* value. Contexts form a tree just like elements and all components in the
|
|
@@ -820,8 +866,10 @@ const $ContextImpl = Symbol.for("crank.ContextImpl");
|
|
|
820
866
|
* schedule and cleanup callbacks.
|
|
821
867
|
*/
|
|
822
868
|
class Context {
|
|
869
|
+
// TODO: If we could make the constructor function take a nicer value, it
|
|
870
|
+
// would be useful for testing purposes.
|
|
823
871
|
constructor(impl) {
|
|
824
|
-
this[
|
|
872
|
+
this[_ContextImpl] = impl;
|
|
825
873
|
}
|
|
826
874
|
/**
|
|
827
875
|
* The current props of the associated element.
|
|
@@ -831,7 +879,7 @@ class Context {
|
|
|
831
879
|
* plugins or utilities which wrap contexts.
|
|
832
880
|
*/
|
|
833
881
|
get props() {
|
|
834
|
-
return this[
|
|
882
|
+
return this[_ContextImpl].ret.el.props;
|
|
835
883
|
}
|
|
836
884
|
// TODO: Should we rename this???
|
|
837
885
|
/**
|
|
@@ -842,45 +890,64 @@ class Context {
|
|
|
842
890
|
* mainly for plugins or utilities which wrap contexts.
|
|
843
891
|
*/
|
|
844
892
|
get value() {
|
|
845
|
-
return this[
|
|
893
|
+
return this[_ContextImpl].renderer.read(getValue(this[_ContextImpl].ret));
|
|
846
894
|
}
|
|
847
895
|
*[Symbol.iterator]() {
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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;
|
|
855
907
|
}
|
|
856
|
-
|
|
857
|
-
|
|
908
|
+
}
|
|
909
|
+
finally {
|
|
910
|
+
ctx.f &= ~IsInForOfLoop;
|
|
858
911
|
}
|
|
859
912
|
}
|
|
860
913
|
async *[Symbol.asyncIterator]() {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
await new Promise((resolve) => (impl.onAvailable = resolve));
|
|
878
|
-
if (impl.f & IsDone) {
|
|
879
|
-
break;
|
|
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;
|
|
880
930
|
}
|
|
931
|
+
else {
|
|
932
|
+
const props = await new Promise((resolve) => (ctx.onProps = resolve));
|
|
933
|
+
if (ctx.f & IsUnmounted) {
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
yield props;
|
|
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;
|
|
881
949
|
}
|
|
882
|
-
|
|
883
|
-
} while (!(impl.f & IsDone));
|
|
950
|
+
}
|
|
884
951
|
}
|
|
885
952
|
/**
|
|
886
953
|
* Re-executes a component.
|
|
@@ -895,32 +962,31 @@ class Context {
|
|
|
895
962
|
* async iterator to suspend.
|
|
896
963
|
*/
|
|
897
964
|
refresh() {
|
|
898
|
-
const
|
|
899
|
-
if (
|
|
965
|
+
const ctx = this[_ContextImpl];
|
|
966
|
+
if (ctx.f & IsUnmounted) {
|
|
900
967
|
console.error("Component is unmounted");
|
|
901
|
-
return
|
|
968
|
+
return ctx.renderer.read(undefined);
|
|
902
969
|
}
|
|
903
|
-
else if (
|
|
970
|
+
else if (ctx.f & IsSyncExecuting) {
|
|
904
971
|
console.error("Component is already executing");
|
|
905
972
|
return this.value;
|
|
906
973
|
}
|
|
907
|
-
|
|
908
|
-
const value = runComponent(impl);
|
|
974
|
+
const value = enqueueComponentRun(ctx);
|
|
909
975
|
if (isPromiseLike(value)) {
|
|
910
|
-
return value.then((value) =>
|
|
976
|
+
return value.then((value) => ctx.renderer.read(value));
|
|
911
977
|
}
|
|
912
|
-
return
|
|
978
|
+
return ctx.renderer.read(value);
|
|
913
979
|
}
|
|
914
980
|
/**
|
|
915
981
|
* Registers a callback which fires when the component commits. Will only
|
|
916
982
|
* fire once per callback and update.
|
|
917
983
|
*/
|
|
918
984
|
schedule(callback) {
|
|
919
|
-
const
|
|
920
|
-
let callbacks = scheduleMap.get(
|
|
985
|
+
const ctx = this[_ContextImpl];
|
|
986
|
+
let callbacks = scheduleMap.get(ctx);
|
|
921
987
|
if (!callbacks) {
|
|
922
988
|
callbacks = new Set();
|
|
923
|
-
scheduleMap.set(
|
|
989
|
+
scheduleMap.set(ctx, callbacks);
|
|
924
990
|
}
|
|
925
991
|
callbacks.add(callback);
|
|
926
992
|
}
|
|
@@ -929,19 +995,19 @@ class Context {
|
|
|
929
995
|
* rendered into the root. Will only fire once per callback and render.
|
|
930
996
|
*/
|
|
931
997
|
flush(callback) {
|
|
932
|
-
const
|
|
933
|
-
if (typeof
|
|
998
|
+
const ctx = this[_ContextImpl];
|
|
999
|
+
if (typeof ctx.root !== "object" || ctx.root === null) {
|
|
934
1000
|
return;
|
|
935
1001
|
}
|
|
936
|
-
let flushMap = flushMaps.get(
|
|
1002
|
+
let flushMap = flushMaps.get(ctx.root);
|
|
937
1003
|
if (!flushMap) {
|
|
938
1004
|
flushMap = new Map();
|
|
939
|
-
flushMaps.set(
|
|
1005
|
+
flushMaps.set(ctx.root, flushMap);
|
|
940
1006
|
}
|
|
941
|
-
let callbacks = flushMap.get(
|
|
1007
|
+
let callbacks = flushMap.get(ctx);
|
|
942
1008
|
if (!callbacks) {
|
|
943
1009
|
callbacks = new Set();
|
|
944
|
-
flushMap.set(
|
|
1010
|
+
flushMap.set(ctx, callbacks);
|
|
945
1011
|
}
|
|
946
1012
|
callbacks.add(callback);
|
|
947
1013
|
}
|
|
@@ -950,45 +1016,45 @@ class Context {
|
|
|
950
1016
|
* fire once per callback.
|
|
951
1017
|
*/
|
|
952
1018
|
cleanup(callback) {
|
|
953
|
-
const
|
|
954
|
-
let callbacks = cleanupMap.get(
|
|
1019
|
+
const ctx = this[_ContextImpl];
|
|
1020
|
+
let callbacks = cleanupMap.get(ctx);
|
|
955
1021
|
if (!callbacks) {
|
|
956
1022
|
callbacks = new Set();
|
|
957
|
-
cleanupMap.set(
|
|
1023
|
+
cleanupMap.set(ctx, callbacks);
|
|
958
1024
|
}
|
|
959
1025
|
callbacks.add(callback);
|
|
960
1026
|
}
|
|
961
1027
|
consume(key) {
|
|
962
|
-
for (let
|
|
963
|
-
const provisions = provisionMaps.get(
|
|
1028
|
+
for (let ctx = this[_ContextImpl].parent; ctx !== undefined; ctx = ctx.parent) {
|
|
1029
|
+
const provisions = provisionMaps.get(ctx);
|
|
964
1030
|
if (provisions && provisions.has(key)) {
|
|
965
1031
|
return provisions.get(key);
|
|
966
1032
|
}
|
|
967
1033
|
}
|
|
968
1034
|
}
|
|
969
1035
|
provide(key, value) {
|
|
970
|
-
const
|
|
971
|
-
let provisions = provisionMaps.get(
|
|
1036
|
+
const ctx = this[_ContextImpl];
|
|
1037
|
+
let provisions = provisionMaps.get(ctx);
|
|
972
1038
|
if (!provisions) {
|
|
973
1039
|
provisions = new Map();
|
|
974
|
-
provisionMaps.set(
|
|
1040
|
+
provisionMaps.set(ctx, provisions);
|
|
975
1041
|
}
|
|
976
1042
|
provisions.set(key, value);
|
|
977
1043
|
}
|
|
978
1044
|
addEventListener(type, listener, options) {
|
|
979
|
-
const
|
|
1045
|
+
const ctx = this[_ContextImpl];
|
|
980
1046
|
let listeners;
|
|
981
1047
|
if (!isListenerOrListenerObject(listener)) {
|
|
982
1048
|
return;
|
|
983
1049
|
}
|
|
984
1050
|
else {
|
|
985
|
-
const listeners1 = listenersMap.get(
|
|
1051
|
+
const listeners1 = listenersMap.get(ctx);
|
|
986
1052
|
if (listeners1) {
|
|
987
1053
|
listeners = listeners1;
|
|
988
1054
|
}
|
|
989
1055
|
else {
|
|
990
1056
|
listeners = [];
|
|
991
|
-
listenersMap.set(
|
|
1057
|
+
listenersMap.set(ctx, listeners);
|
|
992
1058
|
}
|
|
993
1059
|
}
|
|
994
1060
|
options = normalizeListenerOptions(options);
|
|
@@ -999,7 +1065,7 @@ class Context {
|
|
|
999
1065
|
else {
|
|
1000
1066
|
callback = listener;
|
|
1001
1067
|
}
|
|
1002
|
-
const record = { type,
|
|
1068
|
+
const record = { type, listener, callback, options };
|
|
1003
1069
|
if (options.once) {
|
|
1004
1070
|
record.callback = function () {
|
|
1005
1071
|
const i = listeners.indexOf(record);
|
|
@@ -1016,15 +1082,15 @@ class Context {
|
|
|
1016
1082
|
}
|
|
1017
1083
|
listeners.push(record);
|
|
1018
1084
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1019
|
-
for (const value of getChildValues(
|
|
1085
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1020
1086
|
if (isEventTarget(value)) {
|
|
1021
1087
|
value.addEventListener(record.type, record.callback, record.options);
|
|
1022
1088
|
}
|
|
1023
1089
|
}
|
|
1024
1090
|
}
|
|
1025
1091
|
removeEventListener(type, listener, options) {
|
|
1026
|
-
const
|
|
1027
|
-
const listeners = listenersMap.get(
|
|
1092
|
+
const ctx = this[_ContextImpl];
|
|
1093
|
+
const listeners = listenersMap.get(ctx);
|
|
1028
1094
|
if (listeners == null || !isListenerOrListenerObject(listener)) {
|
|
1029
1095
|
return;
|
|
1030
1096
|
}
|
|
@@ -1038,16 +1104,16 @@ class Context {
|
|
|
1038
1104
|
const record = listeners[i];
|
|
1039
1105
|
listeners.splice(i, 1);
|
|
1040
1106
|
// TODO: is it possible to separate out the EventTarget delegation logic
|
|
1041
|
-
for (const value of getChildValues(
|
|
1107
|
+
for (const value of getChildValues(ctx.ret)) {
|
|
1042
1108
|
if (isEventTarget(value)) {
|
|
1043
1109
|
value.removeEventListener(record.type, record.callback, record.options);
|
|
1044
1110
|
}
|
|
1045
1111
|
}
|
|
1046
1112
|
}
|
|
1047
1113
|
dispatchEvent(ev) {
|
|
1048
|
-
const
|
|
1114
|
+
const ctx = this[_ContextImpl];
|
|
1049
1115
|
const path = [];
|
|
1050
|
-
for (let parent =
|
|
1116
|
+
for (let parent = ctx.parent; parent !== undefined; parent = parent.parent) {
|
|
1051
1117
|
path.push(parent);
|
|
1052
1118
|
}
|
|
1053
1119
|
// We patch the stopImmediatePropagation method because ev.cancelBubble
|
|
@@ -1059,7 +1125,7 @@ class Context {
|
|
|
1059
1125
|
immediateCancelBubble = true;
|
|
1060
1126
|
return stopImmediatePropagation.call(ev);
|
|
1061
1127
|
});
|
|
1062
|
-
setEventProperty(ev, "target",
|
|
1128
|
+
setEventProperty(ev, "target", ctx.owner);
|
|
1063
1129
|
// The only possible errors in this block are errors thrown by callbacks,
|
|
1064
1130
|
// and dispatchEvent will only log these errors rather than throwing
|
|
1065
1131
|
// them. Therefore, we place all code in a try block, log errors in the
|
|
@@ -1068,18 +1134,21 @@ class Context {
|
|
|
1068
1134
|
// Each early return within the try block returns true because while the
|
|
1069
1135
|
// return value is overridden in the finally block, TypeScript
|
|
1070
1136
|
// (justifiably) does not recognize the unsafe return statement.
|
|
1071
|
-
//
|
|
1072
|
-
// TODO: Run all callbacks even if one of them errors
|
|
1073
1137
|
try {
|
|
1074
1138
|
setEventProperty(ev, "eventPhase", CAPTURING_PHASE);
|
|
1075
1139
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
1076
1140
|
const target = path[i];
|
|
1077
1141
|
const listeners = listenersMap.get(target);
|
|
1078
1142
|
if (listeners) {
|
|
1079
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1143
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1080
1144
|
for (const record of listeners) {
|
|
1081
1145
|
if (record.type === ev.type && record.options.capture) {
|
|
1082
|
-
|
|
1146
|
+
try {
|
|
1147
|
+
record.callback.call(target.owner, ev);
|
|
1148
|
+
}
|
|
1149
|
+
catch (err) {
|
|
1150
|
+
console.error(err);
|
|
1151
|
+
}
|
|
1083
1152
|
if (immediateCancelBubble) {
|
|
1084
1153
|
return true;
|
|
1085
1154
|
}
|
|
@@ -1091,13 +1160,25 @@ class Context {
|
|
|
1091
1160
|
}
|
|
1092
1161
|
}
|
|
1093
1162
|
{
|
|
1094
|
-
|
|
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);
|
|
1095
1173
|
if (listeners) {
|
|
1096
|
-
setEventProperty(ev, "eventPhase", AT_TARGET);
|
|
1097
|
-
setEventProperty(ev, "currentTarget", impl.ctx);
|
|
1098
1174
|
for (const record of listeners) {
|
|
1099
1175
|
if (record.type === ev.type) {
|
|
1100
|
-
|
|
1176
|
+
try {
|
|
1177
|
+
record.callback.call(ctx.owner, ev);
|
|
1178
|
+
}
|
|
1179
|
+
catch (err) {
|
|
1180
|
+
console.error(err);
|
|
1181
|
+
}
|
|
1101
1182
|
if (immediateCancelBubble) {
|
|
1102
1183
|
return true;
|
|
1103
1184
|
}
|
|
@@ -1114,10 +1195,15 @@ class Context {
|
|
|
1114
1195
|
const target = path[i];
|
|
1115
1196
|
const listeners = listenersMap.get(target);
|
|
1116
1197
|
if (listeners) {
|
|
1117
|
-
setEventProperty(ev, "currentTarget", target.
|
|
1198
|
+
setEventProperty(ev, "currentTarget", target.owner);
|
|
1118
1199
|
for (const record of listeners) {
|
|
1119
1200
|
if (record.type === ev.type && !record.options.capture) {
|
|
1120
|
-
|
|
1201
|
+
try {
|
|
1202
|
+
record.callback.call(target.owner, ev);
|
|
1203
|
+
}
|
|
1204
|
+
catch (err) {
|
|
1205
|
+
console.error(err);
|
|
1206
|
+
}
|
|
1121
1207
|
if (immediateCancelBubble) {
|
|
1122
1208
|
return true;
|
|
1123
1209
|
}
|
|
@@ -1130,10 +1216,6 @@ class Context {
|
|
|
1130
1216
|
}
|
|
1131
1217
|
}
|
|
1132
1218
|
}
|
|
1133
|
-
catch (err) {
|
|
1134
|
-
// TODO: Use setTimeout to rethrow the error.
|
|
1135
|
-
console.error(err);
|
|
1136
|
-
}
|
|
1137
1219
|
finally {
|
|
1138
1220
|
setEventProperty(ev, "eventPhase", NONE);
|
|
1139
1221
|
setEventProperty(ev, "currentTarget", null);
|
|
@@ -1151,42 +1233,47 @@ function ctxContains(parent, child) {
|
|
|
1151
1233
|
}
|
|
1152
1234
|
return false;
|
|
1153
1235
|
}
|
|
1154
|
-
function updateComponent(renderer, root, host, parent, scope, ret, oldProps) {
|
|
1236
|
+
function updateComponent(renderer, root, host, parent, scope, ret, oldProps, hydrationData) {
|
|
1155
1237
|
let ctx;
|
|
1156
1238
|
if (oldProps) {
|
|
1157
1239
|
ctx = ret.ctx;
|
|
1158
|
-
if (ctx.f &
|
|
1240
|
+
if (ctx.f & IsSyncExecuting) {
|
|
1159
1241
|
console.error("Component is already executing");
|
|
1160
|
-
return ret.
|
|
1242
|
+
return ret.cachedChildValues;
|
|
1161
1243
|
}
|
|
1162
1244
|
}
|
|
1163
1245
|
else {
|
|
1164
1246
|
ctx = ret.ctx = new ContextImpl(renderer, root, host, parent, scope, ret);
|
|
1165
1247
|
}
|
|
1166
1248
|
ctx.f |= IsUpdating;
|
|
1167
|
-
|
|
1168
|
-
return runComponent(ctx);
|
|
1249
|
+
return enqueueComponentRun(ctx, hydrationData);
|
|
1169
1250
|
}
|
|
1170
|
-
function updateComponentChildren(ctx, children) {
|
|
1171
|
-
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.
|
|
1172
1258
|
return;
|
|
1173
1259
|
}
|
|
1174
1260
|
else if (children === undefined) {
|
|
1175
1261
|
console.error("A component has returned or yielded undefined. If this was intentional, return or yield null instead.");
|
|
1176
1262
|
}
|
|
1177
1263
|
let childValues;
|
|
1178
|
-
// We set the isExecuting flag in case a child component dispatches an event
|
|
1179
|
-
// which bubbles to this component and causes a synchronous refresh().
|
|
1180
|
-
ctx.f |= IsExecuting;
|
|
1181
1264
|
try {
|
|
1182
|
-
|
|
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);
|
|
1183
1270
|
}
|
|
1184
1271
|
finally {
|
|
1185
|
-
ctx.f &= ~
|
|
1272
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1186
1273
|
}
|
|
1187
1274
|
if (isPromiseLike(childValues)) {
|
|
1188
|
-
ctx.ret.
|
|
1189
|
-
return ctx.ret.
|
|
1275
|
+
ctx.ret.inflightValue = childValues.then((childValues) => commitComponent(ctx, childValues));
|
|
1276
|
+
return ctx.ret.inflightValue;
|
|
1190
1277
|
}
|
|
1191
1278
|
return commitComponent(ctx, childValues);
|
|
1192
1279
|
}
|
|
@@ -1206,8 +1293,8 @@ function commitComponent(ctx, values) {
|
|
|
1206
1293
|
}
|
|
1207
1294
|
}
|
|
1208
1295
|
}
|
|
1209
|
-
const oldValues = wrap(ctx.ret.
|
|
1210
|
-
let value = (ctx.ret.
|
|
1296
|
+
const oldValues = wrap(ctx.ret.cachedChildValues);
|
|
1297
|
+
let value = (ctx.ret.cachedChildValues = unwrap(values));
|
|
1211
1298
|
if (ctx.f & IsScheduling) {
|
|
1212
1299
|
ctx.f |= IsSchedulingRefresh;
|
|
1213
1300
|
}
|
|
@@ -1215,7 +1302,7 @@ function commitComponent(ctx, values) {
|
|
|
1215
1302
|
// If we’re not updating the component, which happens when components are
|
|
1216
1303
|
// refreshed, or when async generator components iterate, we have to do a
|
|
1217
1304
|
// little bit housekeeping when a component’s child values have changed.
|
|
1218
|
-
if (!
|
|
1305
|
+
if (!arrayEqual(oldValues, values)) {
|
|
1219
1306
|
const records = getListenerRecords(ctx.parent, ctx.host);
|
|
1220
1307
|
if (records.length) {
|
|
1221
1308
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -1230,7 +1317,7 @@ function commitComponent(ctx, values) {
|
|
|
1230
1317
|
}
|
|
1231
1318
|
// rearranging the nearest ancestor host element
|
|
1232
1319
|
const host = ctx.host;
|
|
1233
|
-
const oldHostValues = wrap(host.
|
|
1320
|
+
const oldHostValues = wrap(host.cachedChildValues);
|
|
1234
1321
|
invalidate(ctx, host);
|
|
1235
1322
|
const hostValues = getChildValues(host);
|
|
1236
1323
|
ctx.renderer.arrange(host.el.tag, host.value, host.el.props, hostValues,
|
|
@@ -1259,50 +1346,79 @@ function commitComponent(ctx, values) {
|
|
|
1259
1346
|
}
|
|
1260
1347
|
function invalidate(ctx, host) {
|
|
1261
1348
|
for (let parent = ctx.parent; parent !== undefined && parent.host === host; parent = parent.parent) {
|
|
1262
|
-
parent.ret.
|
|
1349
|
+
parent.ret.cachedChildValues = undefined;
|
|
1263
1350
|
}
|
|
1264
|
-
host.
|
|
1351
|
+
host.cachedChildValues = undefined;
|
|
1265
1352
|
}
|
|
1266
|
-
function
|
|
1267
|
-
if (
|
|
1353
|
+
function arrayEqual(arr1, arr2) {
|
|
1354
|
+
if (arr1.length !== arr2.length) {
|
|
1268
1355
|
return false;
|
|
1269
1356
|
}
|
|
1270
|
-
for (let i = 0; i <
|
|
1271
|
-
const value1 =
|
|
1272
|
-
const value2 =
|
|
1357
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
1358
|
+
const value1 = arr1[i];
|
|
1359
|
+
const value2 = arr2[i];
|
|
1273
1360
|
if (value1 !== value2) {
|
|
1274
1361
|
return false;
|
|
1275
1362
|
}
|
|
1276
1363
|
}
|
|
1277
1364
|
return true;
|
|
1278
1365
|
}
|
|
1279
|
-
/**
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
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) {
|
|
1297
1415
|
try {
|
|
1298
|
-
const [block, value] =
|
|
1416
|
+
const [block, value] = runComponent(ctx, hydrationData);
|
|
1299
1417
|
if (block) {
|
|
1300
1418
|
ctx.inflightBlock = block
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1305
|
-
})
|
|
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)
|
|
1306
1422
|
.finally(() => advanceComponent(ctx));
|
|
1307
1423
|
// stepComponent will only return a block if the value is asynchronous
|
|
1308
1424
|
ctx.inflightValue = value;
|
|
@@ -1316,38 +1432,46 @@ function runComponent(ctx) {
|
|
|
1316
1432
|
throw err;
|
|
1317
1433
|
}
|
|
1318
1434
|
}
|
|
1319
|
-
else if (ctx.f & IsAsyncGen) {
|
|
1320
|
-
return ctx.inflightValue;
|
|
1321
|
-
}
|
|
1322
1435
|
else if (!ctx.enqueuedBlock) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
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(() => {
|
|
1326
1444
|
try {
|
|
1327
|
-
const [block, value] =
|
|
1328
|
-
resolve(value);
|
|
1445
|
+
const [block, value] = runComponent(ctx);
|
|
1329
1446
|
if (block) {
|
|
1330
|
-
|
|
1331
|
-
if (!(ctx.f & IsUpdating)) {
|
|
1332
|
-
return propagateError(ctx.parent, err);
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1447
|
+
resolveEnqueuedBlock(block.finally(() => advanceComponent(ctx)));
|
|
1335
1448
|
}
|
|
1449
|
+
return value;
|
|
1336
1450
|
}
|
|
1337
1451
|
catch (err) {
|
|
1338
1452
|
if (!(ctx.f & IsUpdating)) {
|
|
1339
1453
|
return propagateError(ctx.parent, err);
|
|
1340
1454
|
}
|
|
1455
|
+
throw err;
|
|
1341
1456
|
}
|
|
1342
|
-
})
|
|
1343
|
-
.finally(() => advanceComponent(ctx));
|
|
1344
|
-
ctx.enqueuedValue = new Promise((resolve1) => (resolve = resolve1));
|
|
1457
|
+
});
|
|
1345
1458
|
}
|
|
1346
1459
|
return ctx.enqueuedValue;
|
|
1347
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
|
+
}
|
|
1348
1471
|
/**
|
|
1349
1472
|
* This function is responsible for executing the component and handling all
|
|
1350
|
-
* 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.
|
|
1351
1475
|
*
|
|
1352
1476
|
* @returns {[block, value]} A tuple where
|
|
1353
1477
|
* block - A possible promise which represents the duration during which the
|
|
@@ -1362,25 +1486,23 @@ function runComponent(ctx) {
|
|
|
1362
1486
|
* - Sync generator components block while any children are executing, because
|
|
1363
1487
|
* they are expected to only resume when they’ve actually rendered.
|
|
1364
1488
|
*/
|
|
1365
|
-
function
|
|
1489
|
+
function runComponent(ctx, hydrationData) {
|
|
1366
1490
|
const ret = ctx.ret;
|
|
1367
|
-
if (ctx.f & IsDone) {
|
|
1368
|
-
return [undefined, getValue(ret)];
|
|
1369
|
-
}
|
|
1370
1491
|
const initial = !ctx.iterator;
|
|
1371
1492
|
if (initial) {
|
|
1372
|
-
ctx
|
|
1493
|
+
resumePropsIterator(ctx);
|
|
1494
|
+
ctx.f |= IsSyncExecuting;
|
|
1373
1495
|
clearEventListeners(ctx);
|
|
1374
1496
|
let result;
|
|
1375
1497
|
try {
|
|
1376
|
-
result = ret.el.tag.call(ctx.
|
|
1498
|
+
result = ret.el.tag.call(ctx.owner, ret.el.props);
|
|
1377
1499
|
}
|
|
1378
1500
|
catch (err) {
|
|
1379
1501
|
ctx.f |= IsErrored;
|
|
1380
1502
|
throw err;
|
|
1381
1503
|
}
|
|
1382
1504
|
finally {
|
|
1383
|
-
ctx.f &= ~
|
|
1505
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1384
1506
|
}
|
|
1385
1507
|
if (isIteratorLike(result)) {
|
|
1386
1508
|
ctx.iterator = result;
|
|
@@ -1388,124 +1510,241 @@ function stepComponent(ctx) {
|
|
|
1388
1510
|
else if (isPromiseLike(result)) {
|
|
1389
1511
|
// async function component
|
|
1390
1512
|
const result1 = result instanceof Promise ? result : Promise.resolve(result);
|
|
1391
|
-
const value = result1.then((result) => updateComponentChildren(ctx, result), (err) => {
|
|
1513
|
+
const value = result1.then((result) => updateComponentChildren(ctx, result, hydrationData), (err) => {
|
|
1392
1514
|
ctx.f |= IsErrored;
|
|
1393
1515
|
throw err;
|
|
1394
1516
|
});
|
|
1395
|
-
return [result1, value];
|
|
1517
|
+
return [result1.catch(NOOP), value];
|
|
1396
1518
|
}
|
|
1397
1519
|
else {
|
|
1398
1520
|
// sync function component
|
|
1399
|
-
return [
|
|
1521
|
+
return [
|
|
1522
|
+
undefined,
|
|
1523
|
+
updateComponentChildren(ctx, result, hydrationData),
|
|
1524
|
+
];
|
|
1400
1525
|
}
|
|
1401
1526
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
// The argument passed to the first call to next is ignored.
|
|
1405
|
-
oldValue = undefined;
|
|
1406
|
-
}
|
|
1407
|
-
else if (ctx.ret.inflight) {
|
|
1408
|
-
// The value passed back into the generator as the argument to the next
|
|
1409
|
-
// method is a promise if an async generator component has async children.
|
|
1410
|
-
// Sync generator components only resume when their children have fulfilled
|
|
1411
|
-
// so the element’s inflight child values will never be defined.
|
|
1412
|
-
oldValue = ctx.ret.inflight.then((value) => ctx.renderer.read(value), () => ctx.renderer.read(undefined));
|
|
1413
|
-
}
|
|
1414
|
-
else {
|
|
1415
|
-
oldValue = ctx.renderer.read(getValue(ret));
|
|
1527
|
+
else if (hydrationData !== undefined) {
|
|
1528
|
+
throw new Error("Hydration error");
|
|
1416
1529
|
}
|
|
1417
1530
|
let iteration;
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
+
}
|
|
1425
1549
|
}
|
|
1426
|
-
|
|
1427
|
-
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];
|
|
1428
1587
|
}
|
|
1429
|
-
if (
|
|
1430
|
-
//
|
|
1431
|
-
|
|
1432
|
-
|
|
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");
|
|
1433
1607
|
}
|
|
1608
|
+
const block = iteration.catch(NOOP);
|
|
1434
1609
|
const value = iteration.then((iteration) => {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
ctx.f &= ~IsIterating;
|
|
1439
|
-
if (iteration.done) {
|
|
1440
|
-
ctx.f |= IsDone;
|
|
1610
|
+
let value;
|
|
1611
|
+
if (!(ctx.f & IsInForOfLoop)) {
|
|
1612
|
+
runAsyncGenComponent(ctx, Promise.resolve(iteration), hydrationData);
|
|
1441
1613
|
}
|
|
1442
1614
|
try {
|
|
1443
|
-
|
|
1615
|
+
value = updateComponentChildren(ctx,
|
|
1616
|
+
// Children can be void so we eliminate that here
|
|
1617
|
+
iteration.value, hydrationData);
|
|
1444
1618
|
if (isPromiseLike(value)) {
|
|
1445
|
-
|
|
1619
|
+
value = value.catch((err) => handleChildError(ctx, err));
|
|
1446
1620
|
}
|
|
1447
|
-
return value;
|
|
1448
1621
|
}
|
|
1449
1622
|
catch (err) {
|
|
1450
|
-
|
|
1623
|
+
value = handleChildError(ctx, err);
|
|
1451
1624
|
}
|
|
1625
|
+
return value;
|
|
1452
1626
|
}, (err) => {
|
|
1453
|
-
ctx.f |=
|
|
1627
|
+
ctx.f |= IsErrored;
|
|
1454
1628
|
throw err;
|
|
1455
1629
|
});
|
|
1456
|
-
return [
|
|
1457
|
-
}
|
|
1458
|
-
// sync generator component
|
|
1459
|
-
if (initial) {
|
|
1460
|
-
ctx.f |= IsSyncGen;
|
|
1630
|
+
return [block, value];
|
|
1461
1631
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1632
|
+
else {
|
|
1633
|
+
runAsyncGenComponent(ctx, iteration, hydrationData);
|
|
1634
|
+
// async generator component
|
|
1635
|
+
return [ctx.inflightBlock, ctx.inflightValue];
|
|
1465
1636
|
}
|
|
1466
|
-
|
|
1637
|
+
}
|
|
1638
|
+
async function runAsyncGenComponent(ctx, iterationP, hydrationData) {
|
|
1639
|
+
let done = false;
|
|
1467
1640
|
try {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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
|
+
}
|
|
1471
1724
|
}
|
|
1472
1725
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
}
|
|
1479
|
-
return [undefined, value];
|
|
1480
|
-
}
|
|
1481
|
-
/**
|
|
1482
|
-
* Called when the inflight block promise settles.
|
|
1483
|
-
*/
|
|
1484
|
-
function advanceComponent(ctx) {
|
|
1485
|
-
ctx.inflightBlock = ctx.enqueuedBlock;
|
|
1486
|
-
ctx.inflightValue = ctx.enqueuedValue;
|
|
1487
|
-
ctx.enqueuedBlock = undefined;
|
|
1488
|
-
ctx.enqueuedValue = undefined;
|
|
1489
|
-
if (ctx.f & IsAsyncGen && !(ctx.f & IsDone) && !(ctx.f & IsUnmounted)) {
|
|
1490
|
-
runComponent(ctx);
|
|
1726
|
+
finally {
|
|
1727
|
+
if (done) {
|
|
1728
|
+
ctx.f &= ~IsAsyncGen;
|
|
1729
|
+
ctx.iterator = undefined;
|
|
1730
|
+
}
|
|
1491
1731
|
}
|
|
1492
1732
|
}
|
|
1493
1733
|
/**
|
|
1494
|
-
* Called to
|
|
1495
|
-
* generator components.
|
|
1734
|
+
* Called to resume the props async iterator for async generator components.
|
|
1496
1735
|
*/
|
|
1497
|
-
function
|
|
1498
|
-
if (ctx.
|
|
1499
|
-
ctx.
|
|
1500
|
-
ctx.
|
|
1736
|
+
function resumePropsIterator(ctx) {
|
|
1737
|
+
if (ctx.onProps) {
|
|
1738
|
+
ctx.onProps(ctx.ret.el.props);
|
|
1739
|
+
ctx.onProps = undefined;
|
|
1740
|
+
ctx.f &= ~PropsAvailable;
|
|
1501
1741
|
}
|
|
1502
1742
|
else {
|
|
1503
|
-
ctx.f |=
|
|
1743
|
+
ctx.f |= PropsAvailable;
|
|
1504
1744
|
}
|
|
1505
1745
|
}
|
|
1506
1746
|
// TODO: async unmounting
|
|
1507
1747
|
function unmountComponent(ctx) {
|
|
1508
|
-
ctx.f |= IsUnmounted;
|
|
1509
1748
|
clearEventListeners(ctx);
|
|
1510
1749
|
const callbacks = cleanupMap.get(ctx);
|
|
1511
1750
|
if (callbacks) {
|
|
@@ -1515,23 +1754,71 @@ function unmountComponent(ctx) {
|
|
|
1515
1754
|
callback(value);
|
|
1516
1755
|
}
|
|
1517
1756
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
ctx.f
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
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);
|
|
1527
1782
|
}
|
|
1528
1783
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
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);
|
|
1531
1803
|
}
|
|
1532
1804
|
}
|
|
1533
1805
|
}
|
|
1534
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
|
+
}
|
|
1535
1822
|
/*** EVENT TARGET UTILITIES ***/
|
|
1536
1823
|
// EVENT PHASE CONSTANTS
|
|
1537
1824
|
// https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
|
|
@@ -1600,39 +1887,39 @@ function clearEventListeners(ctx) {
|
|
|
1600
1887
|
}
|
|
1601
1888
|
}
|
|
1602
1889
|
/*** ERROR HANDLING UTILITIES ***/
|
|
1603
|
-
// TODO: generator components which throw errors should be recoverable
|
|
1604
1890
|
function handleChildError(ctx, err) {
|
|
1605
|
-
if (ctx.
|
|
1606
|
-
!ctx.iterator ||
|
|
1607
|
-
typeof ctx.iterator.throw !== "function") {
|
|
1891
|
+
if (!ctx.iterator || typeof ctx.iterator.throw !== "function") {
|
|
1608
1892
|
throw err;
|
|
1609
1893
|
}
|
|
1610
|
-
|
|
1894
|
+
resumePropsIterator(ctx);
|
|
1611
1895
|
let iteration;
|
|
1612
1896
|
try {
|
|
1613
|
-
ctx.f |=
|
|
1897
|
+
ctx.f |= IsSyncExecuting;
|
|
1614
1898
|
iteration = ctx.iterator.throw(err);
|
|
1615
1899
|
}
|
|
1616
1900
|
catch (err) {
|
|
1617
|
-
ctx.f |=
|
|
1901
|
+
ctx.f |= IsErrored;
|
|
1618
1902
|
throw err;
|
|
1619
1903
|
}
|
|
1620
1904
|
finally {
|
|
1621
|
-
ctx.f &= ~
|
|
1905
|
+
ctx.f &= ~IsSyncExecuting;
|
|
1622
1906
|
}
|
|
1623
1907
|
if (isPromiseLike(iteration)) {
|
|
1624
1908
|
return iteration.then((iteration) => {
|
|
1625
1909
|
if (iteration.done) {
|
|
1626
|
-
ctx.f
|
|
1910
|
+
ctx.f &= ~IsAsyncGen;
|
|
1911
|
+
ctx.iterator = undefined;
|
|
1627
1912
|
}
|
|
1628
1913
|
return updateComponentChildren(ctx, iteration.value);
|
|
1629
1914
|
}, (err) => {
|
|
1630
|
-
ctx.f |=
|
|
1915
|
+
ctx.f |= IsErrored;
|
|
1631
1916
|
throw err;
|
|
1632
1917
|
});
|
|
1633
1918
|
}
|
|
1634
1919
|
if (iteration.done) {
|
|
1635
|
-
ctx.f
|
|
1920
|
+
ctx.f &= ~IsSyncGen;
|
|
1921
|
+
ctx.f &= ~IsAsyncGen;
|
|
1922
|
+
ctx.iterator = undefined;
|
|
1636
1923
|
}
|
|
1637
1924
|
return updateComponentChildren(ctx, iteration.value);
|
|
1638
1925
|
}
|
|
@@ -1665,6 +1952,6 @@ exports.Raw = Raw;
|
|
|
1665
1952
|
exports.Renderer = Renderer;
|
|
1666
1953
|
exports.cloneElement = cloneElement;
|
|
1667
1954
|
exports.createElement = createElement;
|
|
1668
|
-
exports
|
|
1955
|
+
exports.default = crank;
|
|
1669
1956
|
exports.isElement = isElement;
|
|
1670
1957
|
//# sourceMappingURL=crank.cjs.map
|