@b9g/crank 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_utils.cjs +21 -0
- package/_utils.cjs.map +1 -1
- package/_utils.d.ts +3 -0
- package/_utils.js +19 -1
- package/_utils.js.map +1 -1
- package/crank.cjs +31 -2
- package/crank.cjs.map +1 -1
- package/crank.js +32 -3
- package/crank.js.map +1 -1
- package/package.json +1 -1
- package/umd.js +49 -2
- package/umd.js.map +1 -1
package/_utils.cjs
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const supportsUserTiming = typeof performance !== "undefined" && typeof performance.mark === "function";
|
|
4
|
+
function markStart(label) {
|
|
5
|
+
if (supportsUserTiming) {
|
|
6
|
+
performance.mark("⚙ " + label);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function measureMark(label) {
|
|
10
|
+
if (supportsUserTiming) {
|
|
11
|
+
const name = "⚙ " + label;
|
|
12
|
+
try {
|
|
13
|
+
performance.measure(name, name);
|
|
14
|
+
}
|
|
15
|
+
catch (_) {
|
|
16
|
+
// Mark may not exist
|
|
17
|
+
}
|
|
18
|
+
performance.clearMarks(name);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
3
21
|
function wrap(value) {
|
|
4
22
|
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
5
23
|
}
|
|
@@ -100,7 +118,10 @@ function safeRace(contenders) {
|
|
|
100
118
|
exports.arrayify = arrayify;
|
|
101
119
|
exports.isIteratorLike = isIteratorLike;
|
|
102
120
|
exports.isPromiseLike = isPromiseLike;
|
|
121
|
+
exports.markStart = markStart;
|
|
122
|
+
exports.measureMark = measureMark;
|
|
103
123
|
exports.safeRace = safeRace;
|
|
124
|
+
exports.supportsUserTiming = supportsUserTiming;
|
|
104
125
|
exports.unwrap = unwrap;
|
|
105
126
|
exports.wrap = wrap;
|
|
106
127
|
//# sourceMappingURL=_utils.cjs.map
|
package/_utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_utils.cjs","sources":["../src/_utils.ts"],"sourcesContent":["export function wrap<T>(value: Array<T> | T | undefined): Array<T> {\n\treturn value === undefined ? [] : Array.isArray(value) ? value : [value];\n}\n\nexport function unwrap<T>(arr: Array<T>): Array<T> | T | undefined {\n\treturn arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;\n}\n\nexport type NonStringIterable<T> = Iterable<T> & object;\n\n/**\n * Ensures a value is an array.\n *\n * This function does the same thing as wrap() above except it handles nulls\n * and iterables, so it is appropriate for wrapping user-provided element\n * children.\n */\nexport function arrayify<T>(\n\tvalue: NonStringIterable<T> | T | null | undefined,\n): Array<T> {\n\treturn value == null\n\t\t? []\n\t\t: Array.isArray(value)\n\t\t\t? value\n\t\t\t: typeof value === \"string\" ||\n\t\t\t\t typeof (value as any)[Symbol.iterator] !== \"function\"\n\t\t\t\t? [value as T]\n\t\t\t\t: [...(value as NonStringIterable<T>)];\n}\n\nexport function isIteratorLike(\n\tvalue: any,\n): value is Iterator<unknown> | AsyncIterator<unknown> {\n\treturn value != null && typeof value.next === \"function\";\n}\n\nexport function isPromiseLike(value: any): value is PromiseLike<unknown> {\n\treturn value != null && typeof value.then === \"function\";\n}\n\ntype Deferred<T = unknown> = {\n\tresolve: (value: T | PromiseLike<T>) => void;\n\treject: (reason?: unknown) => void;\n};\n\ntype RaceRecord = {\n\tdeferreds: Set<Deferred>;\n\tsettled: boolean;\n};\n\nfunction createRaceRecord(contender: PromiseLike<unknown>): RaceRecord {\n\tconst deferreds = new Set<Deferred>();\n\tconst record = {deferreds, settled: false};\n\n\t// This call to `then` happens once for the lifetime of the value.\n\tPromise.resolve(contender).then(\n\t\t(value) => {\n\t\t\tfor (const {resolve} of deferreds) {\n\t\t\t\tresolve(value);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t\t(err) => {\n\t\t\tfor (const {reject} of deferreds) {\n\t\t\t\treject(err);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t);\n\treturn record;\n}\n\n// Promise.race is memory unsafe. This is alternative which is. See:\n// https://github.com/nodejs/node/issues/17469#issuecomment-685235106\n// Keys are the values passed to race.\n// Values are a record of data containing a set of deferreds and whether the\n// value has settled.\nconst wm = new WeakMap<object, RaceRecord>();\nexport function safeRace<T>(\n\tcontenders: Iterable<T | PromiseLike<T>>,\n): Promise<Awaited<T>> {\n\tlet deferred: Deferred;\n\tconst result = new Promise((resolve, reject) => {\n\t\tdeferred = {resolve, reject};\n\t\tfor (const contender of contenders) {\n\t\t\tif (!isPromiseLike(contender)) {\n\t\t\t\t// If the contender is a not a then-able, attempting to use it as a key\n\t\t\t\t// in the weakmap would throw an error. Luckily, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on regular values multiple\n\t\t\t\t// times because the promise fulfills immediately.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet record = wm.get(contender);\n\t\t\tif (record === undefined) {\n\t\t\t\trecord = createRaceRecord(contender);\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t\twm.set(contender, record);\n\t\t\t} else if (record.settled) {\n\t\t\t\t// If the value has settled, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on it.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t} else {\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t}\n\t\t}\n\t});\n\n\t// The finally callback executes when any value settles, preventing any of\n\t// the unresolved values from retaining a reference to the resolved value.\n\treturn result.finally(() => {\n\t\tfor (const contender of contenders) {\n\t\t\tif (isPromiseLike(contender)) {\n\t\t\t\tconst record = wm.get(contender);\n\t\t\t\tif (record) {\n\t\t\t\t\trecord.deferreds.delete(deferred);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}) as Promise<Awaited<T>>;\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"_utils.cjs","sources":["../src/_utils.ts"],"sourcesContent":["export const supportsUserTiming =\n\ttypeof performance !== \"undefined\" && typeof performance.mark === \"function\";\n\nexport function markStart(label: string): void {\n\tif (supportsUserTiming) {\n\t\tperformance.mark(\"⚙ \" + label);\n\t}\n}\n\nexport function measureMark(label: string): void {\n\tif (supportsUserTiming) {\n\t\tconst name = \"⚙ \" + label;\n\t\ttry {\n\t\t\tperformance.measure(name, name);\n\t\t} catch (_) {\n\t\t\t// Mark may not exist\n\t\t}\n\t\tperformance.clearMarks(name);\n\t}\n}\n\nexport function wrap<T>(value: Array<T> | T | undefined): Array<T> {\n\treturn value === undefined ? [] : Array.isArray(value) ? value : [value];\n}\n\nexport function unwrap<T>(arr: Array<T>): Array<T> | T | undefined {\n\treturn arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;\n}\n\nexport type NonStringIterable<T> = Iterable<T> & object;\n\n/**\n * Ensures a value is an array.\n *\n * This function does the same thing as wrap() above except it handles nulls\n * and iterables, so it is appropriate for wrapping user-provided element\n * children.\n */\nexport function arrayify<T>(\n\tvalue: NonStringIterable<T> | T | null | undefined,\n): Array<T> {\n\treturn value == null\n\t\t? []\n\t\t: Array.isArray(value)\n\t\t\t? value\n\t\t\t: typeof value === \"string\" ||\n\t\t\t\t typeof (value as any)[Symbol.iterator] !== \"function\"\n\t\t\t\t? [value as T]\n\t\t\t\t: [...(value as NonStringIterable<T>)];\n}\n\nexport function isIteratorLike(\n\tvalue: any,\n): value is Iterator<unknown> | AsyncIterator<unknown> {\n\treturn value != null && typeof value.next === \"function\";\n}\n\nexport function isPromiseLike(value: any): value is PromiseLike<unknown> {\n\treturn value != null && typeof value.then === \"function\";\n}\n\ntype Deferred<T = unknown> = {\n\tresolve: (value: T | PromiseLike<T>) => void;\n\treject: (reason?: unknown) => void;\n};\n\ntype RaceRecord = {\n\tdeferreds: Set<Deferred>;\n\tsettled: boolean;\n};\n\nfunction createRaceRecord(contender: PromiseLike<unknown>): RaceRecord {\n\tconst deferreds = new Set<Deferred>();\n\tconst record = {deferreds, settled: false};\n\n\t// This call to `then` happens once for the lifetime of the value.\n\tPromise.resolve(contender).then(\n\t\t(value) => {\n\t\t\tfor (const {resolve} of deferreds) {\n\t\t\t\tresolve(value);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t\t(err) => {\n\t\t\tfor (const {reject} of deferreds) {\n\t\t\t\treject(err);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t);\n\treturn record;\n}\n\n// Promise.race is memory unsafe. This is alternative which is. See:\n// https://github.com/nodejs/node/issues/17469#issuecomment-685235106\n// Keys are the values passed to race.\n// Values are a record of data containing a set of deferreds and whether the\n// value has settled.\nconst wm = new WeakMap<object, RaceRecord>();\nexport function safeRace<T>(\n\tcontenders: Iterable<T | PromiseLike<T>>,\n): Promise<Awaited<T>> {\n\tlet deferred: Deferred;\n\tconst result = new Promise((resolve, reject) => {\n\t\tdeferred = {resolve, reject};\n\t\tfor (const contender of contenders) {\n\t\t\tif (!isPromiseLike(contender)) {\n\t\t\t\t// If the contender is a not a then-able, attempting to use it as a key\n\t\t\t\t// in the weakmap would throw an error. Luckily, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on regular values multiple\n\t\t\t\t// times because the promise fulfills immediately.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet record = wm.get(contender);\n\t\t\tif (record === undefined) {\n\t\t\t\trecord = createRaceRecord(contender);\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t\twm.set(contender, record);\n\t\t\t} else if (record.settled) {\n\t\t\t\t// If the value has settled, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on it.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t} else {\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t}\n\t\t}\n\t});\n\n\t// The finally callback executes when any value settles, preventing any of\n\t// the unresolved values from retaining a reference to the resolved value.\n\treturn result.finally(() => {\n\t\tfor (const contender of contenders) {\n\t\t\tif (isPromiseLike(contender)) {\n\t\t\t\tconst record = wm.get(contender);\n\t\t\t\tif (record) {\n\t\t\t\t\trecord.deferreds.delete(deferred);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}) as Promise<Awaited<T>>;\n}\n"],"names":[],"mappings":"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"}
|
package/_utils.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export declare const supportsUserTiming: boolean;
|
|
2
|
+
export declare function markStart(label: string): void;
|
|
3
|
+
export declare function measureMark(label: string): void;
|
|
1
4
|
export declare function wrap<T>(value: Array<T> | T | undefined): Array<T>;
|
|
2
5
|
export declare function unwrap<T>(arr: Array<T>): Array<T> | T | undefined;
|
|
3
6
|
export type NonStringIterable<T> = Iterable<T> & object;
|
package/_utils.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
const supportsUserTiming = typeof performance !== "undefined" && typeof performance.mark === "function";
|
|
2
|
+
function markStart(label) {
|
|
3
|
+
if (supportsUserTiming) {
|
|
4
|
+
performance.mark("⚙ " + label);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
function measureMark(label) {
|
|
8
|
+
if (supportsUserTiming) {
|
|
9
|
+
const name = "⚙ " + label;
|
|
10
|
+
try {
|
|
11
|
+
performance.measure(name, name);
|
|
12
|
+
}
|
|
13
|
+
catch (_) {
|
|
14
|
+
// Mark may not exist
|
|
15
|
+
}
|
|
16
|
+
performance.clearMarks(name);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
1
19
|
function wrap(value) {
|
|
2
20
|
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
3
21
|
}
|
|
@@ -95,5 +113,5 @@ function safeRace(contenders) {
|
|
|
95
113
|
});
|
|
96
114
|
}
|
|
97
115
|
|
|
98
|
-
export { arrayify, isIteratorLike, isPromiseLike, safeRace, unwrap, wrap };
|
|
116
|
+
export { arrayify, isIteratorLike, isPromiseLike, markStart, measureMark, safeRace, supportsUserTiming, unwrap, wrap };
|
|
99
117
|
//# sourceMappingURL=_utils.js.map
|
package/_utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_utils.js","sources":["../src/_utils.ts"],"sourcesContent":["export function wrap<T>(value: Array<T> | T | undefined): Array<T> {\n\treturn value === undefined ? [] : Array.isArray(value) ? value : [value];\n}\n\nexport function unwrap<T>(arr: Array<T>): Array<T> | T | undefined {\n\treturn arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;\n}\n\nexport type NonStringIterable<T> = Iterable<T> & object;\n\n/**\n * Ensures a value is an array.\n *\n * This function does the same thing as wrap() above except it handles nulls\n * and iterables, so it is appropriate for wrapping user-provided element\n * children.\n */\nexport function arrayify<T>(\n\tvalue: NonStringIterable<T> | T | null | undefined,\n): Array<T> {\n\treturn value == null\n\t\t? []\n\t\t: Array.isArray(value)\n\t\t\t? value\n\t\t\t: typeof value === \"string\" ||\n\t\t\t\t typeof (value as any)[Symbol.iterator] !== \"function\"\n\t\t\t\t? [value as T]\n\t\t\t\t: [...(value as NonStringIterable<T>)];\n}\n\nexport function isIteratorLike(\n\tvalue: any,\n): value is Iterator<unknown> | AsyncIterator<unknown> {\n\treturn value != null && typeof value.next === \"function\";\n}\n\nexport function isPromiseLike(value: any): value is PromiseLike<unknown> {\n\treturn value != null && typeof value.then === \"function\";\n}\n\ntype Deferred<T = unknown> = {\n\tresolve: (value: T | PromiseLike<T>) => void;\n\treject: (reason?: unknown) => void;\n};\n\ntype RaceRecord = {\n\tdeferreds: Set<Deferred>;\n\tsettled: boolean;\n};\n\nfunction createRaceRecord(contender: PromiseLike<unknown>): RaceRecord {\n\tconst deferreds = new Set<Deferred>();\n\tconst record = {deferreds, settled: false};\n\n\t// This call to `then` happens once for the lifetime of the value.\n\tPromise.resolve(contender).then(\n\t\t(value) => {\n\t\t\tfor (const {resolve} of deferreds) {\n\t\t\t\tresolve(value);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t\t(err) => {\n\t\t\tfor (const {reject} of deferreds) {\n\t\t\t\treject(err);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t);\n\treturn record;\n}\n\n// Promise.race is memory unsafe. This is alternative which is. See:\n// https://github.com/nodejs/node/issues/17469#issuecomment-685235106\n// Keys are the values passed to race.\n// Values are a record of data containing a set of deferreds and whether the\n// value has settled.\nconst wm = new WeakMap<object, RaceRecord>();\nexport function safeRace<T>(\n\tcontenders: Iterable<T | PromiseLike<T>>,\n): Promise<Awaited<T>> {\n\tlet deferred: Deferred;\n\tconst result = new Promise((resolve, reject) => {\n\t\tdeferred = {resolve, reject};\n\t\tfor (const contender of contenders) {\n\t\t\tif (!isPromiseLike(contender)) {\n\t\t\t\t// If the contender is a not a then-able, attempting to use it as a key\n\t\t\t\t// in the weakmap would throw an error. Luckily, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on regular values multiple\n\t\t\t\t// times because the promise fulfills immediately.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet record = wm.get(contender);\n\t\t\tif (record === undefined) {\n\t\t\t\trecord = createRaceRecord(contender);\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t\twm.set(contender, record);\n\t\t\t} else if (record.settled) {\n\t\t\t\t// If the value has settled, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on it.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t} else {\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t}\n\t\t}\n\t});\n\n\t// The finally callback executes when any value settles, preventing any of\n\t// the unresolved values from retaining a reference to the resolved value.\n\treturn result.finally(() => {\n\t\tfor (const contender of contenders) {\n\t\t\tif (isPromiseLike(contender)) {\n\t\t\t\tconst record = wm.get(contender);\n\t\t\t\tif (record) {\n\t\t\t\t\trecord.deferreds.delete(deferred);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}) as Promise<Awaited<T>>;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_utils.js","sources":["../src/_utils.ts"],"sourcesContent":["export const supportsUserTiming =\n\ttypeof performance !== \"undefined\" && typeof performance.mark === \"function\";\n\nexport function markStart(label: string): void {\n\tif (supportsUserTiming) {\n\t\tperformance.mark(\"⚙ \" + label);\n\t}\n}\n\nexport function measureMark(label: string): void {\n\tif (supportsUserTiming) {\n\t\tconst name = \"⚙ \" + label;\n\t\ttry {\n\t\t\tperformance.measure(name, name);\n\t\t} catch (_) {\n\t\t\t// Mark may not exist\n\t\t}\n\t\tperformance.clearMarks(name);\n\t}\n}\n\nexport function wrap<T>(value: Array<T> | T | undefined): Array<T> {\n\treturn value === undefined ? [] : Array.isArray(value) ? value : [value];\n}\n\nexport function unwrap<T>(arr: Array<T>): Array<T> | T | undefined {\n\treturn arr.length === 0 ? undefined : arr.length === 1 ? arr[0] : arr;\n}\n\nexport type NonStringIterable<T> = Iterable<T> & object;\n\n/**\n * Ensures a value is an array.\n *\n * This function does the same thing as wrap() above except it handles nulls\n * and iterables, so it is appropriate for wrapping user-provided element\n * children.\n */\nexport function arrayify<T>(\n\tvalue: NonStringIterable<T> | T | null | undefined,\n): Array<T> {\n\treturn value == null\n\t\t? []\n\t\t: Array.isArray(value)\n\t\t\t? value\n\t\t\t: typeof value === \"string\" ||\n\t\t\t\t typeof (value as any)[Symbol.iterator] !== \"function\"\n\t\t\t\t? [value as T]\n\t\t\t\t: [...(value as NonStringIterable<T>)];\n}\n\nexport function isIteratorLike(\n\tvalue: any,\n): value is Iterator<unknown> | AsyncIterator<unknown> {\n\treturn value != null && typeof value.next === \"function\";\n}\n\nexport function isPromiseLike(value: any): value is PromiseLike<unknown> {\n\treturn value != null && typeof value.then === \"function\";\n}\n\ntype Deferred<T = unknown> = {\n\tresolve: (value: T | PromiseLike<T>) => void;\n\treject: (reason?: unknown) => void;\n};\n\ntype RaceRecord = {\n\tdeferreds: Set<Deferred>;\n\tsettled: boolean;\n};\n\nfunction createRaceRecord(contender: PromiseLike<unknown>): RaceRecord {\n\tconst deferreds = new Set<Deferred>();\n\tconst record = {deferreds, settled: false};\n\n\t// This call to `then` happens once for the lifetime of the value.\n\tPromise.resolve(contender).then(\n\t\t(value) => {\n\t\t\tfor (const {resolve} of deferreds) {\n\t\t\t\tresolve(value);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t\t(err) => {\n\t\t\tfor (const {reject} of deferreds) {\n\t\t\t\treject(err);\n\t\t\t}\n\n\t\t\tdeferreds.clear();\n\t\t\trecord.settled = true;\n\t\t},\n\t);\n\treturn record;\n}\n\n// Promise.race is memory unsafe. This is alternative which is. See:\n// https://github.com/nodejs/node/issues/17469#issuecomment-685235106\n// Keys are the values passed to race.\n// Values are a record of data containing a set of deferreds and whether the\n// value has settled.\nconst wm = new WeakMap<object, RaceRecord>();\nexport function safeRace<T>(\n\tcontenders: Iterable<T | PromiseLike<T>>,\n): Promise<Awaited<T>> {\n\tlet deferred: Deferred;\n\tconst result = new Promise((resolve, reject) => {\n\t\tdeferred = {resolve, reject};\n\t\tfor (const contender of contenders) {\n\t\t\tif (!isPromiseLike(contender)) {\n\t\t\t\t// If the contender is a not a then-able, attempting to use it as a key\n\t\t\t\t// in the weakmap would throw an error. Luckily, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on regular values multiple\n\t\t\t\t// times because the promise fulfills immediately.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet record = wm.get(contender);\n\t\t\tif (record === undefined) {\n\t\t\t\trecord = createRaceRecord(contender);\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t\twm.set(contender, record);\n\t\t\t} else if (record.settled) {\n\t\t\t\t// If the value has settled, it is safe to call\n\t\t\t\t// `Promise.resolve(contender).then` on it.\n\t\t\t\tPromise.resolve(contender).then(resolve, reject);\n\t\t\t} else {\n\t\t\t\trecord.deferreds.add(deferred);\n\t\t\t}\n\t\t}\n\t});\n\n\t// The finally callback executes when any value settles, preventing any of\n\t// the unresolved values from retaining a reference to the resolved value.\n\treturn result.finally(() => {\n\t\tfor (const contender of contenders) {\n\t\t\tif (isPromiseLike(contender)) {\n\t\t\t\tconst record = wm.get(contender);\n\t\t\t\tif (record) {\n\t\t\t\t\trecord.deferreds.delete(deferred);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}) as Promise<Awaited<T>>;\n}\n"],"names":[],"mappings":"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"}
|
package/crank.cjs
CHANGED
|
@@ -428,11 +428,16 @@ function getRootRetainer(renderer, bridge, { children, root, hydrate, }) {
|
|
|
428
428
|
return ret;
|
|
429
429
|
}
|
|
430
430
|
function renderRoot(adapter, root, ret, children) {
|
|
431
|
+
const commitLabel = "commit (" + getTagName(ret.el.tag) + ")";
|
|
432
|
+
_utils.markStart("diff");
|
|
431
433
|
const diff = diffChildren(adapter, root, ret, ret.ctx, ret.scope, ret, children);
|
|
432
434
|
const schedulePromises = [];
|
|
433
435
|
if (_utils.isPromiseLike(diff)) {
|
|
434
436
|
return diff.then(() => {
|
|
437
|
+
_utils.measureMark("diff");
|
|
438
|
+
_utils.markStart(commitLabel);
|
|
435
439
|
commit(adapter, ret, ret, ret.ctx, ret.scope, root, 0, schedulePromises, undefined);
|
|
440
|
+
_utils.measureMark(commitLabel);
|
|
436
441
|
if (schedulePromises.length > 0) {
|
|
437
442
|
return Promise.all(schedulePromises).then(() => {
|
|
438
443
|
if (typeof root !== "object" || root === null) {
|
|
@@ -447,7 +452,10 @@ function renderRoot(adapter, root, ret, children) {
|
|
|
447
452
|
return adapter.read(_utils.unwrap(getChildValues(ret)));
|
|
448
453
|
});
|
|
449
454
|
}
|
|
455
|
+
_utils.measureMark("diff");
|
|
456
|
+
_utils.markStart(commitLabel);
|
|
450
457
|
commit(adapter, ret, ret, ret.ctx, ret.scope, root, 0, schedulePromises, undefined);
|
|
458
|
+
_utils.measureMark(commitLabel);
|
|
451
459
|
if (schedulePromises.length > 0) {
|
|
452
460
|
return Promise.all(schedulePromises).then(() => {
|
|
453
461
|
if (typeof root !== "object" || root === null) {
|
|
@@ -1549,6 +1557,7 @@ class Context extends eventTarget.CustomEventTarget {
|
|
|
1549
1557
|
if (getFlag(ctx.ret, IsScheduling)) {
|
|
1550
1558
|
setFlag(ctx.ret, IsSchedulingRefresh);
|
|
1551
1559
|
}
|
|
1560
|
+
const commitLabel = "commit (" + getTagName(ctx.ret.el.tag) + ")";
|
|
1552
1561
|
let diff;
|
|
1553
1562
|
const schedulePromises = [];
|
|
1554
1563
|
try {
|
|
@@ -1556,7 +1565,12 @@ class Context extends eventTarget.CustomEventTarget {
|
|
|
1556
1565
|
diff = enqueueComponent(ctx);
|
|
1557
1566
|
if (_utils.isPromiseLike(diff)) {
|
|
1558
1567
|
return diff
|
|
1559
|
-
.then(() =>
|
|
1568
|
+
.then(() => {
|
|
1569
|
+
_utils.markStart(commitLabel);
|
|
1570
|
+
const value = commitComponent(ctx, schedulePromises);
|
|
1571
|
+
_utils.measureMark(commitLabel);
|
|
1572
|
+
return ctx.adapter.read(value);
|
|
1573
|
+
})
|
|
1560
1574
|
.then((result) => {
|
|
1561
1575
|
if (schedulePromises.length) {
|
|
1562
1576
|
return Promise.all(schedulePromises).then(() => {
|
|
@@ -1586,7 +1600,9 @@ class Context extends eventTarget.CustomEventTarget {
|
|
|
1586
1600
|
})
|
|
1587
1601
|
.finally(() => setFlag(ctx.ret, IsRefreshing, false));
|
|
1588
1602
|
}
|
|
1603
|
+
_utils.markStart(commitLabel);
|
|
1589
1604
|
const result = ctx.adapter.read(commitComponent(ctx, schedulePromises));
|
|
1605
|
+
_utils.measureMark(commitLabel);
|
|
1590
1606
|
if (schedulePromises.length) {
|
|
1591
1607
|
return Promise.all(schedulePromises).then(() => {
|
|
1592
1608
|
return ctx.adapter.read(getValue(ctx.ret));
|
|
@@ -1816,11 +1832,13 @@ function runComponent(ctx) {
|
|
|
1816
1832
|
}
|
|
1817
1833
|
const ret = ctx.ret;
|
|
1818
1834
|
const initial = !ctx.iterator;
|
|
1835
|
+
const tagName = getTagName(ret.el.tag);
|
|
1819
1836
|
if (initial) {
|
|
1820
1837
|
setFlag(ctx.ret, IsExecuting);
|
|
1821
1838
|
eventTarget.clearEventListeners(ctx.ctx);
|
|
1822
1839
|
let returned;
|
|
1823
1840
|
try {
|
|
1841
|
+
_utils.markStart(tagName);
|
|
1824
1842
|
returned = ret.el.tag.call(ctx.ctx, ret.el.props, ctx.ctx);
|
|
1825
1843
|
}
|
|
1826
1844
|
catch (err) {
|
|
@@ -1835,6 +1853,7 @@ function runComponent(ctx) {
|
|
|
1835
1853
|
}
|
|
1836
1854
|
else if (!_utils.isPromiseLike(returned)) {
|
|
1837
1855
|
// sync function component
|
|
1856
|
+
_utils.measureMark(tagName);
|
|
1838
1857
|
return [
|
|
1839
1858
|
undefined,
|
|
1840
1859
|
diffComponentChildren(ctx, returned, false),
|
|
@@ -1843,6 +1862,7 @@ function runComponent(ctx) {
|
|
|
1843
1862
|
else {
|
|
1844
1863
|
// async function component
|
|
1845
1864
|
const returned1 = returned instanceof Promise ? returned : Promise.resolve(returned);
|
|
1865
|
+
returned1.then(() => _utils.measureMark(tagName), () => _utils.measureMark(tagName));
|
|
1846
1866
|
return [
|
|
1847
1867
|
returned1.catch(NOOP),
|
|
1848
1868
|
returned1.then((returned) => diffComponentChildren(ctx, returned, false), (err) => {
|
|
@@ -1856,6 +1876,7 @@ function runComponent(ctx) {
|
|
|
1856
1876
|
if (initial) {
|
|
1857
1877
|
try {
|
|
1858
1878
|
setFlag(ctx.ret, IsExecuting);
|
|
1879
|
+
_utils.markStart(tagName);
|
|
1859
1880
|
iteration = ctx.iterator.next();
|
|
1860
1881
|
}
|
|
1861
1882
|
catch (err) {
|
|
@@ -1878,7 +1899,9 @@ function runComponent(ctx) {
|
|
|
1878
1899
|
try {
|
|
1879
1900
|
setFlag(ctx.ret, IsExecuting);
|
|
1880
1901
|
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
1902
|
+
_utils.markStart(tagName);
|
|
1881
1903
|
iteration = ctx.iterator.next(oldResult);
|
|
1904
|
+
_utils.measureMark(tagName);
|
|
1882
1905
|
}
|
|
1883
1906
|
catch (err) {
|
|
1884
1907
|
setFlag(ctx.ret, IsErrored);
|
|
@@ -1888,6 +1911,9 @@ function runComponent(ctx) {
|
|
|
1888
1911
|
setFlag(ctx.ret, IsExecuting, false);
|
|
1889
1912
|
}
|
|
1890
1913
|
}
|
|
1914
|
+
else {
|
|
1915
|
+
_utils.measureMark(tagName);
|
|
1916
|
+
}
|
|
1891
1917
|
if (_utils.isPromiseLike(iteration)) {
|
|
1892
1918
|
throw new Error("Mixed generator component");
|
|
1893
1919
|
}
|
|
@@ -1910,6 +1936,7 @@ function runComponent(ctx) {
|
|
|
1910
1936
|
else {
|
|
1911
1937
|
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1912
1938
|
// initializes the async generator loop
|
|
1939
|
+
_utils.measureMark(tagName);
|
|
1913
1940
|
pullComponent(ctx, iteration);
|
|
1914
1941
|
const block = resumePropsAsyncIterator(ctx);
|
|
1915
1942
|
return [block, ctx.pull && ctx.pull.diff];
|
|
@@ -1922,6 +1949,7 @@ function runComponent(ctx) {
|
|
|
1922
1949
|
try {
|
|
1923
1950
|
setFlag(ctx.ret, IsExecuting);
|
|
1924
1951
|
const oldResult = ctx.adapter.read(getValue(ctx.ret));
|
|
1952
|
+
_utils.markStart(tagName);
|
|
1925
1953
|
iteration = ctx.iterator.next(oldResult);
|
|
1926
1954
|
}
|
|
1927
1955
|
catch (err) {
|
|
@@ -1935,6 +1963,7 @@ function runComponent(ctx) {
|
|
|
1935
1963
|
if (!_utils.isPromiseLike(iteration)) {
|
|
1936
1964
|
throw new Error("Mixed generator component");
|
|
1937
1965
|
}
|
|
1966
|
+
iteration.then(() => _utils.measureMark(tagName), () => _utils.measureMark(tagName));
|
|
1938
1967
|
const diff = iteration.then((iteration) => {
|
|
1939
1968
|
if (getFlag(ctx.ret, IsInForAwaitOfLoop)) {
|
|
1940
1969
|
// We have entered a for await...of loop, so we start pulling
|
|
@@ -2329,7 +2358,7 @@ async function unmountComponent(ctx, isNested) {
|
|
|
2329
2358
|
}
|
|
2330
2359
|
}
|
|
2331
2360
|
let didLinger = false;
|
|
2332
|
-
if (!isNested && cleanupPromises
|
|
2361
|
+
if (!isNested && cleanupPromises) {
|
|
2333
2362
|
didLinger = true;
|
|
2334
2363
|
const index = ctx.index;
|
|
2335
2364
|
const lingerers = ctx.host.lingerers || (ctx.host.lingerers = []);
|