@adaas/are-html 0.0.21 → 0.0.22

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.
Files changed (55) hide show
  1. package/.conf/tsconfig.base.json +1 -0
  2. package/.conf/tsconfig.browser.json +1 -0
  3. package/.conf/tsconfig.node.json +1 -0
  4. package/dist/browser/index.d.mts +45 -2
  5. package/dist/browser/index.mjs +170 -10
  6. package/dist/browser/index.mjs.map +1 -1
  7. package/dist/node/directives/AreDirectiveFor.directive.d.mts +37 -1
  8. package/dist/node/directives/AreDirectiveFor.directive.d.ts +37 -1
  9. package/dist/node/directives/AreDirectiveFor.directive.js +85 -4
  10. package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
  11. package/dist/node/directives/AreDirectiveFor.directive.mjs +85 -4
  12. package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
  13. package/dist/node/engine/AreHTML.lifecycle.d.mts +8 -1
  14. package/dist/node/engine/AreHTML.lifecycle.d.ts +8 -1
  15. package/dist/node/engine/AreHTML.lifecycle.js +46 -3
  16. package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
  17. package/dist/node/engine/AreHTML.lifecycle.mjs +46 -3
  18. package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
  19. package/dist/node/helpers/AreScheduler.helper.d.mts +39 -0
  20. package/dist/node/helpers/AreScheduler.helper.d.ts +39 -0
  21. package/dist/node/helpers/AreScheduler.helper.js +40 -0
  22. package/dist/node/helpers/AreScheduler.helper.js.map +1 -0
  23. package/dist/node/helpers/AreScheduler.helper.mjs +40 -0
  24. package/dist/node/helpers/AreScheduler.helper.mjs.map +1 -0
  25. package/dist/node/lib/AreRoot/AreRoot.component.js +1 -1
  26. package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
  27. package/dist/node/lib/AreRoot/AreRoot.component.mjs +1 -1
  28. package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
  29. package/examples/dashboard/dist/index.html +1 -1
  30. package/examples/dashboard/dist/{mq19zxz4-mnlgmd.js → mqh9ryml-xat335.js} +1922 -1316
  31. package/examples/dashboard/src/concept.ts +3 -2
  32. package/examples/for-perf/concept.ts +45 -0
  33. package/examples/for-perf/containers/UI.container.ts +161 -0
  34. package/examples/for-perf/dist/index.html +270 -0
  35. package/examples/for-perf/dist/mqh9ryde-m243t8.js +15223 -0
  36. package/examples/for-perf/dist/mqh9ryfo-6a8d0o.js +15223 -0
  37. package/examples/for-perf/dist/mqh9ryfq-4pf5cv.js +15223 -0
  38. package/examples/for-perf/public/index.html +270 -0
  39. package/examples/for-perf/src/components/PerfApp.component.ts +37 -0
  40. package/examples/for-perf/src/components/PerfControls.component.ts +34 -0
  41. package/examples/for-perf/src/components/PerfGrid.component.ts +225 -0
  42. package/examples/for-perf/src/components/PerfHeader.component.ts +34 -0
  43. package/examples/for-perf/src/components/PerfStats.component.ts +43 -0
  44. package/examples/for-perf/src/concept.ts +94 -0
  45. package/examples/jumpstart/dist/index.html +1 -1
  46. package/examples/jumpstart/dist/{mq7hqrxy-4kus50.js → mq7mgf58-vbf07e.js} +269 -91
  47. package/examples/signal-routing/dist/index.html +1 -1
  48. package/examples/signal-routing/dist/{mq7k53th-qiwy4x.js → mqh9ryc9-dkcbkx.js} +1726 -1419
  49. package/jest.config.ts +1 -0
  50. package/package.json +10 -9
  51. package/src/directives/AreDirectiveFor.directive.ts +141 -10
  52. package/src/engine/AreHTML.lifecycle.ts +83 -6
  53. package/src/helpers/AreScheduler.helper.ts +61 -0
  54. package/src/lib/AreRoot/AreRoot.component.ts +4 -1
  55. package/tsconfig.json +1 -0
