@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 +20 -1
- package/README.md +115 -0
- package/browser.d.ts +69 -18
- package/browser.js +733 -71
- package/browser.min.js +1 -1
- package/browser.ts +733 -71
- package/browser.umd.js +733 -71
- package/browser.umd.min.js +1 -1
- package/package.json +11 -4
- package/server.d.ts +69 -18
- package/src/app.js +314 -46
- package/src/browser.js +2 -0
- package/src/component.js +19 -2
- package/src/elements.js +63 -0
- package/src/handlers.js +19 -2
- package/src/index.js +2 -0
- package/src/lazy-registry.js +204 -0
- package/src/loader.js +23 -5
- package/src/partials.js +19 -2
- package/src/registry-store.js +15 -9
- package/src/signals.js +46 -4
package/server.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
|
|
528
|
-
server: Record<string, { id
|
|
529
|
-
partial: Record<string, { id
|
|
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
|
|
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?:
|
|
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;
|
|
@@ -651,7 +698,7 @@ export declare function asyncSignal<T = unknown>(id: string, fn: AsyncSignalFunc
|
|
|
651
698
|
export declare const Async: AppHub;
|
|
652
699
|
export declare function createApp(appOrDefinition?: AppHub | AppDefinition, options?: CreateAppOptions): AppRuntime;
|
|
653
700
|
export declare function defineApp(initial?: AppDefinition): AppHub;
|
|
654
|
-
export declare function readSnapshot(root?: Document | Element, options?: { attributes?: AttributeConfig }):
|
|
701
|
+
export declare function readSnapshot(root?: Document | Element, options?: { attributes?: AttributeConfig }): RegistryRuntimeSnapshot;
|
|
655
702
|
export declare function attributeName(attributes: AttributeConfig | undefined, type: keyof NormalizedAttributeConfig, name: string): string;
|
|
656
703
|
export declare function defineAttributeConfig(config?: AttributeConfig): NormalizedAttributeConfig;
|
|
657
704
|
export declare function createBoundaryReceiver(options: BoundaryReceiverOptions): BoundaryReceiver;
|
|
@@ -660,6 +707,10 @@ export declare function defineCache(options?: CacheDefinitionOptions): CacheDefi
|
|
|
660
707
|
export declare function component<TProps extends Record<string, unknown> = Record<string, unknown>>(fn: ComponentFunction<TProps>): ComponentFunction<TProps>;
|
|
661
708
|
export declare function createComponentRegistry(initialMap?: Record<string, ComponentFunction>, options?: { registry?: RegistryStore; type?: "component" }): ComponentRegistry;
|
|
662
709
|
export declare function defineComponent<TProps extends Record<string, unknown> = Record<string, unknown>>(fn: ComponentFunction<TProps>): ComponentFunction<TProps>;
|
|
710
|
+
export declare function defineAsyncContainerElement(options?: { tagName?: string; app?: AppHub; Async?: AppHub; customElements?: CustomElementRegistry; HTMLElement?: typeof HTMLElement; window?: Window }): CustomElementConstructor;
|
|
711
|
+
export declare function defineAsyncSuspenseElement(options?: { tagName?: string; customElements?: CustomElementRegistry; HTMLElement?: typeof HTMLElement; window?: Window }): CustomElementConstructor;
|
|
712
|
+
export declare function defineRegistrySnapshot<T extends RegistryRuntimeSnapshot>(snapshot?: T): T;
|
|
713
|
+
export declare function createLazyRegistry(options?: { registryAssets?: RegistryAssetsConfig; assets?: RegistryAssetsConfig; importModule?: (url: string) => MaybePromise<Record<string, unknown>> }): LazyRegistry;
|
|
663
714
|
export declare function delay(ms: number, signal?: AbortSignal): Promise<void>;
|
|
664
715
|
export declare function createHandlerRegistry(initialMap?: Record<string, HandlerFunction>, options?: { registry?: RegistryStore; type?: "handler" }): HandlerRegistry;
|
|
665
716
|
export declare function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult;
|
package/src/app.js
CHANGED
|
@@ -9,8 +9,9 @@ import { createServerNamespace } from "./server.js";
|
|
|
9
9
|
import { createSignal, createSignalRegistry } from "./signals.js";
|
|
10
10
|
import { createRegistryStore } from "./registry-store.js";
|
|
11
11
|
import { attributeName, normalizeAttributeConfig } from "./attributes.js";
|
|
12
|
+
import { createLazyRegistry, defineRegistrySnapshot, sameRegistryValue } from "./lazy-registry.js";
|
|
12
13
|
|
|
13
|
-
const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
|
|
14
|
+
const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component", "asyncSignal"]);
|
|
14
15
|
|
|
15
16
|
export function defineApp(initial, options = {}) {
|
|
16
17
|
const registry = createRegistryStore(undefined, { target: "browser" });
|
|
@@ -39,6 +40,27 @@ export function defineApp(initial, options = {}) {
|
|
|
39
40
|
return runtime;
|
|
40
41
|
},
|
|
41
42
|
|
|
43
|
+
attachRoot(root) {
|
|
44
|
+
return ensureRuntime(app).attachRoot(root);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
detachRoot(root) {
|
|
48
|
+
return app.runtime?.detachRoot(root) ?? app;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
applySnapshot(snapshot, snapshotOptions = {}) {
|
|
52
|
+
if (app.runtime) {
|
|
53
|
+
app.runtime.applySnapshot(snapshot, snapshotOptions);
|
|
54
|
+
return app;
|
|
55
|
+
}
|
|
56
|
+
appendSnapshotDeclarations(registry, snapshot, snapshotOptions);
|
|
57
|
+
return app;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
inspectRoots() {
|
|
61
|
+
return app.runtime?.inspectRoots() ?? { count: 0, roots: [] };
|
|
62
|
+
},
|
|
63
|
+
|
|
42
64
|
_attach(runtime) {
|
|
43
65
|
runtimes.add(runtime);
|
|
44
66
|
return () => app._detach(runtime);
|
|
@@ -64,23 +86,32 @@ export function createApp(appOrDefinition = Async, options = {}) {
|
|
|
64
86
|
});
|
|
65
87
|
const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
|
|
66
88
|
const attributes = normalizeAttributeConfig(options.attributes);
|
|
89
|
+
const lazyRegistry = options.lazyRegistry ?? createLazyRegistry({
|
|
90
|
+
registryAssets: options.registryAssets,
|
|
91
|
+
importModule: options.importModule
|
|
92
|
+
});
|
|
67
93
|
const registry = options.registry ?? app.registry.view({ target });
|
|
68
|
-
const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
|
|
69
|
-
const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
|
|
94
|
+
const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal", lazyRegistry });
|
|
95
|
+
const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler", lazyRegistry });
|
|
70
96
|
const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
|
|
71
97
|
const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
|
|
72
98
|
const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
|
|
73
99
|
const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
|
|
74
|
-
const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
|
|
100
|
+
const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial", lazyRegistry });
|
|
75
101
|
const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
|
|
76
|
-
const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
|
|
102
|
+
const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component", lazyRegistry });
|
|
103
|
+
const hasStartupRoot = options.loader || Object.hasOwn(options, "root");
|
|
104
|
+
const startupRoot = hasStartupRoot ? options.root : null;
|
|
77
105
|
let loader = options.loader;
|
|
78
106
|
let router = options.router;
|
|
107
|
+
let routerStarted = false;
|
|
79
108
|
let detach = () => {};
|
|
80
109
|
let started = false;
|
|
81
110
|
let destroyed = false;
|
|
111
|
+
const rootLoaders = new Map();
|
|
82
112
|
|
|
83
|
-
|
|
113
|
+
const snapshotRoot = startupRoot ?? globalThis.document;
|
|
114
|
+
const initialSnapshot = options.snapshot ?? (target === "browser" ? readSnapshot(snapshotRoot, { attributes }) : undefined);
|
|
84
115
|
attachServerCache(server, serverCache);
|
|
85
116
|
|
|
86
117
|
const runtime = {
|
|
@@ -109,54 +140,112 @@ export function createApp(appOrDefinition = Async, options = {}) {
|
|
|
109
140
|
started = true;
|
|
110
141
|
|
|
111
142
|
if (target !== "server") {
|
|
112
|
-
loader = loader ?? Loader({
|
|
113
|
-
root: options.root,
|
|
114
|
-
signals,
|
|
115
|
-
handlers,
|
|
116
|
-
server,
|
|
117
|
-
cache: browserCache,
|
|
118
|
-
scheduler,
|
|
119
|
-
attributes
|
|
120
|
-
});
|
|
121
|
-
runtime.loader = loader;
|
|
122
|
-
|
|
123
143
|
configureServerContext({ cache: browserCache });
|
|
124
144
|
signals._setContext?.({ server, loader, cache: browserCache, scheduler });
|
|
125
145
|
|
|
126
|
-
loader
|
|
146
|
+
if (loader) {
|
|
147
|
+
registerRootLoader(loader.root, loader);
|
|
148
|
+
loader.start();
|
|
149
|
+
startRouterFor(loader.root);
|
|
150
|
+
} else if (startupRoot != null) {
|
|
151
|
+
runtime.attachRoot(startupRoot);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
configureServerContext({ cache: serverCache });
|
|
155
|
+
signals._setContext?.({ server, cache: serverCache, scheduler });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return runtime;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
use(typeOrModule, entries) {
|
|
162
|
+
app.use(typeOrModule, entries);
|
|
163
|
+
return runtime;
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
attachRoot(root) {
|
|
167
|
+
assertActive();
|
|
168
|
+
if (target === "server") {
|
|
169
|
+
throw new Error("Server runtimes cannot attach DOM roots.");
|
|
170
|
+
}
|
|
171
|
+
if (!root) {
|
|
172
|
+
throw new TypeError("runtime.attachRoot(root) requires a root.");
|
|
173
|
+
}
|
|
174
|
+
if (rootLoaders.has(root)) {
|
|
175
|
+
return runtime;
|
|
176
|
+
}
|
|
127
177
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
root
|
|
132
|
-
boundary: options.boundary ?? "route",
|
|
133
|
-
routes,
|
|
134
|
-
loader,
|
|
178
|
+
const rootLoader = rootLoaders.size === 0 && loader
|
|
179
|
+
? loader
|
|
180
|
+
: Loader({
|
|
181
|
+
root,
|
|
135
182
|
signals,
|
|
136
183
|
handlers,
|
|
137
184
|
server,
|
|
138
185
|
cache: browserCache,
|
|
139
|
-
partials,
|
|
140
186
|
scheduler,
|
|
141
|
-
fetch: options.fetch,
|
|
142
|
-
routeEndpoint: options.routeEndpoint,
|
|
143
187
|
attributes
|
|
144
188
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
189
|
+
registerRootLoader(root, rootLoader);
|
|
190
|
+
rootLoader.start();
|
|
191
|
+
configureServerContext({ cache: browserCache });
|
|
192
|
+
signals._setContext?.({ server, loader: runtime.loader, cache: browserCache, scheduler });
|
|
193
|
+
startRouterFor(root);
|
|
194
|
+
return runtime;
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
detachRoot(root) {
|
|
198
|
+
assertActive();
|
|
199
|
+
if (target === "server") {
|
|
200
|
+
return runtime;
|
|
201
|
+
}
|
|
202
|
+
if (root == null) {
|
|
203
|
+
for (const rootLoader of new Set(rootLoaders.values())) {
|
|
204
|
+
rootLoader.destroy?.();
|
|
205
|
+
}
|
|
206
|
+
rootLoaders.clear();
|
|
207
|
+
router?.destroy?.();
|
|
208
|
+
router = undefined;
|
|
209
|
+
routerStarted = false;
|
|
210
|
+
loader = undefined;
|
|
211
|
+
runtime.loader = undefined;
|
|
212
|
+
runtime.router = undefined;
|
|
213
|
+
return runtime;
|
|
214
|
+
}
|
|
215
|
+
const rootLoader = rootLoaders.get(root);
|
|
216
|
+
if (!rootLoader) {
|
|
217
|
+
return runtime;
|
|
218
|
+
}
|
|
219
|
+
rootLoader.destroy?.();
|
|
220
|
+
rootLoaders.delete(root);
|
|
221
|
+
if (loader === rootLoader) {
|
|
222
|
+
router?.destroy?.();
|
|
223
|
+
router = undefined;
|
|
224
|
+
routerStarted = false;
|
|
225
|
+
const next = rootLoaders.values().next().value;
|
|
226
|
+
loader = next;
|
|
227
|
+
runtime.loader = next;
|
|
228
|
+
runtime.router = undefined;
|
|
229
|
+
if (next) {
|
|
230
|
+
startRouterFor(next.root);
|
|
149
231
|
}
|
|
150
|
-
} else {
|
|
151
|
-
configureServerContext({ cache: serverCache });
|
|
152
|
-
signals._setContext?.({ server, cache: serverCache, scheduler });
|
|
153
232
|
}
|
|
154
|
-
|
|
155
233
|
return runtime;
|
|
156
234
|
},
|
|
157
235
|
|
|
158
|
-
|
|
159
|
-
|
|
236
|
+
inspectRoots() {
|
|
237
|
+
return {
|
|
238
|
+
count: rootLoaders.size,
|
|
239
|
+
roots: [...rootLoaders].map(([root, rootLoader]) => ({
|
|
240
|
+
root,
|
|
241
|
+
loader: rootLoader,
|
|
242
|
+
primary: rootLoader === loader
|
|
243
|
+
}))
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
applySnapshot(snapshot, snapshotOptions = {}) {
|
|
248
|
+
applySnapshotToRuntime(runtime, snapshot, snapshotOptions);
|
|
160
249
|
return runtime;
|
|
161
250
|
},
|
|
162
251
|
|
|
@@ -218,7 +307,14 @@ export function createApp(appOrDefinition = Async, options = {}) {
|
|
|
218
307
|
destroyed = true;
|
|
219
308
|
detach();
|
|
220
309
|
router?.destroy?.();
|
|
221
|
-
|
|
310
|
+
const destroyedLoaders = new Set(rootLoaders.values());
|
|
311
|
+
for (const rootLoader of destroyedLoaders) {
|
|
312
|
+
rootLoader.destroy?.();
|
|
313
|
+
}
|
|
314
|
+
rootLoaders.clear();
|
|
315
|
+
if (loader && !destroyedLoaders.has(loader)) {
|
|
316
|
+
loader?.destroy?.();
|
|
317
|
+
}
|
|
222
318
|
signals.destroy?.();
|
|
223
319
|
if (ownsScheduler) {
|
|
224
320
|
scheduler.destroy();
|
|
@@ -232,10 +328,49 @@ export function createApp(appOrDefinition = Async, options = {}) {
|
|
|
232
328
|
|
|
233
329
|
server.cache = serverCache;
|
|
234
330
|
runtime.server.cache = serverCache;
|
|
331
|
+
runtime.applySnapshot(initialSnapshot, { strict: options.strictSnapshots ?? true });
|
|
235
332
|
detach = app._attach(runtime);
|
|
236
333
|
|
|
237
334
|
return runtime;
|
|
238
335
|
|
|
336
|
+
function registerRootLoader(root, rootLoader) {
|
|
337
|
+
rootLoaders.set(root, rootLoader);
|
|
338
|
+
if (!loader) {
|
|
339
|
+
loader = rootLoader;
|
|
340
|
+
runtime.loader = rootLoader;
|
|
341
|
+
}
|
|
342
|
+
rootLoader.server = server;
|
|
343
|
+
rootLoader.cache = browserCache;
|
|
344
|
+
rootLoader.scheduler = scheduler;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function startRouterFor(root) {
|
|
348
|
+
if (router === false || routerStarted || !(router || shouldStartRouter(routes, options)) || !runtime.loader) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
router = router ?? createRouter({
|
|
352
|
+
mode: options.mode ?? "ssr-spa",
|
|
353
|
+
root,
|
|
354
|
+
boundary: options.boundary ?? "route",
|
|
355
|
+
routes,
|
|
356
|
+
loader: runtime.loader,
|
|
357
|
+
signals,
|
|
358
|
+
handlers,
|
|
359
|
+
server,
|
|
360
|
+
cache: browserCache,
|
|
361
|
+
partials,
|
|
362
|
+
scheduler,
|
|
363
|
+
fetch: options.fetch,
|
|
364
|
+
routeEndpoint: options.routeEndpoint,
|
|
365
|
+
attributes
|
|
366
|
+
});
|
|
367
|
+
runtime.router = router;
|
|
368
|
+
runtime.loader.router = router;
|
|
369
|
+
configureServerContext({ cache: browserCache, router });
|
|
370
|
+
router.start();
|
|
371
|
+
routerStarted = true;
|
|
372
|
+
}
|
|
373
|
+
|
|
239
374
|
function configureServerContext(extra = {}) {
|
|
240
375
|
const cache = isLocalServerRegistry(server) ? serverCache : extra.cache;
|
|
241
376
|
server._setContext?.({
|
|
@@ -278,6 +413,7 @@ export function readSnapshot(root = globalThis.document, { attributes } = {}) {
|
|
|
278
413
|
return {};
|
|
279
414
|
}
|
|
280
415
|
|
|
416
|
+
const merged = {};
|
|
281
417
|
for (const searchRoot of new Set([rootNode, documentRef])) {
|
|
282
418
|
if (!searchRoot?.querySelectorAll) {
|
|
283
419
|
continue;
|
|
@@ -288,17 +424,19 @@ export function readSnapshot(root = globalThis.document, { attributes } = {}) {
|
|
|
288
424
|
}
|
|
289
425
|
const source = script.textContent?.trim() ?? "";
|
|
290
426
|
if (!source) {
|
|
291
|
-
|
|
427
|
+
continue;
|
|
292
428
|
}
|
|
429
|
+
let parsed;
|
|
293
430
|
try {
|
|
294
|
-
|
|
431
|
+
parsed = JSON.parse(source);
|
|
295
432
|
} catch (cause) {
|
|
296
433
|
throw new Error(`Could not parse Async snapshot: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
297
434
|
}
|
|
435
|
+
mergeSnapshot(merged, parsed, { strict: true });
|
|
298
436
|
}
|
|
299
437
|
}
|
|
300
438
|
|
|
301
|
-
return
|
|
439
|
+
return merged;
|
|
302
440
|
}
|
|
303
441
|
|
|
304
442
|
function applyUseToRuntime(runtime, normalized) {
|
|
@@ -308,10 +446,22 @@ function applyUseToRuntime(runtime, normalized) {
|
|
|
308
446
|
applyRegistryUse(runtime.partials, runtime.registry, normalized.partial);
|
|
309
447
|
applyRegistryUse(runtime.routes, runtime.registry, normalized.route);
|
|
310
448
|
applyRegistryUse(runtime.components, runtime.registry, normalized.component);
|
|
449
|
+
applyRegistryStoreUse(runtime.registry, "asyncSignal", normalized.asyncSignal);
|
|
311
450
|
applyRegistryUse(runtime.browser.cache, runtime.registry, normalized.cache.browser);
|
|
312
451
|
applyRegistryUse(runtime.server.cache, runtime.registry, normalized.cache.server);
|
|
313
452
|
}
|
|
314
453
|
|
|
454
|
+
function applyRegistryStoreUse(registry, type, entries) {
|
|
455
|
+
if (!entries || Object.keys(entries).length === 0) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
for (const [id, value] of Object.entries(entries)) {
|
|
459
|
+
if (!registry.has(type, id)) {
|
|
460
|
+
registry.register(type, id, value);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
315
465
|
function applyRegistryUse(registry, runtimeRegistry, entries) {
|
|
316
466
|
if (!entries || Object.keys(entries).length === 0) {
|
|
317
467
|
return;
|
|
@@ -331,6 +481,7 @@ function emptyDeclarations() {
|
|
|
331
481
|
partial: {},
|
|
332
482
|
route: {},
|
|
333
483
|
component: {},
|
|
484
|
+
asyncSignal: {},
|
|
334
485
|
cache: {
|
|
335
486
|
browser: {},
|
|
336
487
|
server: {}
|
|
@@ -386,11 +537,128 @@ function isAppHub(value) {
|
|
|
386
537
|
return Boolean(value && typeof value.use === "function" && typeof value.snapshot === "function" && value.registry);
|
|
387
538
|
}
|
|
388
539
|
|
|
389
|
-
function
|
|
390
|
-
|
|
391
|
-
|
|
540
|
+
function ensureRuntime(app) {
|
|
541
|
+
if (!app.runtime) {
|
|
542
|
+
app.start();
|
|
543
|
+
}
|
|
544
|
+
return app.runtime;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function applySnapshotToRuntime(runtime, snapshot = {}, options = {}) {
|
|
548
|
+
const normalized = normalizeSnapshot(snapshot);
|
|
549
|
+
for (const [path, value] of Object.entries(normalized.signal)) {
|
|
550
|
+
setOrRegisterSignal(runtime.signals, path, value);
|
|
551
|
+
}
|
|
552
|
+
runtime.browser.cache.restore(normalized.cache.browser);
|
|
553
|
+
mergeRegistryEntries(runtime, "handler", normalized.handler, runtime.handlers, options);
|
|
554
|
+
mergeRegistryEntries(runtime, "server", normalized.server, runtime.server, options);
|
|
555
|
+
mergeRegistryEntries(runtime, "partial", normalized.partial, runtime.partials, options);
|
|
556
|
+
mergeRegistryEntries(runtime, "route", normalized.route, runtime.routes, options);
|
|
557
|
+
mergeRegistryEntries(runtime, "component", normalized.component, runtime.components, options);
|
|
558
|
+
mergeRegistryEntries(runtime, "asyncSignal", normalized.asyncSignal, null, options);
|
|
559
|
+
return runtime;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function appendSnapshotDeclarations(registry, snapshot = {}, options = {}) {
|
|
563
|
+
const normalized = normalizeSnapshot(snapshot);
|
|
564
|
+
for (const [id, value] of Object.entries(normalized.signal)) {
|
|
565
|
+
registerSnapshotEntry(registry, "signal", id, createSignal(value), options);
|
|
566
|
+
}
|
|
567
|
+
for (const type of ["handler", "server", "partial", "route", "component", "asyncSignal"]) {
|
|
568
|
+
for (const [id, value] of Object.entries(normalized[type])) {
|
|
569
|
+
registerSnapshotEntry(registry, type, id, value, options);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function mergeRegistryEntries(runtime, type, entries, concreteRegistry, options = {}) {
|
|
575
|
+
if (!entries || Object.keys(entries).length === 0) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
for (const [id, value] of Object.entries(entries)) {
|
|
579
|
+
registerSnapshotEntry(runtime.registry, type, id, value, options);
|
|
580
|
+
}
|
|
581
|
+
concreteRegistry?._adoptMany?.(entries);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function registerSnapshotEntry(registry, type, id, value, options = {}) {
|
|
585
|
+
const strict = options.strict ?? true;
|
|
586
|
+
const map = registry._map(type);
|
|
587
|
+
if (map.has(id)) {
|
|
588
|
+
if (sameRegistryValue(map.get(id), value) || sameSnapshotValue(map.get(id), value)) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (strict) {
|
|
592
|
+
throw new Error(`${type} "${id}" is already registered with a different value.`);
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
registry.set(type, id, value);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function normalizeSnapshot(snapshot = {}) {
|
|
600
|
+
const normalized = {
|
|
601
|
+
signal: {
|
|
602
|
+
...(snapshot.signals ?? {}),
|
|
603
|
+
...(snapshot.signal ?? {})
|
|
604
|
+
},
|
|
605
|
+
handler: { ...(snapshot.handler ?? {}) },
|
|
606
|
+
server: { ...(snapshot.server ?? {}) },
|
|
607
|
+
partial: { ...(snapshot.partial ?? {}) },
|
|
608
|
+
route: { ...(snapshot.route ?? {}) },
|
|
609
|
+
component: { ...(snapshot.component ?? {}) },
|
|
610
|
+
asyncSignal: { ...(snapshot.asyncSignal ?? {}) },
|
|
611
|
+
cache: {
|
|
612
|
+
browser: {
|
|
613
|
+
...(snapshot.entries?.browser ?? {}),
|
|
614
|
+
...(snapshot.cache?.browser ?? {})
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
return normalized;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function mergeSnapshot(target, source, options = {}) {
|
|
622
|
+
const normalized = normalizeSnapshot(defineRegistrySnapshot(source));
|
|
623
|
+
target.signal = {
|
|
624
|
+
...(target.signal ?? target.signals ?? {}),
|
|
625
|
+
...normalized.signal
|
|
626
|
+
};
|
|
627
|
+
target.signals = target.signal;
|
|
628
|
+
target.cache = {
|
|
629
|
+
...(target.cache ?? {}),
|
|
630
|
+
browser: {
|
|
631
|
+
...(target.cache?.browser ?? {}),
|
|
632
|
+
...normalized.cache.browser
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
for (const type of ["handler", "server", "partial", "route", "component", "asyncSignal"]) {
|
|
636
|
+
target[type] = target[type] ?? {};
|
|
637
|
+
for (const [id, value] of Object.entries(normalized[type])) {
|
|
638
|
+
if (Object.hasOwn(target[type], id)) {
|
|
639
|
+
if (sameRegistryValue(target[type][id], value) || sameSnapshotValue(target[type][id], value)) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (options.strict ?? true) {
|
|
643
|
+
throw new Error(`${type} "${id}" is already declared with a different value.`);
|
|
644
|
+
}
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
target[type][id] = value;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return target;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function sameSnapshotValue(left, right) {
|
|
654
|
+
if (left === right) {
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
659
|
+
} catch {
|
|
660
|
+
return false;
|
|
392
661
|
}
|
|
393
|
-
browserCache.restore(snapshot.cache?.browser);
|
|
394
662
|
}
|
|
395
663
|
|
|
396
664
|
function setOrRegisterSignal(signals, path, value) {
|
package/src/browser.js
CHANGED
|
@@ -5,8 +5,10 @@ export { createBoundaryReceiver } from "./boundary-receiver.js";
|
|
|
5
5
|
export { createCacheRegistry, defineCache } from "./cache.js";
|
|
6
6
|
export { component, createComponentRegistry, defineComponent } from "./component.js";
|
|
7
7
|
export { delay } from "./delay.js";
|
|
8
|
+
export { defineAsyncContainerElement, defineAsyncSuspenseElement } from "./elements.js";
|
|
8
9
|
export { createHandlerRegistry } from "./handlers.js";
|
|
9
10
|
export { html } from "./html.js";
|
|
11
|
+
export { createLazyRegistry, defineRegistrySnapshot } from "./lazy-registry.js";
|
|
10
12
|
export { Loader, AsyncLoader } from "./loader.js";
|
|
11
13
|
export { createPartialRegistry } from "./partials.js";
|
|
12
14
|
export { createRegistryStore } from "./registry-store.js";
|