tungsten 0.3.3 → 0.3.5
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/dist/.build-complete +1 -1
- package/dist/Components.d.ts +6 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.js +103 -40
- package/dist/DomPointer.d.ts +11 -0
- package/dist/DomPointer.d.ts.map +1 -1
- package/dist/DomPointer.js +15 -0
- package/dist/DomRegion.d.ts +2 -0
- package/dist/DomRegion.d.ts.map +1 -1
- package/dist/DomRegion.js +23 -3
- package/dist/Framework.d.ts.map +1 -1
- package/dist/Framework.js +28 -6
- package/dist/FrameworkConfig.d.ts +2 -0
- package/dist/FrameworkConfig.d.ts.map +1 -1
- package/dist/Instance/Instance.d.ts +1 -1
- package/dist/Instance/Instance.d.ts.map +1 -1
- package/dist/Servers/DevServer.d.ts.map +1 -1
- package/dist/Servers/DevServer.js +3 -3
- package/dist/Servers/ProdServer.d.ts +1 -1
- package/dist/Servers/ProdServer.d.ts.map +1 -1
- package/dist/Servers/ProdServer.js +13 -3
- package/dist/Servers/loadEnvsDev.d.ts.map +1 -1
- package/dist/Servers/loadEnvsDev.js +17 -6
- package/dist/babelPlugins/BabelExtractComponentStylesheet.d.ts +13 -0
- package/dist/babelPlugins/BabelExtractComponentStylesheet.d.ts.map +1 -0
- package/dist/babelPlugins/BabelExtractComponentStylesheet.js +24 -0
- package/dist/babelPlugins/BabelTransformComponentStyles.d.ts +2 -0
- package/dist/babelPlugins/BabelTransformComponentStyles.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformComponentStyles.js +21 -5
- package/dist/babelPlugins/BabelTransformPackageImports.d.ts +1 -0
- package/dist/babelPlugins/BabelTransformPackageImports.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformPackageImports.js +21 -9
- package/dist/bin/commands/build.d.ts.map +1 -1
- package/dist/bin/commands/build.js +9 -2
- package/dist/bin/commands/compile.d.ts.map +1 -1
- package/dist/bin/commands/compile.js +7 -2
- package/dist/build/CompilationInfo.d.ts +1 -0
- package/dist/build/CompilationInfo.d.ts.map +1 -1
- package/dist/build/combineComponentCss.d.ts +1 -1
- package/dist/build/combineComponentCss.d.ts.map +1 -1
- package/dist/build/combineComponentCss.js +3 -3
- package/dist/build/compileJavascript.d.ts.map +1 -1
- package/dist/build/compileJavascript.js +2 -2
- package/dist/build/compileUserApp.d.ts.map +1 -1
- package/dist/build/compileUserApp.js +14 -5
- package/dist/build/frameworkStylesheets.d.ts +5 -0
- package/dist/build/frameworkStylesheets.d.ts.map +1 -0
- package/dist/build/frameworkStylesheets.js +49 -0
- package/dist/build/getPackageDirForFile.d.ts +2 -0
- package/dist/build/getPackageDirForFile.d.ts.map +1 -0
- package/dist/build/getPackageDirForFile.js +11 -0
- package/dist/build/getSxyClientPackageAliases.d.ts.map +1 -1
- package/dist/build/getSxyClientPackageAliases.js +3 -1
- package/dist/build/makeBabelUserDevConfig.d.ts +1 -1
- package/dist/build/makeBabelUserDevConfig.d.ts.map +1 -1
- package/dist/build/makeBabelUserDevConfig.js +3 -2
- package/dist/build/makeBabelUserProdConfig.d.ts +1 -1
- package/dist/build/makeBabelUserProdConfig.d.ts.map +1 -1
- package/dist/build/makeBabelUserProdConfig.js +4 -2
- package/dist/build/rollup.user.prod.config.d.ts.map +1 -1
- package/dist/build/rollup.user.prod.config.js +4 -1
- package/dist/build/rollupExtractComponentStylesheets.d.ts +12 -0
- package/dist/build/rollupExtractComponentStylesheets.d.ts.map +1 -0
- package/dist/build/rollupExtractComponentStylesheets.js +35 -0
- package/dist/compat/context.d.ts +1 -1
- package/dist/compat/context.d.ts.map +1 -1
- package/dist/componentCss/i/T-components-library-RichTextEditor-RichTextEditor.css +115 -0
- package/dist/componentCss/i/T-components-library-__StatefulMessage-StatefulMessage.css +25 -0
- package/dist/componentCss/tungsten.css +2 -0
- package/dist/componentProxyHandlers.d.ts +2 -1
- package/dist/componentProxyHandlers.d.ts.map +1 -1
- package/dist/componentProxyHandlers.js +45 -9
- package/dist/components/GoToComponent.d.ts +1 -1
- package/dist/components/GoToComponent.d.ts.map +1 -1
- package/dist/components/_GoToComponent.d.ts +10 -0
- package/dist/components/_GoToComponent.d.ts.map +1 -0
- package/dist/components/_GoToComponent.js +13 -0
- package/dist/components/library/RichTextEditor/RichTextEditor.d.ts +50 -0
- package/dist/components/library/RichTextEditor/RichTextEditor.d.ts.map +1 -0
- package/dist/components/library/RichTextEditor/RichTextEditor.js +458 -0
- package/dist/components/library/RichTextEditor/StoredContentNode.d.ts +5 -0
- package/dist/components/library/RichTextEditor/StoredContentNode.d.ts.map +1 -0
- package/dist/components/library/RichTextEditor/StoredContentNode.js +1 -0
- package/dist/components/library/RichTextEditor/activeFormats.d.ts +20 -0
- package/dist/components/library/RichTextEditor/activeFormats.d.ts.map +1 -0
- package/dist/components/library/RichTextEditor/activeFormats.js +48 -0
- package/dist/components/library/RichTextEditor/contentTree.d.ts +30 -0
- package/dist/components/library/RichTextEditor/contentTree.d.ts.map +1 -0
- package/dist/components/library/RichTextEditor/contentTree.js +81 -0
- package/dist/components/library/RichTextEditor/sanitizeAuthoredHtml.d.ts +30 -0
- package/dist/components/library/RichTextEditor/sanitizeAuthoredHtml.d.ts.map +1 -0
- package/dist/components/library/RichTextEditor/sanitizeAuthoredHtml.js +97 -0
- package/dist/components/library/__StatefulMessage/MessageData.d.ts +7 -0
- package/dist/components/library/__StatefulMessage/MessageData.d.ts.map +1 -0
- package/dist/components/library/__StatefulMessage/MessageData.js +1 -0
- package/dist/components/library/__StatefulMessage/StatefulMessage.d.ts +22 -0
- package/dist/components/library/__StatefulMessage/StatefulMessage.d.ts.map +1 -0
- package/dist/components/library/__StatefulMessage/StatefulMessage.js +88 -0
- package/dist/components/library/index.d.ts +5 -0
- package/dist/components/library/index.d.ts.map +1 -0
- package/dist/components/library/index.js +7 -0
- package/dist/defaultConfig.d.ts +1 -0
- package/dist/defaultConfig.d.ts.map +1 -1
- package/dist/defaultConfig.js +1 -0
- package/dist/env/addToEnv.d.ts +2 -0
- package/dist/env/addToEnv.d.ts.map +1 -0
- package/dist/env/addToEnv.js +19 -0
- package/dist/findUserConfig.d.ts +1 -1
- package/dist/indexInner.d.ts +5 -5
- package/dist/indexInner.d.ts.map +1 -1
- package/dist/indexInner.js +2 -1
- package/dist/postcssPlugins/TransformCssNames.d.ts.map +1 -1
- package/dist/postcssPlugins/TransformCssNames.js +10 -2
- package/dist/propsAreEqual.d.ts +8 -0
- package/dist/propsAreEqual.d.ts.map +1 -0
- package/dist/propsAreEqual.js +97 -0
- package/dist/render/HeadData.d.ts +1 -0
- package/dist/render/HeadData.d.ts.map +1 -1
- package/dist/render/contentToHtml.d.ts.map +1 -1
- package/dist/render/contentToHtml.js +1 -0
- package/dist/render/instantiateClassComponent.d.ts.map +1 -1
- package/dist/render/instantiateClassComponent.js +4 -0
- package/dist/render/processHead.d.ts.map +1 -1
- package/dist/render/processHead.js +14 -1
- package/dist/settings.d.ts +2 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +3 -0
- package/dist/stateTracking.d.ts +47 -0
- package/dist/stateTracking.d.ts.map +1 -0
- package/dist/stateTracking.js +83 -0
- package/dist/staticGen/staticGen.d.ts.map +1 -1
- package/dist/staticGen/staticGen.js +117 -107
- package/dist/testing/installTestStyleProcessors.d.ts +2 -0
- package/dist/testing/installTestStyleProcessors.d.ts.map +1 -0
- package/dist/testing/installTestStyleProcessors.js +21 -0
- package/dist/testing/temp/componentPackageMock.d.ts +18 -0
- package/dist/testing/temp/componentPackageMock.d.ts.map +1 -0
- package/dist/testing/temp/componentPackageMock.js +37 -0
- package/dist/testing/temp/contentHtmlElements.d.ts +3 -0
- package/dist/testing/temp/contentHtmlElements.d.ts.map +1 -0
- package/dist/testing/temp/contentHtmlElements.js +12 -0
- package/dist/update/integrateUpdateVdom.d.ts.map +1 -1
- package/dist/update/integrateUpdateVdom.js +160 -51
- package/dist/update/pendingMounts.d.ts +25 -0
- package/dist/update/pendingMounts.d.ts.map +1 -0
- package/dist/update/pendingMounts.js +55 -0
- package/package.json +4 -2
- package/dist/env/injectEnv.d.ts +0 -2
- package/dist/env/injectEnv.d.ts.map +0 -1
- package/dist/env/injectEnv.js +0 -9
- package/dist/env/parseEnv.d.ts +0 -2
- package/dist/env/parseEnv.d.ts.map +0 -1
- package/dist/env/parseEnv.js +0 -14
|
@@ -24,6 +24,57 @@ import { clearCurrentComponent } from 'tungsten/hooks/clearCurrentComponent.js';
|
|
|
24
24
|
import { isDev } from "../isDev.js";
|
|
25
25
|
import { $original } from "../componentProxyHandlers.js";
|
|
26
26
|
import { booleanAttributeValue } from "../booleanAttributeValue.js";
|
|
27
|
+
import { propsAreEqual } from "../propsAreEqual.js";
|
|
28
|
+
import { markRendered, refreshTracing, renderedAt, trace, tracing } from "../stateTracking.js";
|
|
29
|
+
import { beginMountScope, discardPendingMounts, endMountScope, queueMount } from "./pendingMounts.js";
|
|
30
|
+
/**
|
|
31
|
+
* How many dom nodes each element's rendered content currently occupies, recorded on
|
|
32
|
+
* every real update. When an update is skipped for unchanged props the child's dom is
|
|
33
|
+
* untouched, so the count still holds -- and the dom pointer can be advanced without
|
|
34
|
+
* measuring the region, which would mean scanning parent.childNodes.
|
|
35
|
+
*/
|
|
36
|
+
const skippedNodesLength = new WeakMap();
|
|
37
|
+
const reconcileStats = new Map();
|
|
38
|
+
let reconcileDepth = 0;
|
|
39
|
+
function bucket(name) {
|
|
40
|
+
const existing = reconcileStats.get(name);
|
|
41
|
+
if (existing !== undefined) return existing;
|
|
42
|
+
const created = {
|
|
43
|
+
n: 0,
|
|
44
|
+
ms: 0
|
|
45
|
+
};
|
|
46
|
+
reconcileStats.set(name, created);
|
|
47
|
+
return created;
|
|
48
|
+
}
|
|
49
|
+
/** times `work` into `name` when tracing, and is a plain call-through when not */
|
|
50
|
+
async function timed(name, work) {
|
|
51
|
+
if (!tracing()) return work();
|
|
52
|
+
const b = bucket(name);
|
|
53
|
+
const start = performance.now();
|
|
54
|
+
try {
|
|
55
|
+
return await work();
|
|
56
|
+
} finally {
|
|
57
|
+
b.n++;
|
|
58
|
+
b.ms += performance.now() - start;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function timedSync(name, work) {
|
|
62
|
+
if (!tracing()) return work();
|
|
63
|
+
const b = bucket(name);
|
|
64
|
+
const start = performance.now();
|
|
65
|
+
try {
|
|
66
|
+
return work();
|
|
67
|
+
} finally {
|
|
68
|
+
b.n++;
|
|
69
|
+
b.ms += performance.now() - start;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function reportReconcileStats(elementName) {
|
|
73
|
+
if (reconcileStats.size === 0) return;
|
|
74
|
+
const parts = [...reconcileStats.entries()].sort(([, a], [, b]) => b.ms - a.ms).map(([name, b]) => `${name} ${b.ms.toFixed(1)}ms/${b.n}`);
|
|
75
|
+
trace('RECONCILE', elementName, parts.join(' '));
|
|
76
|
+
reconcileStats.clear();
|
|
77
|
+
}
|
|
27
78
|
function debug(...texts) {
|
|
28
79
|
//console.log(...texts)
|
|
29
80
|
//console.log(getStackTrace().split('\n')[2])
|
|
@@ -33,6 +84,15 @@ const timing = false; // eslint-disable-line no-type-assertion/no-type-assertion
|
|
|
33
84
|
export async function integrateElementUpdateVdom(element, newContent) {
|
|
34
85
|
let startTime = 0;
|
|
35
86
|
if (timing) startTime = new Date().getMilliseconds();
|
|
87
|
+
// Only the outermost call reports, so nested components roll up into one line per
|
|
88
|
+
// update rather than one per component. Depth is restored from the value captured on
|
|
89
|
+
// entry rather than decremented, so a throw in a nested call cannot leave the counter
|
|
90
|
+
// stuck above zero and silently suppress every later report.
|
|
91
|
+
const reconcileEntryDepth = ++reconcileDepth;
|
|
92
|
+
if (reconcileEntryDepth === 1) refreshTracing();
|
|
93
|
+
// anything created during this update mounts detached and is attached before the
|
|
94
|
+
// outermost integrate returns, so its mount hooks wait until then
|
|
95
|
+
beginMountScope();
|
|
36
96
|
debug(`integrateElementUpdateVdom`, element);
|
|
37
97
|
debug(`integrateElementUpdateVdom`, newContent);
|
|
38
98
|
//console.log('***** integrateElementUpdateVdom', element.constructor.name, '*****')
|
|
@@ -97,11 +157,17 @@ export async function integrateElementUpdateVdom(element, newContent) {
|
|
|
97
157
|
//console.log('firstElement', element.__domRegion?.firstElement)
|
|
98
158
|
//console.log('lastElement', element.__domRegion?.lastElement)
|
|
99
159
|
//console.log('Current Content after update for element', element.constructor.name, 'is', element.__currentContent)
|
|
100
|
-
|
|
101
|
-
|
|
160
|
+
// These were just written by updateByIndexes() immediately above, so the region's
|
|
161
|
+
// bounds are already known -- asking positionOfFirst()/positionOfLast() would
|
|
162
|
+
// indexOf them back out of parent.childNodes, an O(n) scan per component update.
|
|
163
|
+
const newPositionOfFirstElement = previousPositionOfFirstElement;
|
|
164
|
+
const newPositionOfLastElement = domPointer.index - 1;
|
|
102
165
|
const newNodesLength = newPositionOfFirstElement >= 0 ? 1 + newPositionOfLastElement - newPositionOfFirstElement : 0;
|
|
103
166
|
//console.log('finished updating element. ', element.constructor.name, ' El positions:', newPositionOfFirstElement, newPositionOfLastElement)
|
|
104
167
|
if (timing) out(`Updated ${element.constructor.name} in ${new Date().getMilliseconds() - startTime}ms`);
|
|
168
|
+
reconcileDepth = reconcileEntryDepth - 1;
|
|
169
|
+
if (reconcileDepth === 0 && tracing()) reportReconcileStats(element.constructor.name);
|
|
170
|
+
endMountScope();
|
|
105
171
|
// newNodesLength previousNodesLength
|
|
106
172
|
return {
|
|
107
173
|
renderedContent: renderedTree,
|
|
@@ -149,7 +215,7 @@ export async function applyUpdate({
|
|
|
149
215
|
//oldNode
|
|
150
216
|
//console.log('array update', newNode)
|
|
151
217
|
//console.log('old array', oldNode)
|
|
152
|
-
const keyedOldChildren = getKeyedOldChildren(oldNode);
|
|
218
|
+
const keyedOldChildren = timedSync('indexOldKeys', () => getKeyedOldChildren(oldNode));
|
|
153
219
|
const tempOldChildren = [...oldNode];
|
|
154
220
|
if (Object.keys(keyedOldChildren).length > 0) {
|
|
155
221
|
//console.log('handling a keyed array')
|
|
@@ -198,7 +264,7 @@ export async function applyUpdate({
|
|
|
198
264
|
// create a throwaway DomPointer because we are going to manage our DomPointer differently
|
|
199
265
|
const tempDomPointer = new DomPointer(domPointer.parent, domPointer.index);
|
|
200
266
|
// update
|
|
201
|
-
|
|
267
|
+
const updated = await timed('keyedUpdate', () => applyUpdate({
|
|
202
268
|
tungstenElement,
|
|
203
269
|
instance,
|
|
204
270
|
newNode: childNode,
|
|
@@ -206,10 +272,11 @@ export async function applyUpdate({
|
|
|
206
272
|
domRegion,
|
|
207
273
|
domPointer: tempDomPointer,
|
|
208
274
|
parentNode
|
|
209
|
-
});
|
|
275
|
+
}));
|
|
276
|
+
renderedContent = updated;
|
|
210
277
|
// move
|
|
211
278
|
//console.log('moving the keyed item', childNode[$key])
|
|
212
|
-
moveRenderedContent(
|
|
279
|
+
timedSync('keyedMove', () => moveRenderedContent(updated, domPointer));
|
|
213
280
|
// delete oldChild from temp list
|
|
214
281
|
delete tempOldChildren[oldChildIndex];
|
|
215
282
|
} else {
|
|
@@ -221,7 +288,7 @@ export async function applyUpdate({
|
|
|
221
288
|
//console.log('update/replace a non keyed item', _nki)
|
|
222
289
|
//throwError('b', 'b')
|
|
223
290
|
// update/replace as required
|
|
224
|
-
|
|
291
|
+
const updated = await timed('unkeyedUpdate', () => applyUpdate({
|
|
225
292
|
tungstenElement,
|
|
226
293
|
instance,
|
|
227
294
|
newNode: childNode,
|
|
@@ -229,11 +296,12 @@ export async function applyUpdate({
|
|
|
229
296
|
domRegion,
|
|
230
297
|
domPointer: tempDomPointer,
|
|
231
298
|
parentNode
|
|
232
|
-
});
|
|
299
|
+
}));
|
|
300
|
+
renderedContent = updated;
|
|
233
301
|
// move -- we need to move it, because we may have removed or inserted elements
|
|
234
302
|
// pushing the original out of place
|
|
235
303
|
//console.log('moving the non keyed item', _nki)
|
|
236
|
-
moveRenderedContent(
|
|
304
|
+
timedSync('unkeyedMove', () => moveRenderedContent(updated, domPointer));
|
|
237
305
|
// remove replaced node from list
|
|
238
306
|
delete tempOldChildren[i];
|
|
239
307
|
} else {
|
|
@@ -241,13 +309,13 @@ export async function applyUpdate({
|
|
|
241
309
|
//console.log('dp', domPointer)
|
|
242
310
|
//throwError('c', 'c')
|
|
243
311
|
// create
|
|
244
|
-
renderedContent = await createNode({
|
|
312
|
+
renderedContent = await timed('create', () => createNode({
|
|
245
313
|
tungstenElement,
|
|
246
314
|
instance,
|
|
247
315
|
newNode: childNode,
|
|
248
316
|
domPointer,
|
|
249
317
|
parentNode
|
|
250
|
-
});
|
|
318
|
+
}));
|
|
251
319
|
}
|
|
252
320
|
}
|
|
253
321
|
renderedNodes.push(renderedContent);
|
|
@@ -258,7 +326,7 @@ export async function applyUpdate({
|
|
|
258
326
|
for (const oldChild of tempOldChildren) {
|
|
259
327
|
if (oldChild) {
|
|
260
328
|
//console.log('deleting old child', oldChild)
|
|
261
|
-
deleteNodeIfRendered(oldChild);
|
|
329
|
+
timedSync('trim', () => deleteNodeIfRendered(oldChild));
|
|
262
330
|
}
|
|
263
331
|
}
|
|
264
332
|
return renderedNodes;
|
|
@@ -490,17 +558,26 @@ function moveRenderedContent(renderedContent, domPointer) {
|
|
|
490
558
|
}
|
|
491
559
|
function moveDomNode(domNode, domPointer) {
|
|
492
560
|
//console.log('moving domNode', domNode)
|
|
493
|
-
|
|
561
|
+
// O(1) position check. positionOf() is Array.indexOf over parent.childNodes, which
|
|
562
|
+
// is O(n) in the sibling count -- and this runs once per node per update, making a
|
|
563
|
+
// keyed array update O(n^2): 20k rows cost ~400M comparisons even when nothing moved.
|
|
564
|
+
// Reading the node already at `index` answers the same question, and differs from
|
|
565
|
+
// domNode in exactly the cases indexOf(domNode) !== index did: the node is elsewhere,
|
|
566
|
+
// is not a child at all (indexOf -1), or index is past the end (undefined).
|
|
567
|
+
// one live-NodeList read, reused as the insertBefore reference below -- see
|
|
568
|
+
// DomPointer.insert() for why these reads are worth counting
|
|
569
|
+
const reference = domPointer.parent.childNodes[domPointer.index]; // eslint-disable-line no-type-assertion/no-type-assertion -- indexing past the end gives undefined, which the DOM types do not model
|
|
570
|
+
if (reference !== domNode) {
|
|
494
571
|
//console.log('document.activeElement', document.activeElement, domNode == document.activeElement, domNode === document.activeElement)
|
|
495
572
|
const ownerDocument = domNode.ownerDocument;
|
|
496
573
|
const hasFocus = domNode === ownerDocument.activeElement;
|
|
497
574
|
const activeElement = ownerDocument.activeElement;
|
|
498
|
-
if (
|
|
575
|
+
if (reference === undefined) {
|
|
499
576
|
//console.log('appending', domNode)
|
|
500
577
|
domPointer.parent.appendChild(domNode);
|
|
501
578
|
} else {
|
|
502
579
|
//console.log('inserting at', domPointer.index, domNode)
|
|
503
|
-
domPointer.parent.insertBefore(domNode,
|
|
580
|
+
domPointer.parent.insertBefore(domNode, reference);
|
|
504
581
|
}
|
|
505
582
|
//console.log('had focus', hasFocus)
|
|
506
583
|
if (hasFocus) domNode.focus();
|
|
@@ -598,12 +675,7 @@ function createComment({
|
|
|
598
675
|
debug('-createComment');
|
|
599
676
|
//console.log('createComment', text)
|
|
600
677
|
const comment = documentFromParent(domPointer.parent).createComment(text);
|
|
601
|
-
|
|
602
|
-
domPointer.parent.appendChild(comment);
|
|
603
|
-
} else {
|
|
604
|
-
domPointer.parent.insertBefore(comment, domPointer.parent.childNodes[domPointer.index]);
|
|
605
|
-
}
|
|
606
|
-
domPointer.increment();
|
|
678
|
+
domPointer.insert(comment);
|
|
607
679
|
return comment;
|
|
608
680
|
}
|
|
609
681
|
export function processHtmlNode(node, parentNode) {
|
|
@@ -744,15 +816,10 @@ function createLiteral(value, domPointer) {
|
|
|
744
816
|
debug('-createLiteral:', value);
|
|
745
817
|
//const textNode = createTextElement(String(value), domPointer)
|
|
746
818
|
const text = String(value);
|
|
747
|
-
debug('dom parent', domPointer.parent);
|
|
748
|
-
debug('dom index', domPointer.index);
|
|
819
|
+
if (isDev) debug('dom parent', domPointer.parent);
|
|
820
|
+
if (isDev) debug('dom index', domPointer.index);
|
|
749
821
|
const textNode = documentFromParent(domPointer.parent).createTextNode(text);
|
|
750
|
-
|
|
751
|
-
domPointer.parent.appendChild(textNode);
|
|
752
|
-
} else {
|
|
753
|
-
domPointer.parent.insertBefore(textNode, domPointer.parent.childNodes[domPointer.index]);
|
|
754
|
-
}
|
|
755
|
-
domPointer.increment();
|
|
822
|
+
domPointer.insert(textNode);
|
|
756
823
|
//domPointer.increment()
|
|
757
824
|
return typeof value === 'string' ? {
|
|
758
825
|
[$type]: $string,
|
|
@@ -772,7 +839,8 @@ async function createHtmlElement(tungstenElement, instance, node, domPointer, pa
|
|
|
772
839
|
processHtmlNode(node, parentNode);
|
|
773
840
|
debug('-createHtmlElement', node);
|
|
774
841
|
const htmlEl = documentFromParent(domPointer.parent).createElement(node[$type]);
|
|
775
|
-
|
|
842
|
+
const extendableEl = htmlEl; // eslint-disable-line no-type-assertion/no-type-assertion -- attributes are attached to the element later
|
|
843
|
+
addAttributes(tungstenElement, instance, node, extendableEl);
|
|
776
844
|
if (node[$type] === 'head') {
|
|
777
845
|
for (const el of instance.persistentHeadChildren) {
|
|
778
846
|
htmlEl.appendChild(el.cloneNode(true));
|
|
@@ -783,21 +851,19 @@ async function createHtmlElement(tungstenElement, instance, node, domPointer, pa
|
|
|
783
851
|
if ('ref' in node[$props]) {
|
|
784
852
|
node[$props].ref.current = htmlEl;
|
|
785
853
|
}
|
|
786
|
-
debug(
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
if (
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
debug('insert before', domPointer.parent.childNodes[domPointer.index]);
|
|
794
|
-
domPointer.parent.insertBefore(htmlEl, domPointer.parent.childNodes[domPointer.index]);
|
|
795
|
-
debug('created el', domPointer.get());
|
|
796
|
-
}
|
|
797
|
-
domPointer.increment();
|
|
854
|
+
// debug() has a commented-out body, but its arguments are still evaluated -- and these
|
|
855
|
+
// ones read the live NodeList, which walks the child list. Behind `if (isDev)` they
|
|
856
|
+
// short-circuit in dev and are removed from the production bundle entirely.
|
|
857
|
+
if (isDev) debug('dom parent', domPointer.parent);
|
|
858
|
+
if (isDev) debug('dom parent children length', domPointer.parent.childNodes.length);
|
|
859
|
+
if (isDev) debug('domPointer.index', domPointer.index);
|
|
860
|
+
if (isDev) debug('new html el', htmlEl);
|
|
798
861
|
//!!!! node.domElement = el
|
|
799
862
|
if (IsHTMLElement(domPointer.parent)) debug('***', node[$type], 'html created', htmlEl);
|
|
800
|
-
// children
|
|
863
|
+
// children -- built while htmlEl is still detached, then the finished subtree is
|
|
864
|
+
// inserted once below. Appending into an element that is already in the document
|
|
865
|
+
// makes the browser invalidate style and layout on every child; off document it
|
|
866
|
+
// does none of that until the subtree lands.
|
|
801
867
|
const innerHtml = innerHtmlFromProps(node[$type], node[$props], validChildren(node[$props].children));
|
|
802
868
|
let renderedChildren;
|
|
803
869
|
if (innerHtml !== null) {
|
|
@@ -819,6 +885,8 @@ async function createHtmlElement(tungstenElement, instance, node, domPointer, pa
|
|
|
819
885
|
htmlEl.value = String(node[$props].value);
|
|
820
886
|
}
|
|
821
887
|
}
|
|
888
|
+
// the subtree is complete, so attach it in one operation
|
|
889
|
+
domPointer.insert(htmlEl);
|
|
822
890
|
return {
|
|
823
891
|
...node,
|
|
824
892
|
domNode: htmlEl,
|
|
@@ -1039,6 +1107,7 @@ export async function createTungstenRootComponent(RootComponent, props, routePar
|
|
|
1039
1107
|
instance,
|
|
1040
1108
|
parentElement: null
|
|
1041
1109
|
});
|
|
1110
|
+
beginMountScope();
|
|
1042
1111
|
try {
|
|
1043
1112
|
await component.__init();
|
|
1044
1113
|
const content = await component.__initialContent();
|
|
@@ -1060,13 +1129,16 @@ export async function createTungstenRootComponent(RootComponent, props, routePar
|
|
|
1060
1129
|
});
|
|
1061
1130
|
component.__domRegion = new DomRegion(container, domStartIndex, domPointer.index - domStartIndex);
|
|
1062
1131
|
component.__currentContent = renderedContent;
|
|
1063
|
-
component.__didMount();
|
|
1132
|
+
queueMount(() => component.__didMount());
|
|
1133
|
+
// everything this root created is attached by now, so the queued hooks can run
|
|
1134
|
+
endMountScope();
|
|
1064
1135
|
return {
|
|
1065
1136
|
component,
|
|
1066
1137
|
openingComment,
|
|
1067
1138
|
closingComment
|
|
1068
1139
|
};
|
|
1069
1140
|
} catch (error) {
|
|
1141
|
+
discardPendingMounts();
|
|
1070
1142
|
component.__delete();
|
|
1071
1143
|
while (container.childNodes.length > domStartIndex) {
|
|
1072
1144
|
container.removeChild(container.childNodes[domStartIndex]);
|
|
@@ -1141,7 +1213,7 @@ async function createTungstenClassComponent(parent, instance, node, domPointer)
|
|
|
1141
1213
|
debug(`[${c}]`, '&& dom region last element', element.__domRegion.lastElement);
|
|
1142
1214
|
//console.log('created dom region', element.__domRegion)
|
|
1143
1215
|
element.__currentContent = renderedContent;
|
|
1144
|
-
element.__didMount();
|
|
1216
|
+
queueMount(() => element.__didMount());
|
|
1145
1217
|
const wrapper = {
|
|
1146
1218
|
...node,
|
|
1147
1219
|
element,
|
|
@@ -1185,11 +1257,14 @@ async function createTungstenFunctionComponent(parent, instance, node, domPointe
|
|
|
1185
1257
|
});
|
|
1186
1258
|
//const renderedContent: RenderedContentNode = await integrateNode({ node: content, domRegion: null, domPointer, parent: element, instance })
|
|
1187
1259
|
const domLength = domPointer.index - domStartIndex;
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1260
|
+
if (isDev) {
|
|
1261
|
+
// these run once per created component, and each allocates a template literal
|
|
1262
|
+
debug(`[${x}]`, '&& Continuing create FC', node);
|
|
1263
|
+
debug(`[${x}]`, '&& domPointer.parent', domPointer.parent);
|
|
1264
|
+
debug(`[${x}]`, '&& dom start index', domStartIndex);
|
|
1265
|
+
debug(`[${x}]`, '&& dom length', domLength);
|
|
1266
|
+
debug(`[${x}]`, '&& dom pointer children', domPointer.parent.childNodes);
|
|
1267
|
+
}
|
|
1193
1268
|
//console.log('new dom region when creating func comp', node[$type].name, domPointer.parent)
|
|
1194
1269
|
element.__domRegion = new DomRegion(domPointer.parent, domStartIndex, domLength);
|
|
1195
1270
|
//console.log('&& dom region first element', element.__domRegion.firstElement)
|
|
@@ -1211,6 +1286,27 @@ async function updateClassComponent({
|
|
|
1211
1286
|
}) {
|
|
1212
1287
|
//console.log('-updateClassComponent', newNode)
|
|
1213
1288
|
const element = oldNode.element;
|
|
1289
|
+
// element.props still holds what was last rendered, so this asks "has anything
|
|
1290
|
+
// changed since we last rendered?". If not, the child's dom is already correct and
|
|
1291
|
+
// re-rendering it is wasted work -- which for a large list child means reconciling
|
|
1292
|
+
// every row so an unrelated sibling can update. componentDidUpdate deliberately does
|
|
1293
|
+
// not fire: the component did not update.
|
|
1294
|
+
if (propsAreEqual(element.props, newNode[$props], renderedAt(element))) {
|
|
1295
|
+
if (tracing()) trace('SKIP', element.constructor.name, '(props unchanged since tick', renderedAt(element), ')');
|
|
1296
|
+
// the dom is current for these props, so the baseline moves with it -- otherwise a
|
|
1297
|
+
// component that only ever skips keeps comparing against its mount tick, and the
|
|
1298
|
+
// first shared object it is handed always looks changed
|
|
1299
|
+
markRendered(element);
|
|
1300
|
+
// the weakmap is only written by a previous update, so on the first update after
|
|
1301
|
+
// mount fall back to measuring the element's own region -- its index caches were
|
|
1302
|
+
// seeded when it was created, so this is not a scan either
|
|
1303
|
+
const cachedLength = skippedNodesLength.get(element) ?? element.__domRegion?.length();
|
|
1304
|
+
if (cachedLength !== undefined) {
|
|
1305
|
+
domPointer.increment(cachedLength);
|
|
1306
|
+
return oldNode;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (tracing()) trace('RENDER', element.constructor.name, '(since tick', renderedAt(element), ')');
|
|
1214
1310
|
element.props = newNode[$props];
|
|
1215
1311
|
debug('update class component', newNode);
|
|
1216
1312
|
//console.log('update class component', newNode)
|
|
@@ -1219,6 +1315,8 @@ async function updateClassComponent({
|
|
|
1219
1315
|
//console.log('domPointer children', [...domPointer.parent.childNodes])
|
|
1220
1316
|
//throwError('','')
|
|
1221
1317
|
const updateResult = await element.__update();
|
|
1318
|
+
markRendered(element);
|
|
1319
|
+
skippedNodesLength.set(element, updateResult.nodesLength);
|
|
1222
1320
|
domPointer.increment(updateResult.nodesLength); // bump the dom pointer index forward by the number of nodes within that element's content
|
|
1223
1321
|
const wrapper = {
|
|
1224
1322
|
...newNode,
|
|
@@ -1237,8 +1335,19 @@ async function updateFunctionComponent({
|
|
|
1237
1335
|
// this func does not seem to be updating the dom pointer index
|
|
1238
1336
|
//console.log('updateFunctionComponent')
|
|
1239
1337
|
const standIn = oldNode.element;
|
|
1338
|
+
// see updateClassComponent: unchanged props mean the rendered dom is already correct
|
|
1339
|
+
if (propsAreEqual(standIn.props, newNode[$props], renderedAt(standIn))) {
|
|
1340
|
+
markRendered(standIn); // see updateClassComponent
|
|
1341
|
+
const cachedLength = skippedNodesLength.get(standIn) ?? standIn.__domRegion?.length();
|
|
1342
|
+
if (cachedLength !== undefined) {
|
|
1343
|
+
domPointer.increment(cachedLength);
|
|
1344
|
+
return oldNode;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1240
1347
|
standIn.props = newNode[$props];
|
|
1241
1348
|
const updateResult = await standIn.update();
|
|
1349
|
+
markRendered(standIn);
|
|
1350
|
+
skippedNodesLength.set(standIn, updateResult.nodesLength);
|
|
1242
1351
|
domPointer.increment(updateResult.nodesLength); // bump the dom pointer index forward by the number of nodes within that element's content
|
|
1243
1352
|
const wrapper = {
|
|
1244
1353
|
...newNode,
|
|
@@ -1337,7 +1446,7 @@ export async function hotSwapClassComponent(oldElement, NewType) {
|
|
|
1337
1446
|
});
|
|
1338
1447
|
newElement.__domRegion = new DomRegion(parent, domStartIndex, domPointer.index - domStartIndex);
|
|
1339
1448
|
newElement.__currentContent = renderedContent;
|
|
1340
|
-
newElement.__didMount();
|
|
1449
|
+
queueMount(() => newElement.__didMount());
|
|
1341
1450
|
wrapper.element = newElement;
|
|
1342
1451
|
wrapper[$props] = newElement.props;
|
|
1343
1452
|
newElement.__renderedNode = wrapper;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defers mount hooks until the dom they belong to is in the document.
|
|
3
|
+
*
|
|
4
|
+
* Elements are assembled off document and inserted once their subtree is complete, which
|
|
5
|
+
* is what React, Vue and Svelte all do -- appending into an already-attached element makes
|
|
6
|
+
* the browser invalidate style and layout on every child. The cost is that a component
|
|
7
|
+
* created part way through that build would see a detached node in componentDidMount, where
|
|
8
|
+
* getBoundingClientRect returns zeros and .focus() does nothing, failing silently rather
|
|
9
|
+
* than throwing.
|
|
10
|
+
*
|
|
11
|
+
* So creation opens a scope, mount hooks queue inside it, and the scope's owner flushes
|
|
12
|
+
* them once everything it created has been attached. Hooks are queued as their components
|
|
13
|
+
* finish creating -- children before parents -- and the queue drains in that order, so a
|
|
14
|
+
* parent's hook can still rely on its children having mounted.
|
|
15
|
+
*/
|
|
16
|
+
export declare function beginMountScope(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Runs `hook` when the current scope closes, or immediately if there is no scope -- the
|
|
19
|
+
* dom is already attached in that case, so there is nothing to wait for.
|
|
20
|
+
*/
|
|
21
|
+
export declare function queueMount(hook: () => void): void;
|
|
22
|
+
export declare function endMountScope(): void;
|
|
23
|
+
/** drops anything still queued -- used when a create pass fails and unwinds */
|
|
24
|
+
export declare function discardPendingMounts(): void;
|
|
25
|
+
//# sourceMappingURL=pendingMounts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pendingMounts.d.ts","sourceRoot":"","sources":["../../src/update/pendingMounts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,IAAI,GAAG,IAAI,CAMjD;AAED,wBAAgB,aAAa,IAAI,IAAI,CAgBpC;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,IAAI,IAAI,CAI3C"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defers mount hooks until the dom they belong to is in the document.
|
|
3
|
+
*
|
|
4
|
+
* Elements are assembled off document and inserted once their subtree is complete, which
|
|
5
|
+
* is what React, Vue and Svelte all do -- appending into an already-attached element makes
|
|
6
|
+
* the browser invalidate style and layout on every child. The cost is that a component
|
|
7
|
+
* created part way through that build would see a detached node in componentDidMount, where
|
|
8
|
+
* getBoundingClientRect returns zeros and .focus() does nothing, failing silently rather
|
|
9
|
+
* than throwing.
|
|
10
|
+
*
|
|
11
|
+
* So creation opens a scope, mount hooks queue inside it, and the scope's owner flushes
|
|
12
|
+
* them once everything it created has been attached. Hooks are queued as their components
|
|
13
|
+
* finish creating -- children before parents -- and the queue drains in that order, so a
|
|
14
|
+
* parent's hook can still rely on its children having mounted.
|
|
15
|
+
*/
|
|
16
|
+
const pending = [];
|
|
17
|
+
let depth = 0;
|
|
18
|
+
let flushing = false;
|
|
19
|
+
export function beginMountScope() {
|
|
20
|
+
depth++;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Runs `hook` when the current scope closes, or immediately if there is no scope -- the
|
|
24
|
+
* dom is already attached in that case, so there is nothing to wait for.
|
|
25
|
+
*/
|
|
26
|
+
export function queueMount(hook) {
|
|
27
|
+
if (depth === 0) {
|
|
28
|
+
hook();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
pending.push(hook);
|
|
32
|
+
}
|
|
33
|
+
export function endMountScope() {
|
|
34
|
+
if (depth > 0) depth--;
|
|
35
|
+
if (depth > 0) return;
|
|
36
|
+
// A hook may create more components, which opens and closes further scopes. Draining
|
|
37
|
+
// from the front means anything they queue joins the end of this same pass rather than
|
|
38
|
+
// starting a nested one.
|
|
39
|
+
if (flushing) return;
|
|
40
|
+
flushing = true;
|
|
41
|
+
try {
|
|
42
|
+
while (pending.length > 0) {
|
|
43
|
+
const hook = pending.shift();
|
|
44
|
+
if (hook !== undefined) hook();
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
flushing = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** drops anything still queued -- used when a create pass fails and unwinds */
|
|
51
|
+
export function discardPendingMounts() {
|
|
52
|
+
pending.length = 0;
|
|
53
|
+
depth = 0;
|
|
54
|
+
flushing = false;
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tungsten",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./dist/index.js",
|
|
12
12
|
"./compat": "./dist/compat/index.js",
|
|
13
|
+
"./library": "./dist/components/library/index.js",
|
|
13
14
|
"./react-zone": "./dist/zones/react.js",
|
|
14
15
|
"./testing": "./dist/testing/index.js",
|
|
15
16
|
"./*": "./dist/*",
|
|
@@ -135,7 +136,8 @@
|
|
|
135
136
|
"__build": "pnpm clean && pnpm build-prep && pnpm build-watch",
|
|
136
137
|
"build": "run-s -c clean build-prep build-watch",
|
|
137
138
|
"clean": "rimraf intermed dist",
|
|
138
|
-
"build-once": "rimraf intermed dist && pnpm build-ts-once && pnpm build-babel-once",
|
|
139
|
+
"build-once": "rimraf intermed dist && pnpm build-ts-once && pnpm build-babel-once && pnpm build-component-styles",
|
|
140
|
+
"build-component-styles": "node scripts/buildComponentStyles.mjs",
|
|
139
141
|
"build-once-nd": "rimraf intermed dist.building && pnpm build-ts-once && pnpm build-babel-staged && node scripts/syncDist.mjs && node scripts/writeBuildMarker.mjs",
|
|
140
142
|
"build-quick": "pnpm build-watch",
|
|
141
143
|
"build-prep": "pnpm build-ts-once",
|
package/dist/env/injectEnv.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"injectEnv.d.ts","sourceRoot":"","sources":["../../src/env/injectEnv.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAe,EAAE,MAAM,GAAE,MAAW,QAQxH"}
|
package/dist/env/injectEnv.js
DELETED
package/dist/env/parseEnv.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parseEnv.d.ts","sourceRoot":"","sources":["../../src/env/parseEnv.ts"],"names":[],"mappings":""}
|
package/dist/env/parseEnv.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export {};
|
|
2
|
-
// export function parseEnv(envContent: string): Record<string, string> {
|
|
3
|
-
// const env: Record<string, string> = {}
|
|
4
|
-
// //console.log('parseEnv')
|
|
5
|
-
// const lines = envContent.split('\n')
|
|
6
|
-
// for (const line of lines) {
|
|
7
|
-
// //console.log('line', line)
|
|
8
|
-
// const parts = line.split('=')
|
|
9
|
-
// if (parts.length === 2) {
|
|
10
|
-
// env[parts[0].trim()] = parts[1].trim()
|
|
11
|
-
// }
|
|
12
|
-
// }
|
|
13
|
-
// return env
|
|
14
|
-
// }
|