package/jest.config.ts CHANGED
@@ -17,6 +17,7 @@ const config: Config.InitialOptions = {
17
17
  "@adaas/are-html/instructions/(.*)": "<rootDir>/src/instructions/$1",
18
18
  "@adaas/are-html/watchers/(.*)": "<rootDir>/src/watchers/$1",
19
19
  "@adaas/are-html/signals/(.*)": "<rootDir>/src/signals/$1",
20
+ "@adaas/are-html/helpers/(.*)": "<rootDir>/src/helpers/$1",
20
21
  // Custom lib exports
21
22
  "@adaas/are-html/style/(.*)": "<rootDir>/src/lib/AreStyle/$1",
22
23
  "@adaas/are-html/directive/(.*)": "<rootDir>/src/lib/AreDirective/$1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaas/are-html",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "description": "A-Concept Rendering Engine (ARE) is a powerful rendering engine designed to work seamlessly with the A-Concept framework. This library provides an HTML engine implementation of ARE, enabling developers to create dynamic and interactive user interfaces for web applications using standard HTML syntax.",
5
5
  "keywords": [
6
6
  "a-concept",
@@ -73,6 +73,7 @@
73
73
  "example:signal-routing": "nodemon ./examples/signal-routing/concept.ts",
74
74
  "example:component-styles": "nodemon ./examples/component-styles/concept.ts",
75
75
  "example:auxta": "nodemon ./examples/auxta/concept.ts",
76
+ "example:for-perf": "nodemon ./examples/for-perf/concept.ts",
76
77
  "test:test": "nodemon ./src/test/app.ts",
77
78
  "release": " npm run build && git add . && git commit -m \"new version created :: $(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g')\" && npm version patch && npm publish --access public",
78
79
  "preversion": "echo test",
@@ -82,16 +83,16 @@
82
83
  "build": "tsup --config tsup.config.ts"
83
84
  },
84
85
  "peerDependencies": {
85
- "@adaas/a-concept": "^0.3.27",
86
- "@adaas/a-frame": "^0.1.14",
87
- "@adaas/a-utils": "^0.3.32",
88
- "@adaas/are": "^0.0.21"
86
+ "@adaas/a-concept": "^0.3.29",
87
+ "@adaas/a-frame": "^0.1.17",
88
+ "@adaas/a-utils": "^0.3.34",
89
+ "@adaas/are": "^0.0.23"
89
90
  },
90
91
  "devDependencies": {
91
- "@adaas/a-concept": "^0.3.27",
92
- "@adaas/a-frame": "^0.1.14",
93
- "@adaas/a-utils": "^0.3.32",
94
- "@adaas/are": "^0.0.21",
92
+ "@adaas/a-concept": "^0.3.29",
93
+ "@adaas/a-frame": "^0.1.17",
94
+ "@adaas/a-utils": "^0.3.34",
95
+ "@adaas/are": "^0.0.23",
95
96
  "@types/chai": "^4.3.14",
96
97
  "@types/jest": "^29.5.12",
97
98
  "@types/mocha": "^10.0.6",
@@ -7,6 +7,7 @@ import { AddCommentInstruction } from "@adaas/are-html/instructions/AddComment.i
7
7
  import { AreHTMLNode } from "@adaas/are-html/node";
8
8
  import { AreDirectiveContext } from "@adaas/are-html/directive/AreDirective.context";
9
9
  import { A_Frame } from "@adaas/a-frame/core";
10
+ import { AreSchedulerHelper } from "@adaas/are-html/helpers/AreScheduler.helper";
10
11
 
11
12
 
12
13
  type AreForExpression = {
@@ -17,6 +18,12 @@ type AreForExpression = {
17
18
  trackExpr: string | undefined;
18
19
  };
19
20
 
21
+ /**
22
+ * Per-`$for` reentrancy state used to serialize chunked (async) renders.
23
+ * Keyed by the directive attribute instance (one per `$for` in the template).
24
+ */
25
+ type AreForRenderState = { running: boolean; pending: boolean };
26
+
20
27
 
21
28
  @A_Frame.Define({
22
29
  namespace: 'a-are-html',
@@ -25,6 +32,32 @@ type AreForExpression = {
25
32
  @AreDirective.Priority(1)
26
33
  export class AreDirectiveFor extends AreDirective {
27
34
 
35
+ /**
36
+ * Lists whose number of NEW item nodes is at or below this threshold render
37
+ * fully synchronously — byte-for-byte the previous behavior. Typical UIs
38
+ * (menus, small tables) are therefore completely unaffected; only genuinely
39
+ * large lists pay the (tiny) scheduling cost to keep the main thread responsive.
40
+ */
41
+ private static readonly SYNC_THRESHOLD = 100;
42
+
43
+ /**
44
+ * Per-chunk time budget (ms). During a large-list render we mount item nodes
45
+ * until this much time has elapsed, then yield to the browser so it can paint
46
+ * and process input before the next chunk. ~16ms targets one animation frame.
47
+ */
48
+ private static readonly CHUNK_BUDGET_MS = 16;
49
+
50
+ /**
51
+ * Per-attribute serialization state. A new update() that arrives while a
52
+ * chunked render of the SAME `$for` is still in flight does NOT start a second
53
+ * concurrent pass (which could interleave mutations on the shared children
54
+ * list); instead it marks `pending` and the in-flight run re-runs once more
55
+ * with the latest data when it finishes. This guarantees the children list is
56
+ * only ever mutated by one pass at a time and the final state always reflects
57
+ * the most recent store value.
58
+ */
59
+ private static readonly renderState = new WeakMap<object, AreForRenderState>();
60
+
28
61
 
29
62
  @AreDirective.Transform
30
63
  transform(
@@ -122,7 +155,40 @@ export class AreDirectiveFor extends AreDirective {
122
155
  @A_Inject(AreStore) store: AreStore,
123
156
  @A_Inject(AreScene) scene: AreScene,
124
157
  ...args: any[]
125
- ): void {
158
+ ): void | Promise<void> {
159
+ /**
160
+ * Serialize chunked renders per `$for`. If a previous large-list render
161
+ * is still streaming item nodes across macrotasks, do NOT start a second
162
+ * concurrent pass — that would interleave two diffs over the same shared
163
+ * children list (and leave half-compiled item nodes that the next diff
164
+ * would wrongly "reuse"). Mark a pass as pending instead; the in-flight
165
+ * run re-diffs once more from the latest store value when it completes.
166
+ */
167
+ let state = AreDirectiveFor.renderState.get(attribute);
168
+ if (!state) {
169
+ state = { running: false, pending: false };
170
+ AreDirectiveFor.renderState.set(attribute, state);
171
+ }
172
+ if (state.running) {
173
+ state.pending = true;
174
+ return;
175
+ }
176
+
177
+ return this.performUpdate(attribute, store, scene, state);
178
+ }
179
+
180
+ /**
181
+ * Core of the `$for` update: re-diff the source array against the current
182
+ * children, reconcile reused/removed items, then mount the new ones (small
183
+ * lists synchronously, large lists time-sliced). Never called while another
184
+ * pass for the same `$for` is in flight (see `update`).
185
+ */
186
+ private performUpdate(
187
+ attribute: AreDirectiveAttribute,
188
+ store: AreStore,
189
+ scene: AreScene,
190
+ state: AreForRenderState,
191
+ ): void | Promise<void> {
126
192
  /**
127
193
  * Re-evaluate the source array.
128
194
  */
@@ -167,9 +233,13 @@ export class AreDirectiveFor extends AreDirective {
167
233
  remaining.add(child);
168
234
  }
169
235
 
170
- // ── 2. Walk desired list; reuse existing or spawn new ───────────────
171
- const desired: AreHTMLNode[] = [];
172
- const newOnes: AreHTMLNode[] = [];
236
+ // ── 2. Walk desired list; reuse existing or record items to create ──
237
+ // NOTE: new item nodes are NOT spawned here. Spawning (cloneWithScope +
238
+ // subtree init + scene activation) is the dominant cost of a large
239
+ // render, so it is deferred into the time-sliced loop below alongside
240
+ // transform/compile/mount. Existing (keyed) children are reconciled in
241
+ // place synchronously — that is cheap and keeps reused rows stable.
242
+ const toCreate: Array<{ item: any; idx: number }> = [];
173
243
 
174
244
  for (let i = 0; i < newArray.length; i++) {
175
245
  const item = newArray[i];
@@ -189,11 +259,8 @@ export class AreDirectiveFor extends AreDirective {
189
259
  [key]: item,
190
260
  [index || 'index']: i,
191
261
  };
192
- desired.push(existing);
193
262
  } else {
194
- const itemNode = this.spawnItemNode(attribute.template!, owner, key, index, item, i);
195
- desired.push(itemNode);
196
- newOnes.push(itemNode);
263
+ toCreate.push({ item, idx: i });
197
264
  }
198
265
  }
199
266
 
@@ -206,8 +273,13 @@ export class AreDirectiveFor extends AreDirective {
206
273
  owner.removeChild(child);
207
274
  }
208
275
 
209
- // ── 4. Mount only the new ones (kept children stay where they are).
210
- for (const child of newOnes) {
276
+ // ── 4. Create + mount the new item nodes. ───────────────────────────
277
+ // `spawnItemNode` appends to `owner.children` immediately, so iterating
278
+ // `toCreate` in source order preserves list order (reused children keep
279
+ // their positions, new rows are appended in order) — identical to the
280
+ // previous synchronous behavior.
281
+ const createItem = (desc: { item: any; idx: number }) => {
282
+ const child = this.spawnItemNode(attribute.template!, owner, key, index, desc.item, desc.idx);
211
283
  child.transform();
212
284
  child.compile();
213
285
  // While detached, stop after compile: the item's instructions are
@@ -215,6 +287,65 @@ export class AreDirectiveFor extends AreDirective {
215
287
  // the correct container once the condition becomes truthy. Mounting
216
288
  // here would hoist the item to the nearest mounted ancestor.
217
289
  if (attached) child.mount();
290
+ };
291
+
292
+ // Small lists → fully synchronous, identical to the previous behavior.
293
+ if (toCreate.length <= AreDirectiveFor.SYNC_THRESHOLD) {
294
+ for (const desc of toCreate) createItem(desc);
295
+ return this.finishUpdate(attribute, store, scene, state);
296
+ }
297
+
298
+ // Large lists → time-sliced render. Create item nodes until the frame
299
+ // budget elapses, then yield to the browser (zero-delay macrotask) so
300
+ // it can paint and stay responsive instead of blocking for the whole
301
+ // batch. The `state.running` flag (see `update`) prevents any other
302
+ // update() for this `$for` from interleaving while we stream.
303
+ state.running = true;
304
+ let cursor = 0;
305
+
306
+ const processChunk = (): void | Promise<void> => {
307
+ try {
308
+ const start = AreSchedulerHelper.now();
309
+ while (cursor < toCreate.length) {
310
+ createItem(toCreate[cursor]);
311
+ cursor++;
312
+ if (AreSchedulerHelper.now() - start >= AreDirectiveFor.CHUNK_BUDGET_MS) break;
313
+ }
314
+ } catch (error) {
315
+ // Never leave the `$for` wedged in the running state on failure,
316
+ // or every future update would be silently deferred forever.
317
+ state.running = false;
318
+ state.pending = false;
319
+ throw error;
320
+ }
321
+
322
+ if (cursor < toCreate.length) {
323
+ return new Promise<void>(resolve => {
324
+ AreSchedulerHelper.scheduleMacrotask(() => resolve(processChunk()));
325
+ });
326
+ }
327
+
328
+ return this.finishUpdate(attribute, store, scene, state);
329
+ };
330
+
331
+ return processChunk();
332
+ }
333
+
334
+ /**
335
+ * Completes an update pass. If another update() arrived while a chunked
336
+ * render was streaming, run exactly one more pass now from the latest store
337
+ * value so the final DOM always reflects the most recent data.
338
+ */
339
+ private finishUpdate(
340
+ attribute: AreDirectiveAttribute,
341
+ store: AreStore,
342
+ scene: AreScene,
343
+ state: AreForRenderState,
344
+ ): void | Promise<void> {
345
+ state.running = false;
346
+ if (state.pending) {
347
+ state.pending = false;
348
+ return this.performUpdate(attribute, store, scene, state);
218
349
  }
219
350
  }
220
351
 
@@ -10,6 +10,7 @@ import { AreDirectiveFeatures } from "@adaas/are-html/directive/AreDirective.con
10
10
  import { AreHTMLEngineContext } from "./AreHTML.context";
11
11
  import { AreHTMLNode } from "../lib/AreHTMLNode/AreHTMLNode";
12
12
  import { A_Frame } from "@adaas/a-frame/core";
13
+ import { AreSchedulerHelper } from "@adaas/are-html/helpers/AreScheduler.helper";
13
14
 
14
15
 
15
16
  @A_Frame.Define({
@@ -18,6 +19,14 @@ import { A_Frame } from "@adaas/a-frame/core";
18
19
  })
19
20
  export class AreHTMLLifecycle extends AreLifecycle {
20
21
 
22
+ /**
23
+ * Per-chunk time budget (ms) for the time-sliced initial mount walk. While
24
+ * mounting a large subtree we keep applying nodes until this much wall-clock
25
+ * time has elapsed, then yield to the browser so it can paint and process
26
+ * input before the next chunk. ~16ms targets a single animation frame.
27
+ */
28
+ private static readonly MOUNT_BUDGET_MS = 16;
29
+
21
30
  @AreLifecycle.Init(AreComponentNode)
22
31
  initComponent(
23
32
  @A_Inject(A_Caller) node: AreHTMLNode,
@@ -92,7 +101,7 @@ export class AreHTMLLifecycle extends AreLifecycle {
92
101
 
93
102
  @A_Inject(A_Logger) logger?: A_Logger,
94
103
  ...args: any[]
95
- ) {
104
+ ): void | Promise<void> {
96
105
 
97
106
  logger?.debug(`[Mount] Component Trigger for <${node.aseid.entity}> with aseid :{${node.aseid.toString()}}`);
98
107
 
@@ -103,18 +112,86 @@ export class AreHTMLLifecycle extends AreLifecycle {
103
112
  if (scene.isInactive) return;
104
113
 
105
114
  /**
106
- * 1. We should simply run and render node itself.
115
+ * 1. Render the root of this mount itself.
107
116
  */
108
117
  node.interpret();
118
+
109
119
  /**
110
- * 2. Then go through all children of the node and mount the.
120
+ * 2. Walk the descendant subtree iteratively with an explicit enter/exit
121
+ * stack so we can TIME-SLICE the work. The previous implementation
122
+ * recursed via `child.mount()`, which fires onBeforeMount → onMount
123
+ * (interpret + recurse) → onAfterMount per node and runs the whole tree
124
+ * in one synchronous, un-yielding block. For large initial trees that
125
+ * froze the main thread on first page load.
126
+ *
127
+ * We replicate the exact per-node hook ordering:
128
+ * - enter → onBeforeMount, then (if active) interpret + queue children
129
+ * - exit → onAfterMount (fires AFTER the node's whole subtree, i.e.
130
+ * post-order, matching the recursive `node.mount()` contract)
131
+ *
132
+ * Small trees complete entirely within a single time budget and the
133
+ * handler returns `void` synchronously — a true fast-path with NO
134
+ * behavioural change for typical UIs. Only genuinely large trees exceed
135
+ * the budget, at which point we yield a macrotask (letting the browser
136
+ * paint / stay responsive) and resume the remaining work, returning a
137
+ * Promise that resolves when the whole subtree is mounted.
111
138
  */
112
- for (let i = 0; i < node.children.length; i++) {
113
- const child = node.children[i];
114
- child.mount();
139
+ interface MountFrame { node: AreHTMLNode; entered: boolean; }
140
+
141
+ const stack: MountFrame[] = [];
142
+ for (let i = node.children.length - 1; i >= 0; i--) {
143
+ stack.push({ node: node.children[i] as AreHTMLNode, entered: false });
115
144
  }
145
+
146
+ const step = (): void => {
147
+ const frame = stack[stack.length - 1];
148
+ const current = frame.node;
149
+
150
+ if (frame.entered) {
151
+ // Post-order exit: the whole subtree below `current` is mounted.
152
+ stack.pop();
153
+ current.call(AreNodeFeatures.onAfterMount, current.scope);
154
+ return;
155
+ }
156
+
157
+ frame.entered = true;
158
+
159
+ // onBeforeMount always fires (even for inactive nodes), matching the
160
+ // recursive AreNode.mount() semantics.
161
+ current.call(AreNodeFeatures.onBeforeMount, current.scope);
162
+
163
+ if (!current.scene.isInactive) {
164
+ current.interpret();
165
+ // Push children in reverse so they pop in document order.
166
+ for (let i = current.children.length - 1; i >= 0; i--) {
167
+ stack.push({ node: current.children[i] as AreHTMLNode, entered: false });
168
+ }
169
+ }
170
+ };
171
+
172
+ const drive = (): void | Promise<void> => {
173
+ const start = AreSchedulerHelper.now();
174
+ while (stack.length > 0) {
175
+ step();
176
+ if (stack.length > 0 && AreSchedulerHelper.now() - start >= AreHTMLLifecycle.MOUNT_BUDGET_MS) {
177
+ // Budget exhausted with work remaining — yield, then resume.
178
+ return new Promise<void>((resolve, reject) => {
179
+ AreSchedulerHelper.scheduleMacrotask(() => {
180
+ try {
181
+ resolve(drive());
182
+ } catch (error) {
183
+ reject(error);
184
+ }
185
+ });
186
+ });
187
+ }
188
+ }
189
+ };
190
+
191
+ return drive();
116
192
  }
117
193
 
194
+
118
195
  @A_Feature.Extend({
119
196
  name: AreAttributeFeatures.Update,
120
197
  scope: [AreDirectiveAttribute],
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AreSchedulerHelper
3
+ *
4
+ * Cooperative time-slicing primitives shared by the chunked (async) render
5
+ * paths — the initial whole-page mount walk and the `$for` directive. Both need
6
+ * the SAME two capabilities:
7
+ * 1. a high-resolution clock to measure how long the current chunk has run, and
8
+ * 2. a zero-delay macrotask scheduler to yield to the browser between chunks
9
+ * so it can paint and process input before resuming work.
10
+ *
11
+ * Keeping these in one helper avoids duplicating the `MessageChannel` plumbing
12
+ * across directives/lifecycle and gives a single place to tune the strategy.
13
+ */
14
+ export class AreSchedulerHelper {
15
+
16
+ /**
17
+ * Lazily-created `MessageChannel` used to post zero-delay macrotasks.
18
+ * Created on first use so non-DOM environments (tests / SSR) that never
19
+ * schedule a chunk pay nothing.
20
+ */
21
+ private static _channel?: MessageChannel;
22
+
23
+ /** FIFO queue of callbacks waiting for their posted macrotask to fire. */
24
+ private static readonly _queue: Array<() => void> = [];
25
+
26
+ /**
27
+ * High-resolution wall-clock time in milliseconds. Uses `performance.now()`
28
+ * when available (monotonic, sub-millisecond), falling back to `Date.now()`.
29
+ */
30
+ static now(): number {
31
+ return (typeof performance !== 'undefined' && typeof performance.now === 'function')
32
+ ? performance.now()
33
+ : Date.now();
34
+ }
35
+
36
+ /**
37
+ * Schedule `fn` to run on the next macrotask.
38
+ *
39
+ * `MessageChannel` yields a true macrotask without the ~4ms clamp that nested
40
+ * `setTimeout(0)` calls incur, so the browser can paint between chunks with
41
+ * minimal scheduling overhead. Falls back to `setTimeout` in non-DOM
42
+ * environments (e.g. tests / SSR).
43
+ */
44
+ static scheduleMacrotask(fn: () => void): void {
45
+ if (typeof MessageChannel === 'undefined') {
46
+ setTimeout(fn, 0);
47
+ return;
48
+ }
49
+
50
+ if (!this._channel) {
51
+ this._channel = new MessageChannel();
52
+ this._channel.port1.onmessage = () => {
53
+ const next = this._queue.shift();
54
+ if (next) next();
55
+ };
56
+ }
57
+
58
+ this._queue.push(fn);
59
+ this._channel.port2.postMessage(null);
60
+ }
61
+ }
@@ -160,7 +160,10 @@ export class AreRoot extends Are {
160
160
  child.transform();
161
161
 
162
162
  child.compile();
163
- child.mount();
163
+ // The HTML engine time-slices large initial mounts; await so a heavy
164
+ // routed component renders in yielding chunks instead of freezing the
165
+ // main thread on first entry. Small subtrees resolve synchronously.
166
+ await child.mount();
164
167
  }
165
168
  }
166
169
 
package/tsconfig.json CHANGED
@@ -15,6 +15,7 @@
15
15
  "@adaas/are-html/instructions/*": ["src/instructions/*"],
16
16
  "@adaas/are-html/watchers/*": ["src/watchers/*"],
17
17
  "@adaas/are-html/signals/*": ["src/signals/*"],
18
+ "@adaas/are-html/helpers/*": ["src/helpers/*"],
18
19
  // Custom Lib Exports
19
20
  "@adaas/are-html/style/*": ["src/lib/AreStyle/*"],
20
21
  "@adaas/are-html/directive/*": ["src/lib/AreDirective/*"],