@async/framework 0.2.2 → 0.4.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 - 2026-06-17
4
+
5
+ - Added a generated root `framework.js` ESM bundle for UNPKG browser imports.
6
+ - Expanded the README with Async layer definitions and an htmx comparison.
7
+ - Added `on:attach` as the canonical component attach lifecycle pseudo-event
8
+ with `on:mount` kept as a compatibility alias.
9
+ - Added top-level `class:*` bindings, including aggregate `class:`
10
+ string/object/array class sets.
11
+ - Added inline `html` template bindings for signal refs, class arrays/objects,
12
+ `value="${signalRef}"`, and generated component handlers via
13
+ `this.handler(fn)`.
14
+ - Added generated component-local signals via `this.signal(initial)`.
15
+
16
+ ## 0.3.0 - 2026-06-17
17
+
18
+ - Added a shared registry store behind `Async`, app runtimes, and concrete
19
+ registries so apps can inspect signals, handlers, server ids, routes,
20
+ partials, components, and split cache state from one place.
21
+ - Added configurable HTML attribute prefixes, with `async:*`, `signal:*`, and
22
+ `on:*` as the defaults plus explicit support for `data-async-*`,
23
+ `data-signal-*`, and `data-on-*`.
24
+ - Declared the UNPKG package entry explicitly so the package root can be used as
25
+ a no-build browser ESM CDN import.
26
+ - Documented the UNPKG import-map setup for importing `@async/framework` by
27
+ package name in no-build browser apps.
28
+
3
29
  ## 0.2.2 - 2026-06-17
4
30
 
5
31
  - Fixed release doctor validation to accept the pipeline-generated GitHub
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # @async/framework
2
2
 
3
- Layer 1 AsyncLoader plus small Layer 2 app, routing, server, cache, and SSR
4
- primitives for no-build web apps: signals, async signals, delegated command
5
- events, scoped fragment components, server calls, route partials, and
6
- out-of-order boundary swaps without a virtual DOM.
3
+ Async is a layered framework plan that starts as a no-build browser bootloader:
4
+ signals, async signals, delegated command events, scoped fragment components,
5
+ server calls, route partials, and out-of-order boundary swaps without a virtual
6
+ DOM.
7
7
 
8
8
  ```bash
9
9
  pnpm add @async/framework
10
10
  ```
11
11
 
12
12
  ```html
13
- <main data-async-container>
13
+ <main async:container>
14
14
  <button type="button" on:click="decrement">-</button>
15
- <strong data-async-text="count"></strong>
15
+ <strong signal:text="count"></strong>
16
16
  <button type="button" on:click="increment">+</button>
17
17
  </main>
18
18
  <script type="module" src="./main.js"></script>
@@ -43,10 +43,10 @@ Async.start({ root: document });
43
43
 
44
44
  ## What It Is
45
45
 
46
- `@async/framework` is the browser bootloader layer for Async apps. It keeps the
47
- runtime small and explicit:
46
+ `@async/framework` is the Layer 1 runtime plus the first Layer 2 app/server
47
+ primitives. It keeps the runtime small and explicit:
48
48
 
49
- - No build step for consumers.
49
+ - No build step for Layer 1 consumers.
50
50
  - No virtual DOM, diff path, hydration runtime, or component rerender loop.
51
51
  - Signals are the state boundary.
52
52
  - `Async.use(...)` registers app declarations before or after startup.
@@ -57,8 +57,27 @@ runtime small and explicit:
57
57
  - Boundaries can be swapped out of order and rescanned, which keeps server
58
58
  streaming and partial HTML replacement simple.
59
59
 
60
- Higher layers can still add JSX lowering, chunk manifests, server compilation,
61
- or resumability metadata later. Layer 1 stays plain HTML plus ESM.
60
+ Higher layers can add JSX lowering, TypeScript, chunk manifests, compiler-owned
61
+ server/client splits, and intent-first authoring later. They should compile down
62
+ to the same runtime registries and HTML protocol.
63
+
64
+ ## Layers
65
+
66
+ Async is designed as layers, so each level can stay useful without forcing the
67
+ next level on every app.
68
+
69
+ | Layer | Name | Requirement | Purpose |
70
+ | --- | --- | --- | --- |
71
+ | 1 | Runtime bootloader | No build. CDN or direct ESM import. | Signals, async signals, handlers, command events, lifecycle pseudo-events, scoped fragments, and boundary swaps. |
72
+ | 2 | App/server layer | Light server integration. No app compiler required. | `Async.use(...)`, router modes, server function proxy, partial registry, SSR output, browser activation, and split browser/server cache. |
73
+ | 3 | Authoring build | Build step required. | JSX, ESM, and TypeScript authoring that lowers into Layer 1 HTML attributes and Layer 2 registries. |
74
+ | 4 | Chunk and resumability metadata | Build metadata required. | Lazy module manifests, visibility/prefetch hints, resource graphs, and resumability records that the bootloader can consume. |
75
+ | 5 | Framework compiler | Compiler required. | Server/client partitioning, code motion, optimized registry generation, serialized closures, and deeper resumability transforms. |
76
+ | 6 | TSRX and intent layer | Higher-level compiler required. | More declarative author intent, AI/compiler-friendly metadata, and source forms that generate lower-layer Async apps. |
77
+
78
+ The package in this repository intentionally focuses on Layers 1 and 2. Layers
79
+ 3 through 6 are higher authoring surfaces, not extra runtime requirements for
80
+ plain HTML apps.
62
81
 
63
82
  ## Install
64
83
 
@@ -69,12 +88,78 @@ pnpm add @async/framework
69
88
  The package is ESM-only and supports Node.js 24 and newer for tests, examples,
70
89
  and package lifecycle tooling. Browser consumers import ESM directly.
71
90
 
91
+ ## CDN
92
+
93
+ The package ships a root `framework.js` ESM bundle for UNPKG and can be loaded
94
+ without a build step. Use `@latest` for quick prototypes, and pin an exact
95
+ version in production:
96
+
97
+ ```html
98
+ <main async:container>
99
+ <button type="button" on:click="increment">+</button>
100
+ <strong signal:text="count"></strong>
101
+ </main>
102
+
103
+ <script type="module">
104
+ import {
105
+ Async,
106
+ createSignal
107
+ } from "https://unpkg.com/@async/framework@latest/framework.js";
108
+
109
+ Async.use({
110
+ signal: {
111
+ count: createSignal(0)
112
+ },
113
+ handler: {
114
+ increment() {
115
+ this.signals.update("count", (count) => count + 1);
116
+ }
117
+ }
118
+ });
119
+
120
+ Async.start({ root: document });
121
+ </script>
122
+ ```
123
+
124
+ You can also use an import map so app code imports `@async/framework` by name:
125
+
126
+ ```html
127
+ <script type="importmap">
128
+ {
129
+ "imports": {
130
+ "@async/framework": "https://unpkg.com/@async/framework@latest/framework.js"
131
+ }
132
+ }
133
+ </script>
134
+
135
+ <script type="module">
136
+ import {
137
+ Async,
138
+ createSignal
139
+ } from "@async/framework";
140
+
141
+ Async.use({
142
+ signal: {
143
+ count: 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
+
72
156
  ## Core API
73
157
 
74
158
  ```js
75
159
  import {
76
160
  AsyncLoader,
77
161
  Async,
162
+ attributeName,
78
163
  asyncSignal,
79
164
  createApp,
80
165
  createCacheRegistry,
@@ -84,11 +169,13 @@ import {
84
169
  createSignal,
85
170
  createHandlerRegistry,
86
171
  createPartialRegistry,
172
+ createRegistryStore,
87
173
  createRouteRegistry,
88
174
  createRouter,
89
175
  createServerProxy,
90
176
  createServerRegistry,
91
177
  createSignalRegistry,
178
+ defineAttributeConfig,
92
179
  defineApp,
93
180
  defineCache,
94
181
  defineComponent,
@@ -170,6 +257,50 @@ Naming rules:
170
257
  Singular registry keys are canonical: `signal`, `handler`, `server`,
171
258
  `partial`, `route`, `component`, and nested `cache.browser` / `cache.server`.
172
259
 
260
+ ### Registry Inspection
261
+
262
+ `Async.registry` is the global inspection surface for registered app pieces.
263
+ Every runtime and concrete registry also points at the same backing store:
264
+
265
+ ```js
266
+ Async.registry.keys("signal");
267
+ Async.registry.entries("route");
268
+ Async.registry.snapshot();
269
+
270
+ const runtime = Async.start({ root: document });
271
+
272
+ runtime.registry.keys("handler");
273
+ runtime.signals.registry === runtime.registry;
274
+ runtime.browser.cache.registry === runtime.registry;
275
+ ```
276
+
277
+ Supported inspection types:
278
+
279
+ ```txt
280
+ signal
281
+ handler
282
+ server
283
+ partial
284
+ route
285
+ component
286
+ cache.browser
287
+ cache.server
288
+ cache.browser.entries
289
+ cache.server.entries
290
+ ```
291
+
292
+ Browser runtime inspection exposes server ids as descriptors, not executable
293
+ server functions, and does not expose server cache contents:
294
+
295
+ ```js
296
+ runtime.registry.keys("server");
297
+ runtime.registry.get("server", "products.get");
298
+ // { id: "products.get", kind: "server" }
299
+
300
+ runtime.registry.snapshot().entries.server;
301
+ // {}
302
+ ```
303
+
173
304
  ### Signals
174
305
 
175
306
  ```js
@@ -249,37 +380,130 @@ AsyncLoader scans regular HTML attributes:
249
380
 
250
381
  | Attribute | Behavior |
251
382
  | --- | --- |
252
- | `data-async-container` | Marks a scannable app root |
383
+ | `async:container` | Marks a scannable app root |
253
384
  | `on:click="selectProduct"` | Delegated command event |
254
385
  | `on:submit="preventDefault; save"` | Sequential command chain |
255
386
  | `on:click="server.cart.add(productId)"` | Server command with signal args |
256
- | `data-async-text="product.title"` | Text binding |
257
- | `data-async-value="productId"` | Form value binding with writeback |
258
- | `data-async-attr:disabled="product.$loading"` | Attribute binding |
259
- | `data-async-class:selected="selected"` | Class toggle |
260
- | `data-async-boundary="product"` | Async or streamed replacement boundary |
261
- | `data-async-loading="product"` | Boundary loading template |
262
- | `data-async-ready="product"` | Boundary ready template |
263
- | `data-async-error="product"` | Boundary error template |
387
+ | `on:attach="setup"` | Component root attach lifecycle pseudo-event |
388
+ | `on:visible="trackView"` | Component root visible lifecycle pseudo-event |
389
+ | `signal:text="product.title"` | Text binding |
390
+ | `signal:value="productId"` | Form value binding with writeback |
391
+ | `signal:attr:disabled="product.$loading"` | Attribute binding |
392
+ | `class:selected="selected"` | Class toggle from a signal path |
393
+ | `signal:class="buttonClasses"` | Class set from a signal value: string, object, or array |
394
+ | `async:boundary="product"` | Async or streamed replacement boundary |
395
+ | `async:loading="product"` | Boundary loading template |
396
+ | `async:ready="product"` | Boundary ready template |
397
+ | `async:error="product"` | Boundary error template |
264
398
 
265
399
  ```html
266
- <section data-async-boundary="product">
267
- <template data-async-loading="product">
400
+ <section async:boundary="product">
401
+ <template async:loading="product">
268
402
  <p>Loading...</p>
269
403
  </template>
270
- <template data-async-ready="product">
271
- <h1 data-async-text="product.title"></h1>
404
+ <template async:ready="product">
405
+ <h1 signal:text="product.title"></h1>
272
406
  </template>
273
- <template data-async-error="product">
274
- <p data-async-text="product.$error.message"></p>
407
+ <template async:error="product">
408
+ <p signal:text="product.$error.message"></p>
275
409
  </template>
276
410
  </section>
277
411
  ```
278
412
 
413
+ The default prefixes are `async:`, `signal:`, and `on:`. You can switch to
414
+ data attributes when a host needs that shape:
415
+
416
+ ```js
417
+ Async.start({
418
+ root: document,
419
+ attributes: {
420
+ async: "data-async-",
421
+ class: "data-class-",
422
+ signal: "data-signal-",
423
+ on: "data-on-"
424
+ }
425
+ });
426
+ ```
427
+
428
+ That maps to `data-async-container`, `data-on-click="save"`,
429
+ `data-signal-text="product.title"`, and `data-class-selected="selected"`.
430
+
431
+ Named class toggles use their own top-level namespace:
432
+
433
+ ```html
434
+ <button
435
+ class="button"
436
+ class:selected="selected"
437
+ >
438
+ Add
439
+ </button>
440
+ ```
441
+
442
+ Aggregate class binding uses `signal:class`. It reads the current signal value
443
+ and accepts strings, objects, and arrays:
444
+
445
+ ```js
446
+ Async.use({
447
+ signal: {
448
+ buttonClasses: createSignal([
449
+ "button-primary",
450
+ { selected: true, disabled: false },
451
+ ["compact"]
452
+ ])
453
+ }
454
+ });
455
+ ```
456
+
457
+ ```html
458
+ <button signal:class="buttonClasses">Add</button>
459
+ ```
460
+
461
+ Inside `html` templates, `signal:class` can also receive objects or arrays
462
+ directly. Signal refs inside the object or array are tracked:
463
+
464
+ ```js
465
+ const selected = this.signal("selected", false);
466
+ const tone = this.signal("tone", "primary");
467
+
468
+ return html`
469
+ <article signal:class="${["card", tone, { selected }]}"}>
470
+ ...
471
+ </article>
472
+ `;
473
+ ```
474
+
475
+ For component-local state that does not need a stable public id, omit the name.
476
+ The signal is still registered under the component scope:
477
+
478
+ ```js
479
+ const selected = this.signal(false);
480
+ const tone = this.signal("primary");
481
+
482
+ return html`
483
+ <article signal:class="${["card", selected, tone]}">
484
+ ...
485
+ </article>
486
+ `;
487
+ ```
488
+
489
+ `value="${signalRef}"` in an `html` template is equivalent to adding
490
+ `signal:value` for that signal. It writes back on input/change:
491
+
492
+ ```js
493
+ const productId = this.signal("productId", "sku-1");
494
+
495
+ return html`<input value="${productId}">`;
496
+ ```
497
+
498
+ `signal:class:selected="selected"` remains supported as a compatibility alias,
499
+ but new examples should use `class:selected`. The parser-safe top-level
500
+ aggregate form `class:="buttonClasses"` also remains supported.
501
+
279
502
  ### Command Events
280
503
 
281
- `on:*` works with any native DOM event name. `on:mount` and `on:visible` are
282
- reserved pseudo-events with cleanup support.
504
+ `on:*` works with any native DOM event name. `on:attach` and `on:visible` are
505
+ reserved component lifecycle pseudo-events with cleanup support. `on:mount`
506
+ remains as a compatibility alias for `on:attach`.
283
507
 
284
508
  Command chains use semicolons and are awaited sequentially:
285
509
 
@@ -417,13 +641,13 @@ Router modes:
417
641
  CSR startup can use an empty route boundary:
418
642
 
419
643
  ```html
420
- <main data-async-container>
644
+ <main async:container>
421
645
  <nav>
422
646
  <a href="/">Home</a>
423
647
  <a href="/products/sku-1">Product</a>
424
648
  </nav>
425
649
 
426
- <section data-async-boundary="route"></section>
650
+ <section async:boundary="route"></section>
427
651
  </main>
428
652
  ```
429
653
 
@@ -520,10 +744,10 @@ const response = await serverRuntime.render("/products/123");
520
744
  The returned HTML includes a route boundary plus a JSON snapshot:
521
745
 
522
746
  ```html
523
- <section data-async-boundary="route">
747
+ <section async:boundary="route">
524
748
  <!-- server-rendered route partial -->
525
749
  </section>
526
- <script type="application/json" data-async-snapshot>{}</script>
750
+ <script type="application/json" async:snapshot>{}</script>
527
751
  ```
528
752
 
529
753
  Browser activation scans the existing HTML and attaches events. It does not
@@ -545,21 +769,25 @@ type and no rerender loop.
545
769
 
546
770
  ```js
547
771
  const Toggle = defineComponent(function Toggle() {
548
- const selected = this.signal("selected", false);
549
- const toggle = this.handler("toggle", function () {
550
- selected.update((value) => !value);
772
+ const selected = this.signal(false);
773
+ const attach = this.handler("attach", function ({ element }) {
774
+ element.dataset.attached = "true";
551
775
  });
552
-
553
- this.onMount((target) => {
554
- target.dataset.mounted = "true";
776
+ const visible = this.handler("visible", function ({ element }) {
777
+ element.dataset.visible = "true";
555
778
  });
556
779
 
557
780
  return html`
558
781
  <button
559
782
  type="button"
560
- on:click="${toggle}"
561
- data-async-class:selected="${selected.id}"
562
- data-async-attr:aria-pressed="${selected.id}"
783
+ on:attach="${attach}"
784
+ on:visible="${visible}"
785
+ on:click="${this.handler(function () {
786
+ selected.update((value) => !value);
787
+ })}"
788
+ class:selected="${selected}"
789
+ signal:class="${["toggle", { active: selected }]}"
790
+ signal:attr:aria-pressed="${selected}"
563
791
  >
