@async/framework 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +124 -7
- package/examples/cache/index.html +1 -1
- package/examples/components/index.html +1 -1
- package/examples/components/main.js +2 -2
- package/examples/counter/index.html +1 -1
- package/examples/partials/index.html +1 -1
- package/examples/product/index.html +1 -1
- package/examples/product/main.js +2 -2
- package/examples/router/index.html +1 -1
- package/examples/server-call/index.html +1 -1
- package/examples/ssr/index.html +1 -1
- package/examples/streaming/index.html +1 -1
- package/framework.d.ts +569 -0
- package/framework.js +325 -101
- package/framework.min.js +3648 -0
- package/framework.ts +3 -0
- package/framework.umd.js +4158 -0
- package/framework.umd.min.js +3671 -0
- package/package.json +34 -5
- package/src/app.js +2 -2
- package/src/async-signal.js +12 -1
- package/src/cache.js +5 -0
- package/src/component.js +80 -16
- package/src/handlers.js +12 -4
- package/src/index.js +1 -1
- package/src/loader.js +93 -9
- package/src/partials.js +5 -0
- package/src/registry-store.js +4 -0
- package/src/router.js +11 -2
- package/src/server.js +26 -6
- package/src/signals.js +16 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-06-17
|
|
4
|
+
|
|
5
|
+
- Added `Loader` as the canonical public loader factory, including
|
|
6
|
+
`Async.Loader(...)` for UMD script-tag usage, while keeping `AsyncLoader` as
|
|
7
|
+
a compatibility alias.
|
|
8
|
+
- Added generated root CDN artifacts: `framework.min.js`, `framework.umd.js`,
|
|
9
|
+
`framework.umd.min.js`, `framework.ts`, and `framework.d.ts`.
|
|
10
|
+
- Added package exports and docs for ESM, compact ESM, UMD, compact UMD, and
|
|
11
|
+
TypeScript source/types entrypoints.
|
|
12
|
+
- Added exported helpers to the UMD-only `globalThis.Async` object for
|
|
13
|
+
script-tag CDN usage while keeping ESM `Async` as the app hub export.
|
|
14
|
+
- Added UMD namespace conflict checks so generated helpers cannot silently
|
|
15
|
+
overwrite app-hub fields such as `use`, `start`, or `registry`.
|
|
16
|
+
- Added `registry:lint`, a cached package linter that emits a local registry
|
|
17
|
+
manifest and detects conflicting signal, handler, server, partial, route, or
|
|
18
|
+
component declarations while skipping generated root bundles.
|
|
19
|
+
|
|
20
|
+
## 0.5.0 - 2026-06-17
|
|
21
|
+
|
|
22
|
+
- Added `this.suspense(signalRef, views)` for component-owned async boundary
|
|
23
|
+
templates without adding a wrapper, rerender loop, hydration, or promise
|
|
24
|
+
throwing.
|
|
25
|
+
- Added `signal:prop:*` property bindings and tests for inline signal refs in
|
|
26
|
+
`signal:text`, `signal:attr:*`, and `signal:prop:*`.
|
|
27
|
+
- Added explicit `unregister(id)` APIs to runtime registries and component
|
|
28
|
+
cleanup for scoped signals, async signals, computed signals, and handlers.
|
|
29
|
+
- Added boundary swap cleanup for mounted component fragments and old DOM
|
|
30
|
+
bindings.
|
|
31
|
+
- Added server-call normalization for async signals, including returned signal
|
|
32
|
+
effects, proxy abort propagation, and stable server error messages.
|
|
33
|
+
- Added `prevent` as a command-event alias for `preventDefault`.
|
|
34
|
+
|
|
3
35
|
## 0.4.0 - 2026-06-17
|
|
4
36
|
|
|
5
37
|
- Added a generated root `framework.js` ESM bundle for UNPKG browser imports.
|
package/README.md
CHANGED
|
@@ -90,9 +90,18 @@ and package lifecycle tooling. Browser consumers import ESM directly.
|
|
|
90
90
|
|
|
91
91
|
## CDN
|
|
92
92
|
|
|
93
|
-
The package ships
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
The package ships root CDN artifacts for UNPKG and can be loaded without a
|
|
94
|
+
build step. Use `@latest` for quick prototypes, and pin an exact version in
|
|
95
|
+
production:
|
|
96
|
+
|
|
97
|
+
| File | Format | Use |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `framework.js` | ESM | Readable browser module bundle |
|
|
100
|
+
| `framework.min.js` | ESM | Compact browser module bundle |
|
|
101
|
+
| `framework.umd.js` | UMD | Readable script-tag/CommonJS-style bundle |
|
|
102
|
+
| `framework.umd.min.js` | UMD | Compact script-tag/CommonJS-style bundle and default CDN file |
|
|
103
|
+
| `framework.ts` | TypeScript source facade | TS-aware runtimes and higher-layer tooling |
|
|
104
|
+
| `framework.d.ts` | Type declarations | TypeScript declarations for the public API |
|
|
96
105
|
|
|
97
106
|
```html
|
|
98
107
|
<main async:container>
|
|
@@ -121,6 +130,29 @@ version in production:
|
|
|
121
130
|
</script>
|
|
122
131
|
```
|
|
123
132
|
|
|
133
|
+
For a plain script tag, use the UMD bundle. In this UMD-only global form,
|
|
134
|
+
`globalThis.Async` is the app hub plus the exported helper functions, with
|
|
135
|
+
`globalThis.AsyncFramework` kept as an alias. Lower-level bootloader code can
|
|
136
|
+
call `Async.Loader(...)` directly.
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<script src="https://unpkg.com/@async/framework@latest/framework.umd.min.js"></script>
|
|
140
|
+
<script>
|
|
141
|
+
Async.use({
|
|
142
|
+
signal: {
|
|
143
|
+
count: Async.createSignal(0)
|
|
144
|
+
},
|
|
145
|
+
handler: {
|
|
146
|
+
increment() {
|
|
147
|
+
this.signals.update("count", (count) => count + 1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
Async.start({ root: document });
|
|
153
|
+
</script>
|
|
154
|
+
```
|
|
155
|
+
|
|
124
156
|
You can also use an import map so app code imports `@async/framework` by name:
|
|
125
157
|
|
|
126
158
|
```html
|
|
@@ -157,8 +189,8 @@ You can also use an import map so app code imports `@async/framework` by name:
|
|
|
157
189
|
|
|
158
190
|
```js
|
|
159
191
|
import {
|
|
160
|
-
AsyncLoader,
|
|
161
192
|
Async,
|
|
193
|
+
Loader,
|
|
162
194
|
attributeName,
|
|
163
195
|
asyncSignal,
|
|
164
196
|
createApp,
|
|
@@ -188,6 +220,9 @@ import {
|
|
|
188
220
|
} from "@async/framework";
|
|
189
221
|
```
|
|
190
222
|
|
|
223
|
+
`Loader` is the canonical loader factory. `AsyncLoader` remains as a
|
|
224
|
+
compatibility alias for older code.
|
|
225
|
+
|
|
191
226
|
### App Hub
|
|
192
227
|
|
|
193
228
|
`Async` is an exported app hub singleton. It is not installed on `globalThis`
|
|
@@ -253,6 +288,7 @@ Naming rules:
|
|
|
253
288
|
| `create*` | Runtime instance or mutable runtime primitive |
|
|
254
289
|
| `Async.use(...)` | App-level declaration registration |
|
|
255
290
|
| `registry.register(...)` | Low-level registration on a concrete runtime registry |
|
|
291
|
+
| `registry.unregister(...)` | Low-level removal from a concrete runtime registry |
|
|
256
292
|
|
|
257
293
|
Singular registry keys are canonical: `signal`, `handler`, `server`,
|
|
258
294
|
`partial`, `route`, `component`, and nested `cache.browser` / `cache.server`.
|
|
@@ -314,6 +350,7 @@ signals.set("count", 1);
|
|
|
314
350
|
signals.update("count", (count) => count + 1);
|
|
315
351
|
signals.subscribe("count", (count) => console.log(count));
|
|
316
352
|
signals.ref("count").value;
|
|
353
|
+
signals.unregister("count");
|
|
317
354
|
```
|
|
318
355
|
|
|
319
356
|
Initializer maps are supported:
|
|
@@ -376,7 +413,7 @@ reruns and the previous run is aborted.
|
|
|
376
413
|
|
|
377
414
|
## HTML Protocol
|
|
378
415
|
|
|
379
|
-
|
|
416
|
+
Loader scans regular HTML attributes:
|
|
380
417
|
|
|
381
418
|
| Attribute | Behavior |
|
|
382
419
|
| --- | --- |
|
|
@@ -389,6 +426,7 @@ AsyncLoader scans regular HTML attributes:
|
|
|
389
426
|
| `signal:text="product.title"` | Text binding |
|
|
390
427
|
| `signal:value="productId"` | Form value binding with writeback |
|
|
391
428
|
| `signal:attr:disabled="product.$loading"` | Attribute binding |
|
|
429
|
+
| `signal:prop:checked="selected"` | DOM property binding |
|
|
392
430
|
| `class:selected="selected"` | Class toggle from a signal path |
|
|
393
431
|
| `signal:class="buttonClasses"` | Class set from a signal value: string, object, or array |
|
|
394
432
|
| `async:boundary="product"` | Async or streamed replacement boundary |
|
|
@@ -428,6 +466,24 @@ Async.start({
|
|
|
428
466
|
That maps to `data-async-container`, `data-on-click="save"`,
|
|
429
467
|
`data-signal-text="product.title"`, and `data-class-selected="selected"`.
|
|
430
468
|
|
|
469
|
+
Inside `html` templates, signal refs can be passed directly to binding
|
|
470
|
+
attributes:
|
|
471
|
+
|
|
472
|
+
```js
|
|
473
|
+
const title = this.signal("Keyboard");
|
|
474
|
+
const disabled = this.signal(false);
|
|
475
|
+
const checked = this.signal(true);
|
|
476
|
+
|
|
477
|
+
return html`
|
|
478
|
+
<h1 signal:text="${title}"></h1>
|
|
479
|
+
<button signal:attr:disabled="${disabled}">Save</button>
|
|
480
|
+
<input type="checkbox" signal:prop:checked="${checked}">
|
|
481
|
+
`;
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Use `signal:value` for form value binding with writeback. Use `signal:prop:*`
|
|
485
|
+
when you only need one-way DOM property updates.
|
|
486
|
+
|
|
431
487
|
Named class toggles use their own top-level namespace:
|
|
432
488
|
|
|
433
489
|
```html
|
|
@@ -518,6 +574,7 @@ Plain commands resolve through the handler registry. Built-ins are registered by
|
|
|
518
574
|
default:
|
|
519
575
|
|
|
520
576
|
```txt
|
|
577
|
+
prevent
|
|
521
578
|
preventDefault
|
|
522
579
|
stopPropagation
|
|
523
580
|
stopImmediatePropagation
|
|
@@ -584,6 +641,12 @@ await server.cart.add("sku-1", 2);
|
|
|
584
641
|
|
|
585
642
|
Server responses can include `value`, `signals`, `boundary`, `html`, `redirect`,
|
|
586
643
|
or `error`. Signal patches are applied before boundary swaps and redirects.
|
|
644
|
+
Namespace calls such as `server.cart.add(...)` return the unwrapped `value`.
|
|
645
|
+
|
|
646
|
+
When an async signal calls a server namespace function, the framework passes the
|
|
647
|
+
active abort signal through proxy calls. Returned server effects such as
|
|
648
|
+
`signals`, `cache.browser`, `boundary/html`, and `redirect` are applied before
|
|
649
|
+
the async signal stores the unwrapped `value`.
|
|
587
650
|
|
|
588
651
|
### Router And Partials
|
|
589
652
|
|
|
@@ -764,7 +827,7 @@ createApp(browserApp, {
|
|
|
764
827
|
## Components
|
|
765
828
|
|
|
766
829
|
Components are scoped fragment functions. They return strings or `html`
|
|
767
|
-
templates;
|
|
830
|
+
templates; Loader inserts and scans the result. There is no virtual node
|
|
768
831
|
type and no rerender loop.
|
|
769
832
|
|
|
770
833
|
```js
|
|
@@ -794,7 +857,7 @@ const Toggle = defineComponent(function Toggle() {
|
|
|
794
857
|
`;
|
|
795
858
|
});
|
|
796
859
|
|
|
797
|
-
const loader =
|
|
860
|
+
const loader = Loader({ root: document });
|
|
798
861
|
loader.mount(document.querySelector("#app"), Toggle);
|
|
799
862
|
```
|
|
800
863
|
|
|
@@ -812,10 +875,55 @@ Component helpers:
|
|
|
812
875
|
| `this.handler(name, fn)` | Scoped named handler registry entry |
|
|
813
876
|
| `this.handler(fn)` | Generated scoped handler registry entry |
|
|
814
877
|
| `this.render(Component, props)` | Child fragment rendering |
|
|
878
|
+
| `this.suspense(signalRef, views)` | Async boundary template helper |
|
|
815
879
|
| `this.on(event, fn)` | Fragment lifecycle fallback for `attach`, `visible`, and `destroy` |
|
|
816
880
|
| `this.onMount(fn)` | Compatibility alias for `this.on("attach", fn)` |
|
|
817
881
|
| `this.onVisible(fn)` | Compatibility alias for `this.on("visible", fn)` |
|
|
818
882
|
|
|
883
|
+
`this.suspense(...)` is sugar for Loader boundaries:
|
|
884
|
+
`asyncSignal + async:boundary + async:* templates`. It emits only templates. The
|
|
885
|
+
caller owns the boundary element, and the loader chooses the loading, ready, or
|
|
886
|
+
error template from the async signal status.
|
|
887
|
+
|
|
888
|
+
```js
|
|
889
|
+
const Product = defineComponent(function Product() {
|
|
890
|
+
const product = this.asyncSignal("product", async function () {
|
|
891
|
+
return this.server.products.get("sku-1");
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
return html`
|
|
895
|
+
<article async:boundary="${product.id}">
|
|
896
|
+
${this.suspense(product, {
|
|
897
|
+
loading() {
|
|
898
|
+
return html`<p>Loading...</p>`;
|
|
899
|
+
},
|
|
900
|
+
ready(product) {
|
|
901
|
+
return html`<h1 signal:text="${product.id}.title"></h1>`;
|
|
902
|
+
},
|
|
903
|
+
error(product) {
|
|
904
|
+
return html`<p signal:text="${product.id}.$error.message"></p>`;
|
|
905
|
+
}
|
|
906
|
+
})}
|
|
907
|
+
</article>
|
|
908
|
+
`;
|
|
909
|
+
});
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
The shorthand form treats the callback as the ready template:
|
|
913
|
+
|
|
914
|
+
```js
|
|
915
|
+
this.suspense(product, (product) => html`
|
|
916
|
+
<h1 signal:text="${product.id}.title"></h1>
|
|
917
|
+
`);
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
`this.suspense(...)` is not React Suspense. It does not throw promises,
|
|
921
|
+
hydrate, diff, rerender a component tree, or emit a wrapper element.
|
|
922
|
+
|
|
923
|
+
Component-scoped signals and handlers are unregistered when the mounted
|
|
924
|
+
fragment is destroyed. `loader.swap(...)` cleans up old DOM bindings and mounted
|
|
925
|
+
component fragments under the swapped boundary before inserting the new HTML.
|
|
926
|
+
|
|
819
927
|
Put component lifecycle on the component root element when there is one:
|
|
820
928
|
|
|
821
929
|
```js
|
|
@@ -894,10 +1002,19 @@ Useful commands:
|
|
|
894
1002
|
```bash
|
|
895
1003
|
pnpm run pipeline:verify
|
|
896
1004
|
pnpm run pipeline:pages
|
|
1005
|
+
pnpm run registry:lint
|
|
897
1006
|
pnpm run pipeline:release:doctor
|
|
898
1007
|
pnpm run release:check
|
|
899
1008
|
```
|
|
900
1009
|
|
|
1010
|
+
`registry:lint` scans package source and examples for declared registry ids
|
|
1011
|
+
such as signals, handlers, server functions, partials, routes, and components.
|
|
1012
|
+
It writes `.async/registry-manifest.json` plus a per-file cache at
|
|
1013
|
+
`.async/registry-lint-cache.json`, skips generated root bundles such as
|
|
1014
|
+
`framework.umd.min.js`, and fails only when the same registry type and id are
|
|
1015
|
+
declared with different normalized content. Duplicate declarations with the
|
|
1016
|
+
same content are reported as dedupe candidates, not errors.
|
|
1017
|
+
|
|
901
1018
|
GitHub Pages builds through the generated `pages` job. This private repository
|
|
902
1019
|
needs GitHub Pages support enabled before the generated job can deploy.
|
|
903
1020
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Loader, defineComponent, html } from "../../src/index.js";
|
|
2
2
|
|
|
3
3
|
const Toggle = defineComponent(function Toggle() {
|
|
4
4
|
const selected = this.signal(false);
|
|
@@ -22,5 +22,5 @@ const Toggle = defineComponent(function Toggle() {
|
|
|
22
22
|
`;
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
const loader =
|
|
25
|
+
const loader = Loader({ root: document });
|
|
26
26
|
loader.mount(document.querySelector("#app"), Toggle);
|
package/examples/product/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Loader, createSignal, createSignalRegistry, delay } from "../../src/index.js";
|
|
2
2
|
|
|
3
3
|
const products = {
|
|
4
4
|
"sku-1": {
|
|
@@ -21,4 +21,4 @@ signals.asyncSignal("product", async function () {
|
|
|
21
21
|
return products[id];
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Loader({ root: document.body, signals }).start();
|
package/examples/ssr/index.html
CHANGED