@async/framework 0.9.0 → 0.10.1

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,6 +1,25 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.10.1 - 2026-06-17
4
+
5
+ - Added Terser-powered browser bundle minification and pointed legacy
6
+ `module`/`browser` analyzer fields plus the root `exports.browser` condition
7
+ at `browser.min.js`.
8
+ - Bundle size from bundled TypeScript source: `browser.ts` 171,471 B raw /
9
+ 32,301 B gzip -> `browser.min.js` 72,753 B raw / 21,763 B gzip
10
+ (-98,718 B raw, -10,538 B gzip).
11
+
12
+ ## 0.10.0 - 2026-06-17
13
+
14
+ - Added rootless browser startup plus `attachRoot`, `detachRoot`,
15
+ `inspectRoots`, and streamed `applySnapshot(...)` support for advanced
16
+ build-step bootstrapping.
17
+ - Added compact lazy registry descriptors with `_async` asset resolution,
18
+ inferred exports, and lazy handler, partial, component, and async-signal
19
+ materialization.
20
+ - Added optional `async-container` and `async-suspense` custom elements while
21
+ preserving the existing `async:container`, `async:boundary`, and
22
+ `this.suspense(...)` Layer 1 APIs.
4
23
 
5
24
  ## 0.9.0 - 2026-06-17
6
25
 
package/README.md CHANGED
@@ -187,6 +187,117 @@ You can also use an import map so app code imports `@async/framework` by name:
187
187
  </script>
188
188
  ```
189
189
 
190
+ ## Advanced Build-Step Runtime
191
+
192
+ Layer 1 still works with no build step. A build step can optimize the same
193
+ runtime by emitting SSR HTML plus compact registry descriptors. The browser can
194
+ start in the document head, apply snapshots, and wait for a root to appear:
195
+
196
+ ```html
197
+ <script type="importmap">
198
+ {
199
+ "imports": {
200
+ "@async/framework": "https://unpkg.com/@async/framework@latest/browser.js"
201
+ }
202
+ }
203
+ </script>
204
+
205
+ <script type="application/json" async:snapshot>
206
+ {
207
+ "signal": {
208
+ "productId": "sku-1"
209
+ },
210
+ "handler": {
211
+ "cart.add": { "url": "cart.add.js" }
212
+ },
213
+ "component": {
214
+ "ProductCard": { "url": "ProductCard.js" }
215
+ },
216
+ "asyncSignal": {
217
+ "product.load": { "url": "product.load.js" }
218
+ }
219
+ }
220
+ </script>
221
+
222
+ <script type="module">
223
+ import {
224
+ Async,
225
+ defineAsyncContainerElement,
226
+ defineAsyncSuspenseElement,
227
+ readSnapshot
228
+ } from "@async/framework";
229
+
230
+ Async.start({
231
+ snapshot: readSnapshot(document),
232
+ registryAssets: { baseUrl: "_async" }
233
+ });
234
+
235
+ defineAsyncContainerElement();
236
+ defineAsyncSuspenseElement();
237
+ </script>
238
+ ```
239
+
240
+ `Async.start()` defaults to rootless browser startup. It creates registries,
241
+ applies snapshots, and prepares the scheduler/server proxy context without
242
+ scanning DOM. Attach a root later with `Async.attachRoot(root)` or by using
243
+ `<async-container>`:
244
+
245
+ ```html
246
+ <async-container>
247
+ <button type="button" on:click="cart.add">Add</button>
248
+ </async-container>
249
+ ```
250
+
251
+ Descriptor URLs are relative to a type folder under `registryAssets.baseUrl`.
252
+ The default is:
253
+
254
+ ```js
255
+ {
256
+ baseUrl: "_async",
257
+ paths: {
258
+ component: "component",
259
+ handler: "handler",
260
+ asyncSignal: "asyncSignal",
261
+ partial: "partial",
262
+ route: "route"
263
+ }
264
+ }
265
+ ```
266
+
267
+ So this descriptor:
268
+
269
+ ```json
270
+ { "url": "ProductCard.js#ProductCard" }
271
+ ```
272
+
273
+ resolves as:
274
+
275
+ ```txt
276
+ /_async/component/ProductCard.js#ProductCard
277
+ ```
278
+
279
+ If `#export` is omitted, Async tries the registry id leaf, then the file
280
+ basename, then `default`.
281
+
282
+ For declarative async boundaries, use `<async-suspense>` or keep using
283
+ `this.suspense(...)` inside components:
284
+
285
+ ```html
286
+ <async-suspense for="product.load">
287
+ <template loading>Loading...</template>
288
+ <template ready>
289
+ <h1 signal:text="product.load.title"></h1>
290
+ </template>
291
+ <template error>
292
+ <p signal:text="product.load.$error.message"></p>
293
+ </template>
294
+ </async-suspense>
295
+ ```
296
+
297
+ The build layer can hide `createBoundaryReceiver(...)` setup, but streaming is
298
+ still explicit boundary patches: boundary id, sequence number, HTML, signal
299
+ patches, and browser-cache patches. Async does not ship a component resume graph.
300
+
190
301
  ## Core API
