balises 0.8.2 → 0.8.3
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 +23 -17
- package/dist/esm/async.d.ts +10 -1
- package/dist/esm/async.d.ts.map +1 -1
- package/dist/esm/async.js +2 -1
- package/dist/esm/async.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -113,8 +113,11 @@ const html = baseHtml.with(asyncPlugin);
|
|
|
113
113
|
const userId = signal(1);
|
|
114
114
|
|
|
115
115
|
html`
|
|
116
|
-
${async function* () {
|
|
116
|
+
${async function* (settled, ctx) {
|
|
117
|
+
void settled;
|
|
117
118
|
const id = userId.value; // Track dependency - restarts when userId changes
|
|
119
|
+
const prev = ctx?.lastId;
|
|
120
|
+
if (ctx) ctx.lastId = id;
|
|
118
121
|
|
|
119
122
|
yield html`<div class="loading">Loading...</div>`;
|
|
120
123
|
|
|
@@ -129,6 +132,8 @@ userId.value = 2;
|
|
|
129
132
|
|
|
130
133
|
Async generators replace the entire yielded content on each yield. For surgical updates within a stable DOM structure, use reactive bindings (`${() => state.value}`) instead.
|
|
131
134
|
|
|
135
|
+
Generators receive a mutable context object as their second argument. This object persists across restarts and can hold user-defined state for diffing or caching. Use the `AsyncGeneratorContext<T>` type from `balises/async` for type safety.
|
|
136
|
+
|
|
132
137
|
### DOM Preservation on Restart
|
|
133
138
|
|
|
134
139
|
When a signal changes, the generator restarts and normally replaces the DOM. To preserve existing DOM and enable surgical updates via reactive bindings, return the `settled` parameter:
|
|
@@ -142,8 +147,9 @@ const userId = signal(1);
|
|
|
142
147
|
const state = store({ user: null, loading: false });
|
|
143
148
|
|
|
144
149
|
html`
|
|
145
|
-
${async function* (settled?: RenderedContent) {
|
|
150
|
+
${async function* (settled?: RenderedContent, ctx?: { lastId?: number }) {
|
|
146
151
|
const id = userId.value;
|
|
152
|
+
if (ctx) ctx.lastId = id;
|
|
147
153
|
|
|
148
154
|
if (settled) {
|
|
149
155
|
// Restart: update state, preserve existing DOM
|
|
@@ -700,23 +706,23 @@ Performance comparison of Balises against other popular reactive libraries. Benc
|
|
|
700
706
|
┌───────┬───────────────────┬───────┬───────────────┬──────────────────┐
|
|
701
707
|
│ Rank │ Library │ Score │ Avg Time (μs) │ vs Fastest │
|
|
702
708
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
703
|
-
│ #1 🏆 │ preact@1.12.1 │ 0.000 │
|
|
709
|
+
│ #1 🏆 │ preact@1.12.1 │ 0.000 │ 64.77 │ 1.00x (baseline) │
|
|
704
710
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
705
|
-
│ #2 │ balises@0.8.
|
|
711
|
+
│ #2 │ balises@0.8.2 │ 0.049 │ 94.85 │ 1.46x │
|
|
706
712
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
707
|
-
│ #3 │ vue@3.5.26 │ 0.
|
|
713
|
+
│ #3 │ vue@3.5.26 │ 0.098 │ 95.44 │ 1.47x │
|
|
708
714
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
709
|
-
│ #4 │ maverick@6.0.0 │ 0.
|
|
715
|
+
│ #4 │ maverick@6.0.0 │ 0.140 │ 123.60 │ 1.91x │
|
|
710
716
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
711
|
-
│ #5 │ usignal@0.10.0 │ 0.
|
|
717
|
+
│ #5 │ usignal@0.10.0 │ 0.183 │ 137.29 │ 2.12x │
|
|
712
718
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
713
|
-
│ #6 │ angular@19.2.17 │ 0.
|
|
719
|
+
│ #6 │ angular@19.2.17 │ 0.212 │ 170.18 │ 2.63x │
|
|
714
720
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
715
|
-
│ #7 │ solid@1.9.10 │ 0.342 │
|
|
721
|
+
│ #7 │ solid@1.9.10 │ 0.342 │ 267.61 │ 4.13x │
|
|
716
722
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
717
|
-
│ #8 │ mobx@6.15.0 │ 0.
|
|
723
|
+
│ #8 │ mobx@6.15.0 │ 0.836 │ 930.40 │ 14.37x │
|
|
718
724
|
├───────┼───────────────────┼───────┼───────────────┼──────────────────┤
|
|
719
|
-
│ #9 │ hyperactiv@0.11.3 │ 1.000 │
|
|
725
|
+
│ #9 │ hyperactiv@0.11.3 │ 1.000 │ 1105.34 │ 17.07x │
|
|
720
726
|
└───────┴───────────────────┴───────┴───────────────┴──────────────────┘
|
|
721
727
|
```
|
|
722
728
|
|
|
@@ -726,19 +732,19 @@ Performance comparison of Balises against other popular reactive libraries. Benc
|
|
|
726
732
|
┌───────────────────┬───────────────┬─────────────┬────────────────┬────────────────────┬─────────────┬──────────────┬──────────┐
|
|
727
733
|
│ Library │ S1: 1: Layers │ S2: 2: Wide │ S3: 3: Diamond │ S4: 4: Conditional │ S5: 5: List │ S6: 6: Batch │ Avg Rank │
|
|
728
734
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
729
|
-
│ preact@1.12.1 │ #1 🏆 │ #1 🏆 │ #
|
|
735
|
+
│ preact@1.12.1 │ #1 🏆 │ #1 🏆 │ #2 │ #1 🏆 │ #1 🏆 │ #1 🏆 │ 1.2 │
|
|
730
736
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
731
|
-
│ balises@0.8.
|
|
737
|
+
│ balises@0.8.2 │ #3 │ #2 │ #1 🏆 │ #2 │ #2 │ #2 │ 2.0 │
|
|
732
738
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
733
739
|
│ vue@3.5.26 │ #2 │ #3 │ #5 │ #3 │ #3 │ #5 │ 3.5 │
|
|
734
740
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
735
|
-
│ maverick@6.0.0 │ #
|
|
741
|
+
│ maverick@6.0.0 │ #4 │ #5 │ #4 │ #4 │ #4 │ #4 │ 4.2 │
|
|
736
742
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
737
|
-
│ usignal@0.10.0 │ #
|
|
743
|
+
│ usignal@0.10.0 │ #5 │ #4 │ #3 │ #5 │ #8 │ #6 │ 5.2 │
|
|
738
744
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
739
745
|
│ angular@19.2.17 │ #6 │ #6 │ #6 │ #6 │ #5 │ #3 │ 5.3 │
|
|
740
746
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
741
|
-
│ solid@1.9.10 │ #7 │ #8 │ #7 │ #7 │ #7 │ #
|
|
747
|
+
│ solid@1.9.10 │ #7 │ #8 │ #7 │ #7 │ #7 │ #7 │ 7.2 │
|
|
742
748
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
743
749
|
│ mobx@6.15.0 │ #9 │ #7 │ #8 │ #8 │ #6 │ #8 │ 7.7 │
|
|
744
750
|
├───────────────────┼───────────────┼─────────────┼────────────────┼────────────────────┼─────────────┼──────────────┼──────────┤
|
|
@@ -761,7 +767,7 @@ Performance comparison of Balises against other popular reactive libraries. Benc
|
|
|
761
767
|
- These are synthetic benchmarks measuring pure reactivity - real apps should consider the whole picture (ecosystem, docs, community, etc.)
|
|
762
768
|
- Lower rank = better performance
|
|
763
769
|
|
|
764
|
-
_Last updated: 2026-01-
|
|
770
|
+
_Last updated: 2026-01-19_
|
|
765
771
|
|
|
766
772
|
<!-- BENCHMARK_RESULTS_END -->
|
|
767
773
|
|
package/dist/esm/async.d.ts
CHANGED
|
@@ -33,8 +33,13 @@ import { type InterpolationPlugin } from "./template.js";
|
|
|
33
33
|
* ```ts
|
|
34
34
|
* import asyncPlugin, { type RenderedContent } from "balises/async";
|
|
35
35
|
*
|
|
36
|
-
* async function* loadUser(
|
|
36
|
+
* async function* loadUser(
|
|
37
|
+
* settled?: RenderedContent,
|
|
38
|
+
* ctx?: AsyncGeneratorContext<{ lastId?: number }>,
|
|
39
|
+
* ) {
|
|
37
40
|
* const id = userId.value; // Track dependency
|
|
41
|
+
* const previous = ctx?.lastId;
|
|
42
|
+
* ctx && (ctx.lastId = id);
|
|
38
43
|
*
|
|
39
44
|
* if (settled) {
|
|
40
45
|
* // Restart: update state, keep existing DOM
|
|
@@ -55,6 +60,10 @@ export interface RenderedContent {
|
|
|
55
60
|
/** @internal Brand to prevent construction outside the library */
|
|
56
61
|
readonly __brand: "RenderedContent";
|
|
57
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Mutable context object that persists across async generator restarts.
|
|
65
|
+
*/
|
|
66
|
+
export type AsyncGeneratorContext<T extends object = Record<string, unknown>> = T;
|
|
58
67
|
/**
|
|
59
68
|
* Plugin that handles async generator functions.
|
|
60
69
|
* Auto-detects `async function*` without needing a wrapper.
|
package/dist/esm/async.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/async.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,OAAO,EAAY,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAMnE
|
|
1
|
+
{"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/async.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,OAAO,EAAY,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAMnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC1E,CAAC,CAAC;AAyDJ;;;GAGG;AACH,QAAA,MAAM,WAAW,EAAE,mBAMlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
package/dist/esm/async.js
CHANGED
|
@@ -120,6 +120,7 @@ function bindAsyncGenerator(genFn, marker, disposers) {
|
|
|
120
120
|
let iterationId = 0;
|
|
121
121
|
let depUnsubscribers = [];
|
|
122
122
|
let lastSettled = null;
|
|
123
|
+
const context = {};
|
|
123
124
|
const clearNodes = () => {
|
|
124
125
|
for (let i = 0; i < childDisposers.length; i++)
|
|
125
126
|
childDisposers[i]();
|
|
@@ -153,7 +154,7 @@ function bindAsyncGenerator(genFn, marker, disposers) {
|
|
|
153
154
|
cleanupGenerator();
|
|
154
155
|
if (disposed)
|
|
155
156
|
return;
|
|
156
|
-
generator = genFn(lastSettled ?? undefined);
|
|
157
|
+
generator = genFn(lastSettled ?? undefined, context);
|
|
157
158
|
let lastYielded = null;
|
|
158
159
|
while (!disposed && thisIteration === iterationId) {
|
|
159
160
|
let result;
|
package/dist/esm/async.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/async.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Async generator support for templates.\n *\n * This module provides opt-in support for async generators in templates,\n * enabling loading states, progressive content, and automatic restart\n * when signal dependencies change.\n *\n * @example\n * ```ts\n * import { html as baseHtml, signal } from \"balises\";\n * import asyncPlugin from \"balises/async\";\n *\n * const html = baseHtml.with(asyncPlugin);\n * const userId = signal(1);\n *\n * // Async generators are auto-detected - no wrapper needed!\n * html`<div>${async function* () {\n * yield html`<span>Loading user ${userId.value}...</span>`;\n * const user = await fetchUser(userId.value);\n * return html`<span>${user.name}</span>`;\n * }}</div>`.render();\n * ```\n */\n\nimport {\n onTrack,\n type Subscriber,\n type TrackableSource,\n} from \"./signals/context.js\";\nimport { Template, type InterpolationPlugin } from \"./template.js\";\nimport { isSignal, type Reactive } from \"./signals/index.js\";\n\n/** Reactive source type - TrackableSource may or may not be subscribable */\ntype SubscribableSource = Reactive<unknown>;\n\n/**\n * Opaque handle representing settled content from an async generator.\n *\n * When an async generator restarts due to signal changes, it receives the\n * previous settled content as its first argument. Return this value to\n * preserve the existing DOM instead of re-rendering.\n *\n * @example\n * ```ts\n * import asyncPlugin, { type RenderedContent } from \"balises/async\";\n *\n * async function* loadUser(settled?: RenderedContent) {\n * const id = userId.value; // Track dependency\n *\n * if (settled) {\n * // Restart: update state, keep existing DOM\n * const user = await fetchUser(id);\n * state.user = user; // Triggers surgical updates via reactive bindings\n * return settled; // Preserve DOM\n * }\n *\n * // First load\n * yield html`<div class=\"skeleton\">...</div>`;\n * const user = await fetchUser(id);\n * state.user = user;\n * return UserCard({ state });\n * }\n * ```\n */\nexport interface RenderedContent {\n /** @internal Brand to prevent construction outside the library */\n readonly __brand: \"RenderedContent\";\n}\n\n/** Internal structure for RenderedContent */\ninterface RenderedContentInternal extends RenderedContent {\n readonly nodes: Node[];\n readonly childDisposers: (() => void)[];\n}\n\n/** Async generator function type */\ntype AsyncGenFn = (\n settled?: RenderedContent,\n) => AsyncGenerator<unknown, unknown, unknown>;\n\n/**\n * Check if a value is an async generator function.\n */\nfunction isAsyncGeneratorFunction(\n value: unknown,\n): value is AsyncGeneratorFunction {\n if (typeof value !== \"function\") return false;\n const constructor = value.constructor;\n return (\n constructor &&\n (constructor.name === \"AsyncGeneratorFunction\" ||\n // Check prototype chain for async generator\n Object.prototype.toString.call(constructor.prototype) ===\n \"[object AsyncGeneratorFunction]\")\n );\n}\n\n/**\n * Render content and insert nodes before marker.\n * Handles Templates and primitives.\n */\nfunction insertContent(\n marker: Comment,\n value: unknown,\n nodes: Node[],\n disposers: (() => void)[],\n): void {\n const parent = marker.parentNode!;\n\n for (const item of Array.isArray(value) ? value : [value]) {\n if (item instanceof Template) {\n const { fragment, dispose } = item.render();\n disposers.push(dispose);\n nodes.push(...fragment.childNodes);\n parent.insertBefore(fragment, marker);\n } else if (item != null && typeof item !== \"boolean\") {\n const node = document.createTextNode(String(item));\n nodes.push(node);\n parent.insertBefore(node, marker);\n }\n }\n}\n\n/**\n * Plugin that handles async generator functions.\n * Auto-detects `async function*` without needing a wrapper.\n */\nconst asyncPlugin: InterpolationPlugin = (value) => {\n if (!isAsyncGeneratorFunction(value)) return null;\n\n return (marker, disposers) => {\n bindAsyncGenerator(value as AsyncGenFn, marker, disposers);\n };\n};\n\nexport default asyncPlugin;\n\n/** Result of tracking dependencies during a function call */\ninterface TrackResult<T> {\n value: T;\n subscribe: (callback: Subscriber) => void;\n unsubscribe: () => void;\n}\n\n/**\n * Track reactive dependencies accessed during a function call.\n * Sets up the onTrack hook temporarily to capture signal/computed accesses.\n */\nfunction track<T>(fn: () => T): TrackResult<T> {\n const sources = new Set<TrackableSource>();\n const prevHook = onTrack.current;\n onTrack.current = (source) => sources.add(source);\n\n let value: T;\n try {\n value = fn();\n } finally {\n onTrack.current = prevHook;\n }\n\n let unsubscribers: (() => void)[] = [];\n let subscribed = false;\n\n return {\n value,\n subscribe: (callback: Subscriber) => {\n if (subscribed) return;\n subscribed = true;\n for (const source of sources) {\n // Only subscribe to actual signals/computeds (not selector slots)\n if (isSignal(source)) {\n unsubscribers.push(\n (source as SubscribableSource).subscribe(callback),\n );\n }\n }\n },\n unsubscribe: () => {\n for (const unsub of unsubscribers) unsub();\n unsubscribers = [];\n subscribed = false;\n },\n };\n}\n\n/**\n * Bind an async generator function to a marker position.\n * Tracks signal dependencies during generator execution and restarts\n * the generator when those dependencies change.\n */\nfunction bindAsyncGenerator(\n genFn: AsyncGenFn,\n marker: Comment,\n disposers: (() => void)[],\n): void {\n let generator: AsyncGenerator<unknown> | null = null;\n let currentNodes: Node[] = [];\n let childDisposers: (() => void)[] = [];\n let disposed = false;\n let iterationId = 0;\n let depUnsubscribers: (() => void)[] = [];\n let lastSettled: RenderedContentInternal | null = null;\n\n const clearNodes = () => {\n for (let i = 0; i < childDisposers.length; i++) childDisposers[i]!();\n for (let i = 0; i < currentNodes.length; i++)\n currentNodes[i]!.parentNode?.removeChild(currentNodes[i]!);\n childDisposers = [];\n currentNodes = [];\n };\n\n const clearDeps = () => {\n for (let i = 0; i < depUnsubscribers.length; i++) depUnsubscribers[i]!();\n depUnsubscribers = [];\n };\n\n const cleanupGenerator = () => {\n clearDeps();\n if (generator) {\n generator.return(undefined);\n generator = null;\n }\n };\n\n const cleanup = () => {\n cleanupGenerator();\n clearNodes();\n };\n\n const render = (value: unknown) => {\n clearNodes();\n insertContent(marker, value, currentNodes, childDisposers);\n };\n\n const runGenerator = async () => {\n const thisIteration = ++iterationId;\n cleanupGenerator();\n\n if (disposed) return;\n\n generator = genFn(lastSettled ?? undefined);\n let lastYielded: unknown = null;\n\n while (!disposed && thisIteration === iterationId) {\n let result: IteratorResult<unknown>;\n\n try {\n const tracked = track(() => generator!.next());\n\n tracked.subscribe(() => {\n if (!disposed && thisIteration === iterationId) {\n void runGenerator();\n }\n });\n depUnsubscribers.push(tracked.unsubscribe);\n\n result = await tracked.value;\n } catch (e) {\n cleanup();\n if (!disposed) throw e;\n return;\n }\n\n if (thisIteration !== iterationId) return;\n\n const { value, done } = result;\n\n if (done) {\n if (value === lastSettled && lastSettled !== null) {\n currentNodes = lastSettled.nodes;\n childDisposers = lastSettled.childDisposers;\n } else {\n render(value !== undefined ? value : lastYielded);\n lastSettled = {\n __brand: \"RenderedContent\" as const,\n nodes: currentNodes,\n childDisposers: childDisposers,\n };\n }\n return;\n }\n\n lastYielded = value;\n render(value);\n }\n };\n\n void runGenerator();\n\n disposers.push(() => {\n disposed = true;\n cleanup();\n lastSettled = null;\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/async.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,OAAO,GAGR,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAA4B,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAiB,MAAM,oBAAoB,CAAC;AA8D7D;;GAEG;AACH,SAAS,wBAAwB,CAC/B,KAAc;IAEd,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9C,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,OAAO,CACL,WAAW;QACX,CAAC,WAAW,CAAC,IAAI,KAAK,wBAAwB;YAC5C,4CAA4C;YAC5C,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC;gBACnD,iCAAiC,CAAC,CACvC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CACpB,MAAe,EACf,KAAc,EACd,KAAa,EACb,SAAyB;IAEzB,MAAM,MAAM,GAAG,MAAM,CAAC,UAAW,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1D,IAAI,IAAI,YAAY,QAAQ,EAAE,CAAC;YAC7B,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;YACnC,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;aAAM,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,SAAS,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,WAAW,GAAwB,CAAC,KAAK,EAAE,EAAE;IACjD,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAElD,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE;QAC3B,kBAAkB,CAAC,KAAmB,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IAC7D,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,WAAW,CAAC;AAS3B;;;GAGG;AACH,SAAS,KAAK,CAAI,EAAW;IAC3B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,OAAO,CAAC,OAAO,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAElD,IAAI,KAAQ,CAAC;IACb,IAAI,CAAC;QACH,KAAK,GAAG,EAAE,EAAE,CAAC;IACf,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC;IAC7B,CAAC;IAED,IAAI,aAAa,GAAmB,EAAE,CAAC;IACvC,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,OAAO;QACL,KAAK;QACL,SAAS,EAAE,CAAC,QAAoB,EAAE,EAAE;YAClC,IAAI,UAAU;gBAAE,OAAO;YACvB,UAAU,GAAG,IAAI,CAAC;YAClB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,kEAAkE;gBAClE,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;oBACrB,aAAa,CAAC,IAAI,CACf,MAA6B,CAAC,SAAS,CAAC,QAAQ,CAAC,CACnD,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QACD,WAAW,EAAE,GAAG,EAAE;YAChB,KAAK,MAAM,KAAK,IAAI,aAAa;gBAAE,KAAK,EAAE,CAAC;YAC3C,aAAa,GAAG,EAAE,CAAC;YACnB,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CACzB,KAAiB,EACjB,MAAe,EACf,SAAyB;IAEzB,IAAI,SAAS,GAAmC,IAAI,CAAC;IACrD,IAAI,YAAY,GAAW,EAAE,CAAC;IAC9B,IAAI,cAAc,GAAmB,EAAE,CAAC;IACxC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,gBAAgB,GAAmB,EAAE,CAAC;IAC1C,IAAI,WAAW,GAAmC,IAAI,CAAC;IACvD,MAAM,OAAO,GAA0B,EAAE,CAAC;IAE1C,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,cAAc,CAAC,CAAC,CAAE,EAAE,CAAC;QACrE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE;YAC1C,YAAY,CAAC,CAAC,CAAE,CAAC,UAAU,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC,CAAE,CAAC,CAAC;QAC7D,cAAc,GAAG,EAAE,CAAC;QACpB,YAAY,GAAG,EAAE,CAAC;IACpB,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,gBAAgB,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,gBAAgB,CAAC,CAAC,CAAE,EAAE,CAAC;QACzE,gBAAgB,GAAG,EAAE,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG,GAAG,EAAE;QAC5B,SAAS,EAAE,CAAC;QACZ,IAAI,SAAS,EAAE,CAAC;YACd,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC5B,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,gBAAgB,EAAE,CAAC;QACnB,UAAU,EAAE,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,EAAE;QAChC,UAAU,EAAE,CAAC;QACb,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;IAC7D,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,IAAI,EAAE;QAC9B,MAAM,aAAa,GAAG,EAAE,WAAW,CAAC;QACpC,gBAAgB,EAAE,CAAC;QAEnB,IAAI,QAAQ;YAAE,OAAO;QAErB,SAAS,GAAG,KAAK,CAAC,WAAW,IAAI,SAAS,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,WAAW,GAAY,IAAI,CAAC;QAEhC,OAAO,CAAC,QAAQ,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;YAClD,IAAI,MAA+B,CAAC;YAEpC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,SAAU,CAAC,IAAI,EAAE,CAAC,CAAC;gBAE/C,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE;oBACrB,IAAI,CAAC,QAAQ,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;wBAC/C,KAAK,YAAY,EAAE,CAAC;oBACtB,CAAC;gBACH,CAAC,CAAC,CAAC;gBACH,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBAE3C,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC;YAC/B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC,QAAQ;oBAAE,MAAM,CAAC,CAAC;gBACvB,OAAO;YACT,CAAC;YAED,IAAI,aAAa,KAAK,WAAW;gBAAE,OAAO;YAE1C,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;YAE/B,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,KAAK,KAAK,WAAW,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;oBAClD,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC;oBACjC,cAAc,GAAG,WAAW,CAAC,cAAc,CAAC;gBAC9C,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;oBAClD,WAAW,GAAG;wBACZ,OAAO,EAAE,iBAA0B;wBACnC,KAAK,EAAE,YAAY;wBACnB,cAAc,EAAE,cAAc;qBAC/B,CAAC;gBACJ,CAAC;gBACD,OAAO;YACT,CAAC;YAED,WAAW,GAAG,KAAK,CAAC;YACpB,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,YAAY,EAAE,CAAC;IAEpB,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;QAClB,QAAQ,GAAG,IAAI,CAAC;QAChB,OAAO,EAAE,CAAC;QACV,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Async generator support for templates.\n *\n * This module provides opt-in support for async generators in templates,\n * enabling loading states, progressive content, and automatic restart\n * when signal dependencies change.\n *\n * @example\n * ```ts\n * import { html as baseHtml, signal } from \"balises\";\n * import asyncPlugin from \"balises/async\";\n *\n * const html = baseHtml.with(asyncPlugin);\n * const userId = signal(1);\n *\n * // Async generators are auto-detected - no wrapper needed!\n * html`<div>${async function* () {\n * yield html`<span>Loading user ${userId.value}...</span>`;\n * const user = await fetchUser(userId.value);\n * return html`<span>${user.name}</span>`;\n * }}</div>`.render();\n * ```\n */\n\nimport {\n onTrack,\n type Subscriber,\n type TrackableSource,\n} from \"./signals/context.js\";\nimport { Template, type InterpolationPlugin } from \"./template.js\";\nimport { isSignal, type Reactive } from \"./signals/index.js\";\n\n/** Reactive source type - TrackableSource may or may not be subscribable */\ntype SubscribableSource = Reactive<unknown>;\n\n/**\n * Opaque handle representing settled content from an async generator.\n *\n * When an async generator restarts due to signal changes, it receives the\n * previous settled content as its first argument. Return this value to\n * preserve the existing DOM instead of re-rendering.\n *\n * @example\n * ```ts\n * import asyncPlugin, { type RenderedContent } from \"balises/async\";\n *\n * async function* loadUser(\n * settled?: RenderedContent,\n * ctx?: AsyncGeneratorContext<{ lastId?: number }>,\n * ) {\n * const id = userId.value; // Track dependency\n * const previous = ctx?.lastId;\n * ctx && (ctx.lastId = id);\n *\n * if (settled) {\n * // Restart: update state, keep existing DOM\n * const user = await fetchUser(id);\n * state.user = user; // Triggers surgical updates via reactive bindings\n * return settled; // Preserve DOM\n * }\n *\n * // First load\n * yield html`<div class=\"skeleton\">...</div>`;\n * const user = await fetchUser(id);\n * state.user = user;\n * return UserCard({ state });\n * }\n * ```\n */\nexport interface RenderedContent {\n /** @internal Brand to prevent construction outside the library */\n readonly __brand: \"RenderedContent\";\n}\n\n/**\n * Mutable context object that persists across async generator restarts.\n */\nexport type AsyncGeneratorContext<T extends object = Record<string, unknown>> =\n T;\n\n/** Internal structure for RenderedContent */\ninterface RenderedContentInternal extends RenderedContent {\n readonly nodes: Node[];\n readonly childDisposers: (() => void)[];\n}\n\n/** Async generator function type */\ntype AsyncGenFn = (\n settled?: RenderedContent,\n ctx?: AsyncGeneratorContext,\n) => AsyncGenerator<unknown, unknown, unknown>;\n\n/**\n * Check if a value is an async generator function.\n */\nfunction isAsyncGeneratorFunction(\n value: unknown,\n): value is AsyncGeneratorFunction {\n if (typeof value !== \"function\") return false;\n const constructor = value.constructor;\n return (\n constructor &&\n (constructor.name === \"AsyncGeneratorFunction\" ||\n // Check prototype chain for async generator\n Object.prototype.toString.call(constructor.prototype) ===\n \"[object AsyncGeneratorFunction]\")\n );\n}\n\n/**\n * Render content and insert nodes before marker.\n * Handles Templates and primitives.\n */\nfunction insertContent(\n marker: Comment,\n value: unknown,\n nodes: Node[],\n disposers: (() => void)[],\n): void {\n const parent = marker.parentNode!;\n\n for (const item of Array.isArray(value) ? value : [value]) {\n if (item instanceof Template) {\n const { fragment, dispose } = item.render();\n disposers.push(dispose);\n nodes.push(...fragment.childNodes);\n parent.insertBefore(fragment, marker);\n } else if (item != null && typeof item !== \"boolean\") {\n const node = document.createTextNode(String(item));\n nodes.push(node);\n parent.insertBefore(node, marker);\n }\n }\n}\n\n/**\n * Plugin that handles async generator functions.\n * Auto-detects `async function*` without needing a wrapper.\n */\nconst asyncPlugin: InterpolationPlugin = (value) => {\n if (!isAsyncGeneratorFunction(value)) return null;\n\n return (marker, disposers) => {\n bindAsyncGenerator(value as AsyncGenFn, marker, disposers);\n };\n};\n\nexport default asyncPlugin;\n\n/** Result of tracking dependencies during a function call */\ninterface TrackResult<T> {\n value: T;\n subscribe: (callback: Subscriber) => void;\n unsubscribe: () => void;\n}\n\n/**\n * Track reactive dependencies accessed during a function call.\n * Sets up the onTrack hook temporarily to capture signal/computed accesses.\n */\nfunction track<T>(fn: () => T): TrackResult<T> {\n const sources = new Set<TrackableSource>();\n const prevHook = onTrack.current;\n onTrack.current = (source) => sources.add(source);\n\n let value: T;\n try {\n value = fn();\n } finally {\n onTrack.current = prevHook;\n }\n\n let unsubscribers: (() => void)[] = [];\n let subscribed = false;\n\n return {\n value,\n subscribe: (callback: Subscriber) => {\n if (subscribed) return;\n subscribed = true;\n for (const source of sources) {\n // Only subscribe to actual signals/computeds (not selector slots)\n if (isSignal(source)) {\n unsubscribers.push(\n (source as SubscribableSource).subscribe(callback),\n );\n }\n }\n },\n unsubscribe: () => {\n for (const unsub of unsubscribers) unsub();\n unsubscribers = [];\n subscribed = false;\n },\n };\n}\n\n/**\n * Bind an async generator function to a marker position.\n * Tracks signal dependencies during generator execution and restarts\n * the generator when those dependencies change.\n */\nfunction bindAsyncGenerator(\n genFn: AsyncGenFn,\n marker: Comment,\n disposers: (() => void)[],\n): void {\n let generator: AsyncGenerator<unknown> | null = null;\n let currentNodes: Node[] = [];\n let childDisposers: (() => void)[] = [];\n let disposed = false;\n let iterationId = 0;\n let depUnsubscribers: (() => void)[] = [];\n let lastSettled: RenderedContentInternal | null = null;\n const context: AsyncGeneratorContext = {};\n\n const clearNodes = () => {\n for (let i = 0; i < childDisposers.length; i++) childDisposers[i]!();\n for (let i = 0; i < currentNodes.length; i++)\n currentNodes[i]!.parentNode?.removeChild(currentNodes[i]!);\n childDisposers = [];\n currentNodes = [];\n };\n\n const clearDeps = () => {\n for (let i = 0; i < depUnsubscribers.length; i++) depUnsubscribers[i]!();\n depUnsubscribers = [];\n };\n\n const cleanupGenerator = () => {\n clearDeps();\n if (generator) {\n generator.return(undefined);\n generator = null;\n }\n };\n\n const cleanup = () => {\n cleanupGenerator();\n clearNodes();\n };\n\n const render = (value: unknown) => {\n clearNodes();\n insertContent(marker, value, currentNodes, childDisposers);\n };\n\n const runGenerator = async () => {\n const thisIteration = ++iterationId;\n cleanupGenerator();\n\n if (disposed) return;\n\n generator = genFn(lastSettled ?? undefined, context);\n let lastYielded: unknown = null;\n\n while (!disposed && thisIteration === iterationId) {\n let result: IteratorResult<unknown>;\n\n try {\n const tracked = track(() => generator!.next());\n\n tracked.subscribe(() => {\n if (!disposed && thisIteration === iterationId) {\n void runGenerator();\n }\n });\n depUnsubscribers.push(tracked.unsubscribe);\n\n result = await tracked.value;\n } catch (e) {\n cleanup();\n if (!disposed) throw e;\n return;\n }\n\n if (thisIteration !== iterationId) return;\n\n const { value, done } = result;\n\n if (done) {\n if (value === lastSettled && lastSettled !== null) {\n currentNodes = lastSettled.nodes;\n childDisposers = lastSettled.childDisposers;\n } else {\n render(value !== undefined ? value : lastYielded);\n lastSettled = {\n __brand: \"RenderedContent\" as const,\n nodes: currentNodes,\n childDisposers: childDisposers,\n };\n }\n return;\n }\n\n lastYielded = value;\n render(value);\n }\n };\n\n void runGenerator();\n\n disposers.push(() => {\n disposed = true;\n cleanup();\n lastSettled = null;\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "balises",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "A very simple reactive html templating library",
|
|
5
5
|
"author": "Julien Elbaz <elbywan@hotmail.com>",
|
|
6
6
|
"type": "module",
|
|
@@ -98,6 +98,6 @@
|
|
|
98
98
|
"typescript-eslint": "8.50.1"
|
|
99
99
|
},
|
|
100
100
|
"dependencies": {
|
|
101
|
-
"balises": "0.8.
|
|
101
|
+
"balises": "0.8.3"
|
|
102
102
|
}
|
|
103
103
|
}
|