564
792
  Toggle
565
793
  </button>
@@ -576,17 +804,46 @@ Component helpers:
576
804
 
577
805
  | Helper | Behavior |
578
806
  | --- | --- |
579
- | `this.signal(name, initial)` | Scoped get-or-create signal |
807
+ | `this.signal(name, initial)` | Scoped named get-or-create signal |
808
+ | `this.signal(initial)` | Generated scoped local signal |
580
809
  | `this.computed(name, fn)` | Scoped computed signal |
581
810
  | `this.asyncSignal(name, fn)` | Scoped async signal |
582
811
  | `this.effect(fn)` | Scoped effect with cleanup |
583
- | `this.handler(name, fn)` | Scoped handler registry entry |
812
+ | `this.handler(name, fn)` | Scoped named handler registry entry |
813
+ | `this.handler(fn)` | Generated scoped handler registry entry |
584
814
  | `this.render(Component, props)` | Child fragment rendering |
585
- | `this.onMount(fn)` | One-shot mount hook |
586
- | `this.onVisible(fn)` | One-shot visibility hook |
815
+ | `this.on(event, fn)` | Fragment lifecycle fallback for `attach`, `visible`, and `destroy` |
816
+ | `this.onMount(fn)` | Compatibility alias for `this.on("attach", fn)` |
817
+ | `this.onVisible(fn)` | Compatibility alias for `this.on("visible", fn)` |
818
+
819
+ Put component lifecycle on the component root element when there is one:
820
+
821
+ ```js
822
+ const attach = this.handler("attach", function ({ element }) {
823
+ element.dataset.attached = "true";
824
+ });
825
+ const visible = this.handler("visible", function ({ element }) {
826
+ element.dataset.visible = "true";
827
+ });
587
828
 
588
- `on:mount` and `on:visible` are loader pseudo-events with cleanup support. They
589
- do not drive component rerenders.
829
+ return html`<article on:attach="${attach}" on:visible="${visible}">...</article>`;
830
+ ```
831
+
832
+ If a component returns text or multiple root nodes, use the scoped fallback:
833
+
834
+ ```js
835
+ this.on("attach", (target) => {
836
+ target.dataset.attached = "true";
837
+ });
838
+
839
+ this.on("destroy", () => {
840
+ // Clean up fragment-scoped resources.
841
+ });
842
+ ```
843
+
844
+ `on:visible` is defined as a component lifecycle pseudo-event. It runs once when
845
+ the component root first becomes visible. Lifecycle events do not drive
846
+ component rerenders.
590
847
 
591
848
  ## Streaming
592
849
 
@@ -597,7 +854,7 @@ loader.swap(
597
854
  "product",
598
855
  `
599
856
  <article>
600
- <h1 data-async-text="product.title"></h1>
857
+ <h1 signal:text="product.title"></h1>
601
858
  <button type="button" on:click="selectProduct">Select</button>
602
859
  </article>
603
860
  `
@@ -653,3 +910,22 @@ then runs release doctor.
653
910
  The core runtime is intentionally small. Bundling, lazy chunk manifests, JSX
654
911
  lowering, TSRX lowering, server resource compilation, and higher-level
655
912
  resumability metadata are deferred to later layers.
913
+
914
+ ## Async And htmx
915
+
916
+ Async and htmx are both HTML-first and avoid a virtual DOM, but they optimize
917
+ for different boundaries.
918
+
919
+ | Area | htmx | Async |
920
+ | --- | --- | --- |
921
+ | Primary model | HTML attributes issue HTTP requests and swap server responses. | HTML attributes bind signals, command events, server calls, and route boundaries. |
922
+ | State | Server-owned hypermedia state; browser state is intentionally minimal. | Browser signal registry plus server signal patches and cache snapshots. |
923
+ | Server interaction | DOM attributes describe HTTP verbs, targets, and swaps. | `server.*(...)` commands call registered server functions and apply returned effects. |
924
+ | Routing | Usually server navigation or htmx-boosted navigation. | CSR, SPA, SSR, SSR-SPA, and MPA router modes built around partial boundaries. |
925
+ | Components | Server-rendered HTML fragments. | Scoped fragment functions today; higher layers can compile JSX/TSRX later. |
926
+ | Build story | No build by default. | Layer 1 is no-build/CDN; higher layers can add build or compiler steps. |
927
+
928
+ Use htmx when the server should own most interaction through hypermedia and
929
+ HTTP swaps. Use Async when you want an HTML-first runtime that also has local
930
+ signals, async resources, registered browser/server handlers, route partials,
931
+ and a path to higher compiler layers without changing the Layer 1 protocol.
@@ -6,10 +6,10 @@
6
6
  <title>AsyncLoader Cache</title>
7
7
  </head>
8
8
  <body>
9
- <main data-async-container>
9
+ <main async:container>
10
10
  <button type="button" on:click="cacheDemo.loadProduct">Load product</button>
11
- <p>Product: <strong data-async-text="cacheDemo.title"></strong></p>
12
- <p>Server calls: <strong data-async-text="cacheDemo.calls"></strong></p>
11
+ <p>Product: <strong signal:text="cacheDemo.title"></strong></p>
12
+ <p>Server calls: <strong signal:text="cacheDemo.calls"></strong></p>
13
13
  </main>
14
14
  <script type="module" src="./main.js"></script>
15
15
  </body>
@@ -1,21 +1,21 @@
1
1
  import { AsyncLoader, defineComponent, html } from "../../src/index.js";
2
2
 
3
3
  const Toggle = defineComponent(function Toggle() {
4
- const selected = this.signal("selected", false);
5
- const toggle = this.handler("toggle", function () {
6
- selected.update((value) => !value);
7
- });
8
-
9
- this.onMount(() => {
10
- document.body.dataset.toggleMounted = "true";
4
+ const selected = this.signal(false);
5
+ const attach = this.handler("attach", function ({ element }) {
6
+ element.dataset.attached = "true";
11
7
  });
12
8
 
13
9
  return html`
14
10
  <button
15
11
  type="button"
16
- on:click="${toggle}"
17
- data-async-class:selected="${selected.id}"
18
- data-async-attr:aria-pressed="${selected.id}"
12
+ on:attach="${attach}"
13
+ on:click="${this.handler(function () {
14
+ selected.update((value) => !value);
15
+ })}"
16
+ class:selected="${selected}"
17
+ signal:class="${["toggle", { active: selected }]}"
18
+ signal:attr:aria-pressed="${selected}"
19
19
  >
20
20
  Toggle
21
21
  </button>
@@ -4,9 +4,9 @@
4
4
  <meta charset="utf-8">
5
5
  <title>AsyncLoader Counter</title>
6
6
  </head>
7
- <body data-async-container>
7
+ <body async:container>
8
8
  <main>
9
- <p>Count: <strong data-async-text="counter.count"></strong></p>
9
+ <p>Count: <strong signal:text="counter.count"></strong></p>
10
10
  <button type="button" on:click="counter.decrement">-</button>
11
11
  <button type="button" on:click="counter.increment">+</button>
12
12
  </main>
@@ -6,9 +6,9 @@
6
6
  <title>AsyncLoader Partials</title>
7
7
  </head>
8
8
  <body>
9
- <main data-async-container>
9
+ <main async:container>
10
10
  <button type="button" on:click="partialsDemo.loadProduct">Load product</button>
11
- <section data-async-boundary="product"></section>
11
+ <section async:boundary="product"></section>
12
12
  </main>
13
13
  <script type="module" src="./main.js"></script>
14
14
  </body>
@@ -4,26 +4,26 @@
4
4
  <meta charset="utf-8">
5
5
  <title>AsyncLoader Product</title>
6
6
  </head>
7
- <body data-async-container>
7
+ <body async:container>
8
8
  <main>
9
9
  <label>
10
10
  Product
11
- <select data-async-value="productId">
11
+ <select signal:value="productId">
12
12
  <option value="sku-1">Mechanical Keyboard</option>
13
13
  <option value="sku-2">Studio Headphones</option>
14
14
  </select>
15
15
  </label>
16
16
 
17
- <section data-async-boundary="product">
18
- <template data-async-loading="product">
17
+ <section async:boundary="product">
18
+ <template async:loading="product">
19
19
  <p>Loading product...</p>
20
20
  </template>
21
- <template data-async-ready="product">
22
- <h1 data-async-text="product.title"></h1>
23
- <p data-async-text="product.description"></p>
21
+ <template async:ready="product">
22
+ <h1 signal:text="product.title"></h1>
23
+ <p signal:text="product.description"></p>
24
24
  </template>
25
- <template data-async-error="product">
26
- <p data-async-text="product.$error.message"></p>
25
+ <template async:error="product">
26
+ <p signal:text="product.$error.message"></p>
27
27
  </template>
28
28
  </section>
29
29
  </main>
@@ -6,12 +6,12 @@
6
6
  <title>AsyncLoader CSR Router</title>
7
7
  </head>
8
8
  <body>
9
- <main data-async-container>
9
+ <main async:container>
10
10
  <nav>
11
11
  <a href="/">Home</a>
12
12
  <a href="/products/sku-1">Product</a>
13
13
  </nav>
14
- <section data-async-boundary="route"></section>
14
+ <section async:boundary="route"></section>
15
15
  </main>
16
16
  <script type="module" src="./main.js"></script>
17
17
  </body>