191
302
 
192
303
  For npm consumers, `@async/framework` uses conditional exports: browser-aware
@@ -202,6 +313,7 @@ import {
202
313
  createApp,
203
314
  createCacheRegistry,
204
315
  createComponentRegistry,
316
+ createLazyRegistry,
205
317
  component,
206
318
  computed,
207
319
  createSignal,
@@ -213,10 +325,13 @@ import {
213
325
  createScheduler,
214
326
  createServerProxy,
215
327
  createSignalRegistry,
328
+ defineAsyncContainerElement,
329
+ defineAsyncSuspenseElement,
216
330
  defineAttributeConfig,
217
331
  defineApp,
218
332
  defineCache,
219
333
  defineComponent,
334
+ defineRegistrySnapshot,
220
335
  defineRoute,
221
336
  delay,
222
337
  effect,
package/browser.d.ts CHANGED
@@ -13,6 +13,7 @@ export type RegistryType =
13
13
  | "partial"
14
14
  | "route"
15
15
  | "component"
16
+ | "asyncSignal"
16
17
  | "cache.browser"
17
18
  | "cache.server"
18
19
  | "cache.browser.entries"
@@ -34,6 +35,20 @@ export interface NormalizedAttributeConfig {
34
35
 
35
36
  export type TemplatePrimitive = string | number | boolean | null | undefined;
36
37
  export type TemplateLike = TemplateResult | TemplatePrimitive | Node | TemplateLike[];
38
+ export interface LazyDescriptor {
39
+ url: string;
40
+ [key: string]: unknown;
41
+ }
42
+ export interface RegistryAssetsConfig {
43
+ baseUrl?: string;
44
+ paths?: Partial<Record<"component" | "handler" | "asyncSignal" | "partial" | "route", string>>;
45
+ }
46
+ export interface LazyRegistry {
47
+ registryAssets: Required<Pick<RegistryAssetsConfig, "baseUrl">> & { paths: Record<string, string> };
48
+ resolveUrl(type: "component" | "handler" | "asyncSignal" | "partial" | "route", id: string, descriptor: LazyDescriptor): { moduleUrl: string; exportNames: string[]; url: string };
49
+ resolve<T = unknown>(type: "component" | "handler" | "asyncSignal" | "partial" | "route", id: string, descriptor: LazyDescriptor): Promise<T>;
50
+ inspect(): { registryAssets: unknown; modules: string[]; exports: string[] };
51
+ }
37
52
 
38
53
  export interface TemplateResult {
39
54
  readonly strings: TemplateStringsArray;
@@ -209,8 +224,8 @@ export interface HandlerContext {
209
224
  export type HandlerFunction = (this: HandlerContext, context: HandlerContext) => MaybePromise<unknown>;
210
225
 
211
226
  export interface HandlerRegistry extends RegistryInspection<HandlerFunction> {
212
- register(id: string, fn: HandlerFunction): string;
213
- registerMany(map?: Record<string, HandlerFunction>): this;
227
+ register(id: string, fn: HandlerFunction | LazyDescriptor): string;
228
+ registerMany(map?: Record<string, HandlerFunction | LazyDescriptor>): this;
214
229
  unregister(id: string): boolean;
215
230
  resolve(id: string): HandlerFunction | undefined;
216
231
  run(ref: string, context?: Partial<HandlerContext>): Promise<unknown[]>;
@@ -322,8 +337,8 @@ export interface PartialContext {
322
337
  export type PartialFunction = (this: PartialContext, props: Record<string, unknown>) => MaybePromise<TemplateLike | ServerEnvelope>;
323
338
 
324
339
  export interface PartialRegistry extends RegistryInspection<PartialFunction> {
325
- register(id: string, fn: PartialFunction): string;
326
- registerMany(map?: Record<string, PartialFunction>): this;
340
+ register(id: string, fn: PartialFunction | LazyDescriptor): string;
341
+ registerMany(map?: Record<string, PartialFunction | LazyDescriptor>): this;
327
342
  unregister(id: string): boolean;
328
343
  resolve(id: string): PartialFunction | undefined;
329
344
  render(id: string, props?: Record<string, unknown>, context?: Partial<PartialContext>): Promise<ServerEnvelope>;
@@ -423,8 +438,8 @@ export interface SuspenseViews {
423
438
  }
424
439
 
425
440
  export interface ComponentRegistry extends RegistryInspection<ComponentFunction> {
426
- register(id: string, Component: ComponentFunction): string;
427
- registerMany(map?: Record<string, ComponentFunction>): this;
441
+ register(id: string, Component: ComponentFunction | LazyDescriptor): string;
442
+ registerMany(map?: Record<string, ComponentFunction | LazyDescriptor>): this;
428
443
  unregister(id: string): boolean;
429
444
  resolve(id: string): ComponentFunction | undefined;
430
445
  }
@@ -524,22 +539,24 @@ export interface RegistryStore {
524
539
 
525
540
  export interface RegistrySnapshot {
526
541
  signal: Record<string, unknown>;
527
- handler: Record<string, { id: string; kind: "handler" }>;
528
- server: Record<string, { id: string; kind: "server" }>;
529
- partial: Record<string, { id: string; kind: "partial" }>;
542
+ handler: Record<string, { id?: string } | LazyDescriptor>;
543
+ server: Record<string, { id?: string } | LazyDescriptor>;
544
+ partial: Record<string, { id?: string } | LazyDescriptor>;
530
545
  route: Record<string, RouteDefinition>;
531
- component: Record<string, { id: string; kind: "component" }>;
546
+ component: Record<string, { id?: string } | LazyDescriptor>;
547
+ asyncSignal: Record<string, { id?: string } | LazyDescriptor>;
532
548
  cache: { browser: Record<string, CacheDefinition>; server: Record<string, CacheDefinition> };
533
549
  entries: { browser: Record<string, unknown>; server: Record<string, unknown> };
534
550
  }
535
551
 
536
552
  export interface AppDefinition {
537
553
  signal?: SignalMap;
538
- handler?: Record<string, HandlerFunction>;
554
+ handler?: Record<string, HandlerFunction | LazyDescriptor>;
539
555
  server?: Record<string, ServerFunction>;
540
- partial?: Record<string, PartialFunction>;
556
+ partial?: Record<string, PartialFunction | LazyDescriptor>;
541
557
  route?: Record<string, RouteDefinition | string>;
542
- component?: Record<string, ComponentFunction>;
558
+ component?: Record<string, ComponentFunction | LazyDescriptor>;
559
+ asyncSignal?: Record<string, AsyncSignalFunction | LazyDescriptor>;
543
560
  cache?: {
544
561
  browser?: Record<string, CacheDefinition | CacheDefinitionOptions>;
545
562
  server?: Record<string, CacheDefinition | CacheDefinitionOptions>;
@@ -547,25 +564,43 @@ export interface AppDefinition {
547
564
  entries?: { browser?: Record<string, unknown>; server?: Record<string, unknown> };
548
565
  }
549
566
 
567
+ export interface RegistryRuntimeSnapshot extends AppDefinition {
568
+ signals?: Record<string, unknown>;
569
+ }
570
+
571
+ export interface RootInspection {
572
+ count: number;
573
+ roots: Array<{ root: Document | Element | DocumentFragment; loader: LoaderInstance; primary: boolean }>;
574
+ }
575
+
550
576
  export interface AppHub {
551
577
  registry: RegistryStore;
552
578
  runtime?: AppRuntime;
553
579
  use(type: "signal", entries: SignalMap): this;
554
- use(type: "handler", entries: Record<string, HandlerFunction>): this;
580
+ use(type: "handler", entries: Record<string, HandlerFunction | LazyDescriptor>): this;
555
581
  use(type: "server", entries: Record<string, ServerFunction>): this;
556
- use(type: "partial", entries: Record<string, PartialFunction>): this;
582
+ use(type: "partial", entries: Record<string, PartialFunction | LazyDescriptor>): this;
557
583
  use(type: "route", entries: Record<string, RouteDefinition | string>): this;
558
- use(type: "component", entries: Record<string, ComponentFunction>): this;
584
+ use(type: "component", entries: Record<string, ComponentFunction | LazyDescriptor>): this;
585
+ use(type: "asyncSignal", entries: Record<string, AsyncSignalFunction | LazyDescriptor>): this;
559
586
  use(moduleObject: AppDefinition): this;
560
587
  snapshot(): AppDefinition;
561
588
  start(options?: CreateAppOptions): AppRuntime;
589
+ attachRoot(root: Document | Element | DocumentFragment): AppRuntime;
590
+ detachRoot(root?: Document | Element | DocumentFragment): this;
591
+ applySnapshot(snapshot: RegistryRuntimeSnapshot, options?: { strict?: boolean }): this;
592
+ inspectRoots(): RootInspection;
562
593
  }
563
594
 
564
595
  export interface CreateAppOptions extends LoaderOptions {
565
596
  target?: RuntimeTarget;
566
597
  mode?: RouterMode;
567
598
  boundary?: string;
568
- snapshot?: { signals?: Record<string, unknown>; cache?: { browser?: Record<string, unknown> } };
599
+ snapshot?: RegistryRuntimeSnapshot;
600
+ registryAssets?: RegistryAssetsConfig;
601
+ importModule?: (url: string) => MaybePromise<Record<string, unknown>>;
602
+ lazyRegistry?: LazyRegistry;
603
+ strictSnapshots?: boolean;
569
604
  registry?: RegistryStore;
570
605
  loader?: LoaderInstance;
571
606
  router?: Router | false;
@@ -604,6 +639,10 @@ export interface AppRuntime {
604
639
  attributes: NormalizedAttributeConfig;
605
640
  start(): this;
606
641
  use(type: Parameters<AppHub["use"]>[0], entries?: unknown): this;
642
+ attachRoot(root: Document | Element | DocumentFragment): this;
643
+ detachRoot(root?: Document | Element | DocumentFragment): this;
644
+ applySnapshot(snapshot: RegistryRuntimeSnapshot, options?: { strict?: boolean }): this;
645
+ inspectRoots(): RootInspection;
607
646
  render(url: string | URL): Promise<RenderResult>;
608
647
  destroy(): void;
609
648
  }
@@ -614,6 +653,10 @@ export interface AsyncNamespace extends AppHub {
614
653
  createApp: typeof createApp;
615
654
  defineApp: typeof defineApp;
616
655
  readSnapshot: typeof readSnapshot;
656
+ attachRoot: AppHub["attachRoot"];
657
+ detachRoot: AppHub["detachRoot"];
658
+ applySnapshot: AppHub["applySnapshot"];
659
+ inspectRoots: AppHub["inspectRoots"];
617
660
  attributeName: typeof attributeName;
618
661
  defineAttributeConfig: typeof defineAttributeConfig;
619
662
  createBoundaryReceiver: typeof createBoundaryReceiver;
@@ -622,6 +665,10 @@ export interface AsyncNamespace extends AppHub {
622
665
  component: typeof component;
623
666
  createComponentRegistry: typeof createComponentRegistry;
624
667
  defineComponent: typeof defineComponent;
668
+ defineAsyncContainerElement: typeof defineAsyncContainerElement;
669
+ defineAsyncSuspenseElement: typeof defineAsyncSuspenseElement;
670
+ defineRegistrySnapshot: typeof defineRegistrySnapshot;
671
+ createLazyRegistry: typeof createLazyRegistry;
625
672
  delay: typeof delay;
626
673
  createHandlerRegistry: typeof createHandlerRegistry;
627
674
  html: typeof html;
@@ -649,7 +696,7 @@ export declare function asyncSignal<T = unknown>(id: string, fn: AsyncSignalFunc
649
696
  export declare const Async: AppHub;
650
697
  export declare function createApp(appOrDefinition?: AppHub | AppDefinition, options?: CreateAppOptions): AppRuntime;
651
698
  export declare function defineApp(initial?: AppDefinition): AppHub;
652
- export declare function readSnapshot(root?: Document | Element, options?: { attributes?: AttributeConfig }): { signals?: Record<string, unknown>; cache?: { browser?: Record<string, unknown> } };
699
+ export declare function readSnapshot(root?: Document | Element, options?: { attributes?: AttributeConfig }): RegistryRuntimeSnapshot;
653
700
  export declare function attributeName(attributes: AttributeConfig | undefined, type: keyof NormalizedAttributeConfig, name: string): string;
654
701
  export declare function defineAttributeConfig(config?: AttributeConfig): NormalizedAttributeConfig;
655
702
  export declare function createBoundaryReceiver(options: BoundaryReceiverOptions): BoundaryReceiver;
@@ -658,6 +705,10 @@ export declare function defineCache(options?: CacheDefinitionOptions): CacheDefi
658
705
  export declare function component<TProps extends Record<string, unknown> = Record<string, unknown>>(fn: ComponentFunction<TProps>): ComponentFunction<TProps>;
659
706
  export declare function createComponentRegistry(initialMap?: Record<string, ComponentFunction>, options?: { registry?: RegistryStore; type?: "component" }): ComponentRegistry;
660
707
  export declare function defineComponent<TProps extends Record<string, unknown> = Record<string, unknown>>(fn: ComponentFunction<TProps>): ComponentFunction<TProps>;
708
+ export declare function defineAsyncContainerElement(options?: { tagName?: string; app?: AppHub; Async?: AppHub; customElements?: CustomElementRegistry; HTMLElement?: typeof HTMLElement; window?: Window }): CustomElementConstructor;
709
+ export declare function defineAsyncSuspenseElement(options?: { tagName?: string; customElements?: CustomElementRegistry; HTMLElement?: typeof HTMLElement; window?: Window }): CustomElementConstructor;
710
+ export declare function defineRegistrySnapshot<T extends RegistryRuntimeSnapshot>(snapshot?: T): T;
711
+ export declare function createLazyRegistry(options?: { registryAssets?: RegistryAssetsConfig; assets?: RegistryAssetsConfig; importModule?: (url: string) => MaybePromise<Record<string, unknown>> }): LazyRegistry;
661
712
  export declare function delay(ms: number, signal?: AbortSignal): Promise<void>;
662
713
  export declare function createHandlerRegistry(initialMap?: Record<string, HandlerFunction>, options?: { registry?: RegistryStore; type?: "handler" }): HandlerRegistry;
663
714
  export declare function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult;