@microsoft/webui-framework 0.0.28 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/element/style-catalog.d.ts +33 -0
- package/dist/element/style-catalog.js +154 -0
- package/dist/element/styles.d.ts +2 -29
- package/dist/element/styles.js +4 -157
- package/dist/lifecycle.js +4 -0
- package/dist/static-host.js +2 -1
- package/dist/streaming-bootstrap.js +2 -2
- package/dist/streaming-deferred.js +44 -15
- package/dist/streaming-install.js +3 -2
- package/dist/streaming-mode.d.ts +2 -1
- package/dist/streaming-mode.js +4 -1
- package/dist/streaming-template-hosts.d.ts +5 -0
- package/dist/streaming-template-hosts.js +39 -0
- package/dist/template-content.d.ts +1 -0
- package/dist/template-content.js +6 -2
- package/dist/template-element.js +43 -41
- package/dist/template-registry.d.ts +29 -0
- package/dist/template-registry.js +228 -0
- package/dist/template.d.ts +1 -25
- package/dist/template.js +4 -206
- package/dist/trusted-types-policy.d.ts +4 -0
- package/dist/trusted-types-policy.js +62 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ If you are building WebUI apps in this repo, this is the component model used by
|
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
19
|
+
Trusted Types support is automatic; no setup call is needed. To enforce it,
|
|
20
|
+
allow the `webui` policy in CSP. See [Trusted Types](https://microsoft.github.io/webui/guide/concepts/hydration#trusted-types)
|
|
21
|
+
for CSP requirements and raw-HTML limits.
|
|
22
|
+
|
|
19
23
|
In this workspace:
|
|
20
24
|
|
|
21
25
|
```json
|
|
@@ -246,6 +250,20 @@ authored `<boundary>` directives through
|
|
|
246
250
|
`@microsoft/webui-framework` entry has no dependency on the coordinator, so
|
|
247
251
|
normal applications pay no streaming bundle or initialization cost.
|
|
248
252
|
|
|
253
|
+
To keep the full application out of `<head>`, put the explicit
|
|
254
|
+
`import '@microsoft/webui-framework/streaming.js'` in a small application-owned
|
|
255
|
+
entry. Register that file with your bundler, preserve its side effect, and
|
|
256
|
+
share framework modules with the application. Identify its output and static
|
|
257
|
+
dependencies through native bundler metadata and your existing asset handoff.
|
|
258
|
+
WebUI does not create entries, inject imports, or require a streaming manifest.
|
|
259
|
+
|
|
260
|
+
Load the coordinator with `type="module" async`; marker-based deferral handles
|
|
261
|
+
application-first delivery without a second framework bundle.
|
|
262
|
+
The application may load later, but early-interactive components still need
|
|
263
|
+
their registration modules. Use `fetchpriority="low"` on deferred authored
|
|
264
|
+
module scripts to exclude them from automatic modulepreload hints. See
|
|
265
|
+
[Separate coordinator and application assets](https://microsoft.github.io/webui/guide/concepts/hydration#separate-coordinator-and-application-assets).
|
|
266
|
+
|
|
249
267
|
Boundaries may be authored in entries and reusable components, including
|
|
250
268
|
runtime conditions, outlets, and selected routes. A boundary-bearing subtree
|
|
251
269
|
reached from a `<for>` body fails the build with `boundary-in-repeat`. A whole
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ComponentStyleResource = ({
|
|
2
|
+
kind: 'link';
|
|
3
|
+
href: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'style';
|
|
6
|
+
css: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'module';
|
|
9
|
+
specifier: string;
|
|
10
|
+
css: string;
|
|
11
|
+
}) & {
|
|
12
|
+
members?: string[];
|
|
13
|
+
};
|
|
14
|
+
export interface ComponentStyles {
|
|
15
|
+
version: 1;
|
|
16
|
+
strategy: 'link' | 'style' | 'module';
|
|
17
|
+
resources: Record<string, ComponentStyleResource>;
|
|
18
|
+
closures: Record<string, string[]>;
|
|
19
|
+
}
|
|
20
|
+
export interface DocumentCatalog {
|
|
21
|
+
readonly resources: Map<string, ComponentStyleResource>;
|
|
22
|
+
readonly closures: Map<string, readonly string[]>;
|
|
23
|
+
}
|
|
24
|
+
export declare function catalogFor(document: Document): DocumentCatalog;
|
|
25
|
+
export declare function sameComponentStyleResource(current: ComponentStyleResource, next: ComponentStyleResource): boolean;
|
|
26
|
+
export declare function sameComponentStyleClosure(current: readonly string[], next: readonly string[]): boolean;
|
|
27
|
+
export declare function prepareComponentStyles(value: unknown): ComponentStyles | undefined;
|
|
28
|
+
export declare function requireComponentStyles(value: unknown): ComponentStyles;
|
|
29
|
+
export declare function validateComponentStylesRegistration(styles: ComponentStyles | undefined, document?: Document): void;
|
|
30
|
+
export declare function registerPreparedComponentStyles(styles: ComponentStyles, document?: Document): void;
|
|
31
|
+
export declare function registerComponentStyles(value: ComponentStyles | unknown, document?: Document): void;
|
|
32
|
+
export declare function hasRegisteredComponentStyleResource(id: string, document?: Document): boolean;
|
|
33
|
+
export declare function hasComponentStyleWork(rootId: string, document?: Document): boolean;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const catalogs = new WeakMap();
|
|
2
|
+
export function catalogFor(document) {
|
|
3
|
+
let catalog = catalogs.get(document);
|
|
4
|
+
if (!catalog) {
|
|
5
|
+
catalog = {
|
|
6
|
+
resources: new Map(),
|
|
7
|
+
closures: new Map(),
|
|
8
|
+
};
|
|
9
|
+
catalogs.set(document, catalog);
|
|
10
|
+
}
|
|
11
|
+
return catalog;
|
|
12
|
+
}
|
|
13
|
+
function isObject(value) {
|
|
14
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
export function sameComponentStyleResource(current, next) {
|
|
17
|
+
if (current === next)
|
|
18
|
+
return true;
|
|
19
|
+
const currentMembers = current.members;
|
|
20
|
+
const nextMembers = next.members;
|
|
21
|
+
if (currentMembers?.length !== nextMembers?.length ||
|
|
22
|
+
currentMembers?.some((member, index) => member !== nextMembers?.[index])) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
switch (current.kind) {
|
|
26
|
+
case 'link':
|
|
27
|
+
return next.kind === 'link' && current.href === next.href;
|
|
28
|
+
case 'style':
|
|
29
|
+
return next.kind === 'style' && current.css === next.css;
|
|
30
|
+
case 'module':
|
|
31
|
+
return next.kind === 'module' &&
|
|
32
|
+
current.specifier === next.specifier &&
|
|
33
|
+
current.css === next.css;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function sameComponentStyleClosure(current, next) {
|
|
37
|
+
return current === next ||
|
|
38
|
+
(current.length === next.length && current.every((id, index) => id === next[index]));
|
|
39
|
+
}
|
|
40
|
+
export function prepareComponentStyles(value) {
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (!isObject(value) || value.version !== 1) {
|
|
44
|
+
throw new Error('[WebUI] componentStyles must use version 1.');
|
|
45
|
+
}
|
|
46
|
+
if (value.strategy !== 'link' && value.strategy !== 'style' && value.strategy !== 'module') {
|
|
47
|
+
throw new Error('[WebUI] componentStyles strategy must be "link", "style", or "module".');
|
|
48
|
+
}
|
|
49
|
+
if (!isObject(value.resources) || !isObject(value.closures)) {
|
|
50
|
+
throw new Error('[WebUI] componentStyles resources and closures must be objects.');
|
|
51
|
+
}
|
|
52
|
+
const strategy = value.strategy;
|
|
53
|
+
const resources = {};
|
|
54
|
+
for (const id of Object.keys(value.resources)) {
|
|
55
|
+
const resource = value.resources[id];
|
|
56
|
+
if (!id || !isObject(resource) || resource.kind !== strategy) {
|
|
57
|
+
throw new Error(`[WebUI] Invalid component style resource "${id}".`);
|
|
58
|
+
}
|
|
59
|
+
let members;
|
|
60
|
+
if (resource.members !== undefined) {
|
|
61
|
+
if (!Array.isArray(resource.members) ||
|
|
62
|
+
resource.members.length < 2 ||
|
|
63
|
+
resource.members.some(member => typeof member !== 'string' || !member) ||
|
|
64
|
+
new Set(resource.members).size !== resource.members.length) {
|
|
65
|
+
throw new Error(`[WebUI] Invalid component style resource members "${id}".`);
|
|
66
|
+
}
|
|
67
|
+
members = [...resource.members];
|
|
68
|
+
}
|
|
69
|
+
if (resource.kind === 'link' && typeof resource.href === 'string' && resource.href) {
|
|
70
|
+
resources[id] = members
|
|
71
|
+
? { kind: 'link', href: resource.href, members }
|
|
72
|
+
: { kind: 'link', href: resource.href };
|
|
73
|
+
}
|
|
74
|
+
else if (resource.kind === 'style' && typeof resource.css === 'string') {
|
|
75
|
+
resources[id] = members
|
|
76
|
+
? { kind: 'style', css: resource.css, members }
|
|
77
|
+
: { kind: 'style', css: resource.css };
|
|
78
|
+
}
|
|
79
|
+
else if (resource.kind === 'module' &&
|
|
80
|
+
typeof resource.specifier === 'string' && resource.specifier &&
|
|
81
|
+
typeof resource.css === 'string') {
|
|
82
|
+
resources[id] = members
|
|
83
|
+
? { kind: 'module', specifier: resource.specifier, css: resource.css, members }
|
|
84
|
+
: { kind: 'module', specifier: resource.specifier, css: resource.css };
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
throw new Error(`[WebUI] Invalid component style resource "${id}".`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const closures = {};
|
|
91
|
+
for (const root of Object.keys(value.closures)) {
|
|
92
|
+
const closure = value.closures[root];
|
|
93
|
+
if (!root || !Array.isArray(closure)) {
|
|
94
|
+
throw new Error(`[WebUI] Invalid component style closure "${root}".`);
|
|
95
|
+
}
|
|
96
|
+
closures[root] = closure.map((id) => {
|
|
97
|
+
if (typeof id !== 'string' || !id) {
|
|
98
|
+
throw new Error(`[WebUI] Invalid resource ID in component style closure "${root}".`);
|
|
99
|
+
}
|
|
100
|
+
return id;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return { version: 1, strategy, resources, closures };
|
|
104
|
+
}
|
|
105
|
+
export function requireComponentStyles(value) {
|
|
106
|
+
const styles = prepareComponentStyles(value);
|
|
107
|
+
if (!styles) {
|
|
108
|
+
throw new Error('[WebUI] componentStyles is required.');
|
|
109
|
+
}
|
|
110
|
+
return styles;
|
|
111
|
+
}
|
|
112
|
+
export function validateComponentStylesRegistration(styles, document = globalThis.document) {
|
|
113
|
+
if (!styles)
|
|
114
|
+
return;
|
|
115
|
+
const catalog = catalogFor(document);
|
|
116
|
+
for (const id of Object.keys(styles.resources)) {
|
|
117
|
+
const current = catalog.resources.get(id);
|
|
118
|
+
if (current && !sameComponentStyleResource(current, styles.resources[id])) {
|
|
119
|
+
throw new Error(`[WebUI] Conflicting component style resource "${id}".`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const root of Object.keys(styles.closures)) {
|
|
123
|
+
const current = catalog.closures.get(root);
|
|
124
|
+
const next = styles.closures[root];
|
|
125
|
+
if (current && !sameComponentStyleClosure(current, next)) {
|
|
126
|
+
throw new Error(`[WebUI] Conflicting component style closure "${root}".`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function registerPreparedComponentStyles(styles, document = globalThis.document) {
|
|
131
|
+
const catalog = catalogFor(document);
|
|
132
|
+
for (const id of Object.keys(styles.resources)) {
|
|
133
|
+
if (catalog.resources.has(id))
|
|
134
|
+
continue;
|
|
135
|
+
const resource = styles.resources[id];
|
|
136
|
+
catalog.resources.set(id, resource);
|
|
137
|
+
}
|
|
138
|
+
for (const root of Object.keys(styles.closures)) {
|
|
139
|
+
if (!catalog.closures.has(root)) {
|
|
140
|
+
catalog.closures.set(root, styles.closures[root]);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function registerComponentStyles(value, document = globalThis.document) {
|
|
145
|
+
const styles = requireComponentStyles(value);
|
|
146
|
+
validateComponentStylesRegistration(styles, document);
|
|
147
|
+
registerPreparedComponentStyles(styles, document);
|
|
148
|
+
}
|
|
149
|
+
export function hasRegisteredComponentStyleResource(id, document = globalThis.document) {
|
|
150
|
+
return catalogFor(document).resources.has(id);
|
|
151
|
+
}
|
|
152
|
+
export function hasComponentStyleWork(rootId, document = globalThis.document) {
|
|
153
|
+
return (catalogFor(document).closures.get(rootId)?.length ?? 0) > 0;
|
|
154
|
+
}
|
package/dist/element/styles.d.ts
CHANGED
|
@@ -1,39 +1,12 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
href: string;
|
|
4
|
-
} | {
|
|
5
|
-
kind: 'style';
|
|
6
|
-
css: string;
|
|
7
|
-
} | {
|
|
8
|
-
kind: 'module';
|
|
9
|
-
specifier: string;
|
|
10
|
-
css: string;
|
|
11
|
-
}) & {
|
|
12
|
-
members?: string[];
|
|
13
|
-
};
|
|
14
|
-
export interface ComponentStyles {
|
|
15
|
-
version: 1;
|
|
16
|
-
strategy: 'link' | 'style' | 'module';
|
|
17
|
-
resources: Record<string, ComponentStyleResource>;
|
|
18
|
-
closures: Record<string, string[]>;
|
|
19
|
-
}
|
|
1
|
+
export { hasComponentStyleWork, hasRegisteredComponentStyleResource, prepareComponentStyles, registerComponentStyles, registerPreparedComponentStyles, requireComponentStyles, sameComponentStyleClosure, sameComponentStyleResource, validateComponentStylesRegistration, } from './style-catalog.js';
|
|
2
|
+
export type { ComponentStyleResource, ComponentStyles } from './style-catalog.js';
|
|
20
3
|
type StyleTarget = Document | ShadowRoot;
|
|
21
4
|
type CssModule = {
|
|
22
5
|
default: CSSStyleSheet;
|
|
23
6
|
};
|
|
24
7
|
type CssModuleLoader = (specifier: string) => Promise<CssModule>;
|
|
25
8
|
export declare function setCssModuleLoaderForTests(loader?: CssModuleLoader): void;
|
|
26
|
-
export declare function sameComponentStyleResource(current: ComponentStyleResource, next: ComponentStyleResource): boolean;
|
|
27
|
-
export declare function sameComponentStyleClosure(current: readonly string[], next: readonly string[]): boolean;
|
|
28
|
-
export declare function prepareComponentStyles(value: unknown): ComponentStyles | undefined;
|
|
29
|
-
export declare function requireComponentStyles(value: unknown): ComponentStyles;
|
|
30
|
-
export declare function validateComponentStylesRegistration(styles: ComponentStyles | undefined, document?: Document): void;
|
|
31
|
-
export declare function registerPreparedComponentStyles(styles: ComponentStyles, document?: Document): void;
|
|
32
|
-
export declare function registerComponentStyles(value: ComponentStyles | unknown, document?: Document): void;
|
|
33
|
-
export declare function hasRegisteredComponentStyleResource(id: string, document?: Document): boolean;
|
|
34
|
-
export declare function hasComponentStyleWork(rootId: string, document?: Document): boolean;
|
|
35
9
|
export declare function isComponentStyleMarker(element: Element): boolean;
|
|
36
10
|
export declare function isComponentStyleResourceMarker(element: Element): boolean;
|
|
37
11
|
export declare function claimSsrComponentStyles(source: Element, target: StyleTarget): void;
|
|
38
12
|
export declare function installComponentStyles(rootId: string, target: StyleTarget, host?: HTMLElement): Promise<void> | undefined;
|
|
39
|
-
export {};
|
package/dist/element/styles.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { installComponentLinkStyles } from './link-styles.js';
|
|
2
|
+
import { setImportMapContent } from '../trusted-types-policy.js';
|
|
3
|
+
import { catalogFor } from './style-catalog.js';
|
|
4
|
+
export { hasComponentStyleWork, hasRegisteredComponentStyleResource, prepareComponentStyles, registerComponentStyles, registerPreparedComponentStyles, requireComponentStyles, sameComponentStyleClosure, sameComponentStyleResource, validateComponentStylesRegistration, } from './style-catalog.js';
|
|
2
5
|
const DEV = typeof __WEBUI_DEV__ === 'undefined' || __WEBUI_DEV__;
|
|
3
|
-
const catalogs = new WeakMap();
|
|
4
6
|
const installed = new WeakMap();
|
|
5
7
|
const completedClosures = new WeakMap();
|
|
6
8
|
const pending = new WeakMap();
|
|
@@ -14,26 +16,12 @@ let loadCssModule = defaultCssModuleLoader;
|
|
|
14
16
|
export function setCssModuleLoaderForTests(loader) {
|
|
15
17
|
loadCssModule = loader ?? defaultCssModuleLoader;
|
|
16
18
|
}
|
|
17
|
-
function catalogFor(document) {
|
|
18
|
-
let catalog = catalogs.get(document);
|
|
19
|
-
if (!catalog) {
|
|
20
|
-
catalog = {
|
|
21
|
-
resources: new Map(),
|
|
22
|
-
closures: new Map(),
|
|
23
|
-
};
|
|
24
|
-
catalogs.set(document, catalog);
|
|
25
|
-
}
|
|
26
|
-
return catalog;
|
|
27
|
-
}
|
|
28
19
|
function isDocument(target) {
|
|
29
20
|
return target.nodeType === 9;
|
|
30
21
|
}
|
|
31
22
|
function owningDocument(target) {
|
|
32
23
|
return isDocument(target) ? target : target.ownerDocument;
|
|
33
24
|
}
|
|
34
|
-
function isObject(value) {
|
|
35
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
36
|
-
}
|
|
37
25
|
function styleGlobal(owner) {
|
|
38
26
|
const configured = owner.defaultView?.__webui;
|
|
39
27
|
if (configured || owner !== globalThis.document || typeof window === 'undefined') {
|
|
@@ -49,145 +37,6 @@ function readNonce(target = document) {
|
|
|
49
37
|
const meta = owner.querySelector('meta[name="webui-nonce"]');
|
|
50
38
|
return meta?.content ?? '';
|
|
51
39
|
}
|
|
52
|
-
export function sameComponentStyleResource(current, next) {
|
|
53
|
-
if (current === next)
|
|
54
|
-
return true;
|
|
55
|
-
const currentMembers = current.members;
|
|
56
|
-
const nextMembers = next.members;
|
|
57
|
-
if (currentMembers?.length !== nextMembers?.length ||
|
|
58
|
-
currentMembers?.some((member, index) => member !== nextMembers?.[index])) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
switch (current.kind) {
|
|
62
|
-
case 'link':
|
|
63
|
-
return next.kind === 'link' && current.href === next.href;
|
|
64
|
-
case 'style':
|
|
65
|
-
return next.kind === 'style' && current.css === next.css;
|
|
66
|
-
case 'module':
|
|
67
|
-
return next.kind === 'module' &&
|
|
68
|
-
current.specifier === next.specifier &&
|
|
69
|
-
current.css === next.css;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
export function sameComponentStyleClosure(current, next) {
|
|
73
|
-
return current === next ||
|
|
74
|
-
(current.length === next.length && current.every((id, index) => id === next[index]));
|
|
75
|
-
}
|
|
76
|
-
export function prepareComponentStyles(value) {
|
|
77
|
-
if (value === undefined)
|
|
78
|
-
return undefined;
|
|
79
|
-
if (!isObject(value) || value.version !== 1) {
|
|
80
|
-
throw new Error('[WebUI] componentStyles must use version 1.');
|
|
81
|
-
}
|
|
82
|
-
if (value.strategy !== 'link' && value.strategy !== 'style' && value.strategy !== 'module') {
|
|
83
|
-
throw new Error('[WebUI] componentStyles strategy must be "link", "style", or "module".');
|
|
84
|
-
}
|
|
85
|
-
if (!isObject(value.resources) || !isObject(value.closures)) {
|
|
86
|
-
throw new Error('[WebUI] componentStyles resources and closures must be objects.');
|
|
87
|
-
}
|
|
88
|
-
const strategy = value.strategy;
|
|
89
|
-
const resources = {};
|
|
90
|
-
for (const id of Object.keys(value.resources)) {
|
|
91
|
-
const resource = value.resources[id];
|
|
92
|
-
if (!id || !isObject(resource) || resource.kind !== strategy) {
|
|
93
|
-
throw new Error(`[WebUI] Invalid component style resource "${id}".`);
|
|
94
|
-
}
|
|
95
|
-
let members;
|
|
96
|
-
if (resource.members !== undefined) {
|
|
97
|
-
if (!Array.isArray(resource.members) ||
|
|
98
|
-
resource.members.length < 2 ||
|
|
99
|
-
resource.members.some(member => typeof member !== 'string' || !member) ||
|
|
100
|
-
new Set(resource.members).size !== resource.members.length) {
|
|
101
|
-
throw new Error(`[WebUI] Invalid component style resource members "${id}".`);
|
|
102
|
-
}
|
|
103
|
-
members = [...resource.members];
|
|
104
|
-
}
|
|
105
|
-
if (resource.kind === 'link' && typeof resource.href === 'string' && resource.href) {
|
|
106
|
-
resources[id] = members
|
|
107
|
-
? { kind: 'link', href: resource.href, members }
|
|
108
|
-
: { kind: 'link', href: resource.href };
|
|
109
|
-
}
|
|
110
|
-
else if (resource.kind === 'style' && typeof resource.css === 'string') {
|
|
111
|
-
resources[id] = members
|
|
112
|
-
? { kind: 'style', css: resource.css, members }
|
|
113
|
-
: { kind: 'style', css: resource.css };
|
|
114
|
-
}
|
|
115
|
-
else if (resource.kind === 'module' &&
|
|
116
|
-
typeof resource.specifier === 'string' && resource.specifier &&
|
|
117
|
-
typeof resource.css === 'string') {
|
|
118
|
-
resources[id] = members
|
|
119
|
-
? { kind: 'module', specifier: resource.specifier, css: resource.css, members }
|
|
120
|
-
: { kind: 'module', specifier: resource.specifier, css: resource.css };
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
throw new Error(`[WebUI] Invalid component style resource "${id}".`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const closures = {};
|
|
127
|
-
for (const root of Object.keys(value.closures)) {
|
|
128
|
-
const closure = value.closures[root];
|
|
129
|
-
if (!root || !Array.isArray(closure)) {
|
|
130
|
-
throw new Error(`[WebUI] Invalid component style closure "${root}".`);
|
|
131
|
-
}
|
|
132
|
-
closures[root] = closure.map((id) => {
|
|
133
|
-
if (typeof id !== 'string' || !id) {
|
|
134
|
-
throw new Error(`[WebUI] Invalid resource ID in component style closure "${root}".`);
|
|
135
|
-
}
|
|
136
|
-
return id;
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return { version: 1, strategy, resources, closures };
|
|
140
|
-
}
|
|
141
|
-
export function requireComponentStyles(value) {
|
|
142
|
-
const styles = prepareComponentStyles(value);
|
|
143
|
-
if (!styles) {
|
|
144
|
-
throw new Error('[WebUI] componentStyles is required.');
|
|
145
|
-
}
|
|
146
|
-
return styles;
|
|
147
|
-
}
|
|
148
|
-
export function validateComponentStylesRegistration(styles, document = globalThis.document) {
|
|
149
|
-
if (!styles)
|
|
150
|
-
return;
|
|
151
|
-
const catalog = catalogFor(document);
|
|
152
|
-
for (const id of Object.keys(styles.resources)) {
|
|
153
|
-
const current = catalog.resources.get(id);
|
|
154
|
-
if (current && !sameComponentStyleResource(current, styles.resources[id])) {
|
|
155
|
-
throw new Error(`[WebUI] Conflicting component style resource "${id}".`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
for (const root of Object.keys(styles.closures)) {
|
|
159
|
-
const current = catalog.closures.get(root);
|
|
160
|
-
const next = styles.closures[root];
|
|
161
|
-
if (current && !sameComponentStyleClosure(current, next)) {
|
|
162
|
-
throw new Error(`[WebUI] Conflicting component style closure "${root}".`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
export function registerPreparedComponentStyles(styles, document = globalThis.document) {
|
|
167
|
-
const catalog = catalogFor(document);
|
|
168
|
-
for (const id of Object.keys(styles.resources)) {
|
|
169
|
-
if (catalog.resources.has(id))
|
|
170
|
-
continue;
|
|
171
|
-
const resource = styles.resources[id];
|
|
172
|
-
catalog.resources.set(id, resource);
|
|
173
|
-
}
|
|
174
|
-
for (const root of Object.keys(styles.closures)) {
|
|
175
|
-
if (!catalog.closures.has(root)) {
|
|
176
|
-
catalog.closures.set(root, styles.closures[root]);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
export function registerComponentStyles(value, document = globalThis.document) {
|
|
181
|
-
const styles = requireComponentStyles(value);
|
|
182
|
-
validateComponentStylesRegistration(styles, document);
|
|
183
|
-
registerPreparedComponentStyles(styles, document);
|
|
184
|
-
}
|
|
185
|
-
export function hasRegisteredComponentStyleResource(id, document = globalThis.document) {
|
|
186
|
-
return catalogFor(document).resources.has(id);
|
|
187
|
-
}
|
|
188
|
-
export function hasComponentStyleWork(rootId, document = globalThis.document) {
|
|
189
|
-
return (catalogFor(document).closures.get(rootId)?.length ?? 0) > 0;
|
|
190
|
-
}
|
|
191
40
|
function targetInstalledResources(target) {
|
|
192
41
|
let targetInstalled = installed.get(target);
|
|
193
42
|
if (!targetInstalled) {
|
|
@@ -362,9 +211,7 @@ function installModuleImportMap(target, specifier, css) {
|
|
|
362
211
|
const nonce = readNonce(target);
|
|
363
212
|
if (nonce)
|
|
364
213
|
script.nonce = nonce;
|
|
365
|
-
script
|
|
366
|
-
imports: { [specifier]: `data:text/css,${encodeURIComponent(css)}` },
|
|
367
|
-
});
|
|
214
|
+
setImportMapContent(script, specifier, css, owner.defaultView);
|
|
368
215
|
owner.head.appendChild(script);
|
|
369
216
|
installedSpecifiers.add(specifier);
|
|
370
217
|
}
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isStreamingHydrationMode } from './streaming-mode.js';
|
|
1
2
|
let pendingCount = 0;
|
|
2
3
|
let started = false;
|
|
3
4
|
let completed = false;
|
|
@@ -74,6 +75,9 @@ function tryComplete() {
|
|
|
74
75
|
streamingGateAborted ||
|
|
75
76
|
pendingCount !== 0)
|
|
76
77
|
return;
|
|
78
|
+
if (!streamingGateActive && hasBrowserDocument && isStreamingHydrationMode()) {
|
|
79
|
+
streamingGateActive = true;
|
|
80
|
+
}
|
|
77
81
|
if (!started && !streamingGateActive)
|
|
78
82
|
return;
|
|
79
83
|
if (streamingGateActive &&
|
package/dist/static-host.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { consumePendingParentState, TemplateElement, } from './template-element.js';
|
|
2
2
|
import { hasComponentStyleWork } from './element/styles.js';
|
|
3
|
-
import { getTemplateRegistry } from './template.js';
|
|
3
|
+
import { getTemplateRegistry, installTemplateDefinitionPreparation, } from './template.js';
|
|
4
4
|
import { templateNeedsStaticHost } from './template-roots.js';
|
|
5
5
|
import { ACTIVATION_STATIC_HOST_OPT_OUT, isStreamingHydrationMode, STREAMED_HOST_ATTR, STREAMING_BOUNDARY_ACTIVATE, } from './streaming-mode.js';
|
|
6
6
|
import { TEMPLATES_REGISTERED_EVENT, templateRegistrationDetail, } from './template-events.js';
|
|
@@ -97,6 +97,7 @@ export function installTemplateElementRuntime() {
|
|
|
97
97
|
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
98
98
|
return;
|
|
99
99
|
runtimeInstalled = true;
|
|
100
|
+
installTemplateDefinitionPreparation(null);
|
|
100
101
|
window.addEventListener(TEMPLATES_REGISTERED_EVENT, (event) => {
|
|
101
102
|
const detail = templateRegistrationDetail(event);
|
|
102
103
|
if (!detail?.templates)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { registerTemplateData } from './template.js';
|
|
2
|
-
import { registerComponentStyles } from './element/
|
|
1
|
+
import { registerTemplateData } from './template-registry.js';
|
|
2
|
+
import { registerComponentStyles } from './element/style-catalog.js';
|
|
3
3
|
export function applyBoundaryBootstrap(bootstrap) {
|
|
4
4
|
registerComponentStyles(bootstrap.componentStyles);
|
|
5
5
|
if (bootstrap.templates)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { markLateActivationPending, settleLateActivation, } from './lifecycle.js';
|
|
2
2
|
import { abandonDeferredDescendants, abandonDeferredElement, removeStreamingAttributes, } from './streaming-cleanup.js';
|
|
3
3
|
import { firstNodeWithin, MAX_ELEMENTS_PER_BOUNDARY, MAX_MARKER_SCAN_NODES, nextAfterSubtreeWithin, nextWithinRoot, STREAMING_ENCLOSING_SPAN_ATTR, streamingErrorMessage, } from './streaming-dom.js';
|
|
4
|
-
import { ACTIVATION_ACTIVATED, ACTIVATION_ANCESTOR_BARRIER, ACTIVATION_MISSING_TEMPLATE, ACTIVATION_STATIC_HOST_OPT_OUT,
|
|
4
|
+
import { ACTIVATION_ACTIVATED, ACTIVATION_ANCESTOR_BARRIER, ACTIVATION_MISSING_TEMPLATE, ACTIVATION_STATIC_HOST_OPT_OUT, registerStreamingRootResume, STREAMED_HOST_ATTR, STREAMING_BOUNDARY_ACTIVATE, } from './streaming-mode.js';
|
|
5
5
|
import { applyStateUpdate } from './streaming-state.js';
|
|
6
|
+
import { templateNeedsStaticHost } from './template-roots.js';
|
|
7
|
+
import { invalidateTemplateHostRuntime, loadTemplateHostRuntime, resetTemplateHostRuntimeForTests, } from './streaming-template-hosts.js';
|
|
6
8
|
export const ELEMENT_IGNORED = 10;
|
|
7
9
|
export const ELEMENT_DEFERRED = 11;
|
|
8
10
|
export const ELEMENT_LIMIT_FAILURE = 12;
|
|
@@ -20,9 +22,11 @@ const pendingTagWaiters = new Map();
|
|
|
20
22
|
const pendingBarrierRoots = new Set();
|
|
21
23
|
let pendingUndefinedRoots = 0;
|
|
22
24
|
let activationGeneration = 0;
|
|
25
|
+
let templateHostRuntimeRequested = false;
|
|
23
26
|
let failureHandler = null;
|
|
24
27
|
let invalidActivationOutcome;
|
|
25
|
-
|
|
28
|
+
let pendingRootRecords;
|
|
29
|
+
registerStreamingRootResume(resumeRetainedElement);
|
|
26
30
|
export function configureStreamingFailureHandler(handler) {
|
|
27
31
|
failureHandler = handler;
|
|
28
32
|
}
|
|
@@ -89,23 +93,45 @@ function activateMarkedElement(el, state, updates, bypass) {
|
|
|
89
93
|
customElements
|
|
90
94
|
.whenDefined(tag)
|
|
91
95
|
.then(() => onTagDefined(tag, generation));
|
|
96
|
+
requestTemplateHostRuntime(tag, generation);
|
|
92
97
|
}
|
|
93
98
|
if (!waiter.roots.has(el)) {
|
|
94
99
|
waiter.roots.add(el);
|
|
95
100
|
pendingUndefinedRoots++;
|
|
96
101
|
}
|
|
97
|
-
el[PENDING_ROOT_CONNECTED] = resumePendingRoot;
|
|
98
102
|
return ELEMENT_DEFERRED;
|
|
99
103
|
}
|
|
104
|
+
function resumeRetainedElement(el) {
|
|
105
|
+
if (!hasPendingRecord(el))
|
|
106
|
+
return false;
|
|
107
|
+
if (pendingBarrierRoots.has(el))
|
|
108
|
+
resumeBarrierRoot.call(el);
|
|
109
|
+
else
|
|
110
|
+
resumePendingRoot.call(el);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
function requestTemplateHostRuntime(tag, generation) {
|
|
114
|
+
if (templateHostRuntimeRequested)
|
|
115
|
+
return;
|
|
116
|
+
const meta = window.__webui?.templates?.[tag];
|
|
117
|
+
if (!meta || !templateNeedsStaticHost(meta))
|
|
118
|
+
return;
|
|
119
|
+
if (window.__webui?.templateHostExclusions?.has(tag))
|
|
120
|
+
return;
|
|
121
|
+
templateHostRuntimeRequested = true;
|
|
122
|
+
void loadTemplateHostRuntime()?.catch((error) => {
|
|
123
|
+
if (generation === activationGeneration) {
|
|
124
|
+
fail(`failed to load template-host runtime: ${streamingErrorMessage(error)}`);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
100
128
|
function deferBehindBarrier(el, state, updates, bypass) {
|
|
101
129
|
stashPendingRecord(el, state, updates, bypass);
|
|
102
130
|
pendingBarrierRoots.add(el);
|
|
103
|
-
el[PENDING_ROOT_CONNECTED] = resumeBarrierRoot;
|
|
104
131
|
}
|
|
105
132
|
function activatePendingBarrierRoot(el) {
|
|
106
133
|
if (!pendingBarrierRoots.delete(el))
|
|
107
134
|
return ELEMENT_IGNORED;
|
|
108
|
-
delete el[PENDING_ROOT_CONNECTED];
|
|
109
135
|
const record = takePendingRecord(el);
|
|
110
136
|
const updates = releaseUpdates(record);
|
|
111
137
|
try {
|
|
@@ -180,7 +206,6 @@ function resumePendingRoot() {
|
|
|
180
206
|
if (!waiter ||
|
|
181
207
|
waiter.generation !== activationGeneration ||
|
|
182
208
|
!waiter.roots.has(this)) {
|
|
183
|
-
delete this[PENDING_ROOT_CONNECTED];
|
|
184
209
|
return;
|
|
185
210
|
}
|
|
186
211
|
activatePendingRoot(tag, waiter, this);
|
|
@@ -189,7 +214,6 @@ function activatePendingRoot(tag, waiter, el) {
|
|
|
189
214
|
if (!waiter.roots.delete(el))
|
|
190
215
|
return;
|
|
191
216
|
pendingUndefinedRoots--;
|
|
192
|
-
delete el[PENDING_ROOT_CONNECTED];
|
|
193
217
|
const record = takePendingRecord(el);
|
|
194
218
|
const updates = releaseUpdates(record);
|
|
195
219
|
const state = record?.state;
|
|
@@ -309,6 +333,8 @@ function abandonDeferredTree(el) {
|
|
|
309
333
|
abandonDeferredElement(el);
|
|
310
334
|
}
|
|
311
335
|
export function abandonPendingWaiters() {
|
|
336
|
+
activationGeneration++;
|
|
337
|
+
invalidateTemplateHostRuntime();
|
|
312
338
|
if (pendingBarrierRoots.size !== 0) {
|
|
313
339
|
for (const el of pendingBarrierRoots)
|
|
314
340
|
clearPendingRoot(el);
|
|
@@ -323,28 +349,30 @@ export function abandonPendingWaiters() {
|
|
|
323
349
|
pendingTagWaiters.clear();
|
|
324
350
|
}
|
|
325
351
|
pendingUndefinedRoots = 0;
|
|
352
|
+
pendingRootRecords = undefined;
|
|
326
353
|
}
|
|
327
354
|
function clearPendingRoot(el) {
|
|
328
355
|
releaseUpdates(takePendingRecord(el));
|
|
329
|
-
delete el[PENDING_ROOT_CONNECTED];
|
|
330
356
|
abandonDeferredTree(el);
|
|
331
357
|
}
|
|
332
358
|
function stashPendingRecord(el, state, updates, bypass) {
|
|
333
|
-
el
|
|
359
|
+
(pendingRootRecords ??= new WeakMap()).set(el, {
|
|
334
360
|
state,
|
|
335
361
|
updates,
|
|
336
362
|
bypass,
|
|
337
|
-
};
|
|
363
|
+
});
|
|
338
364
|
if (updates)
|
|
339
365
|
updates.pendingRoots++;
|
|
340
366
|
}
|
|
341
367
|
function hasPendingRecord(el) {
|
|
342
|
-
return
|
|
368
|
+
return pendingRootRecords?.has(el) ?? false;
|
|
343
369
|
}
|
|
344
370
|
function takePendingRecord(el) {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
371
|
+
const record = pendingRootRecords?.get(el);
|
|
372
|
+
pendingRootRecords?.delete(el);
|
|
373
|
+
if (pendingUndefinedRoots === 0 && pendingBarrierRoots.size === 0) {
|
|
374
|
+
pendingRootRecords = undefined;
|
|
375
|
+
}
|
|
348
376
|
return record;
|
|
349
377
|
}
|
|
350
378
|
function releaseUpdates(record) {
|
|
@@ -384,7 +412,8 @@ function invokeActivationHook(el, state, bypass) {
|
|
|
384
412
|
}
|
|
385
413
|
export function resetDeferredActivationForTests() {
|
|
386
414
|
abandonPendingWaiters();
|
|
387
|
-
|
|
415
|
+
templateHostRuntimeRequested = false;
|
|
416
|
+
resetTemplateHostRuntimeForTests();
|
|
388
417
|
}
|
|
389
418
|
export function pendingTagWaiterCountForTests() {
|
|
390
419
|
return pendingTagWaiters.size;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { beginStreamingGate } from './lifecycle.js';
|
|
2
|
-
import { installTemplateElementRuntime } from './static-host.js';
|
|
3
2
|
import { enqueueStreamingSentinel, installStreamingTruncationGuard, } from './streaming-coordinator.js';
|
|
4
3
|
import { isStreamingHydrationMode } from './streaming-mode.js';
|
|
4
|
+
import { installTemplateDefinitionPreparation } from './template-registry.js';
|
|
5
|
+
import { prepareTemplateDefinitions } from './streaming-template-hosts.js';
|
|
5
6
|
const SENTINEL_TAG = 'webui-hydrate';
|
|
6
7
|
let installed = false;
|
|
7
8
|
class WebUiHydrateSentinel extends HTMLElement {
|
|
@@ -13,12 +14,12 @@ export function installStreamingCoordinator() {
|
|
|
13
14
|
if (installed || !isStreamingHydrationMode())
|
|
14
15
|
return;
|
|
15
16
|
installed = true;
|
|
17
|
+
installTemplateDefinitionPreparation(prepareTemplateDefinitions);
|
|
16
18
|
if (!customElements.get(SENTINEL_TAG)) {
|
|
17
19
|
customElements.define(SENTINEL_TAG, WebUiHydrateSentinel);
|
|
18
20
|
}
|
|
19
21
|
beginStreamingGate();
|
|
20
22
|
installStreamingTruncationGuard();
|
|
21
|
-
installTemplateElementRuntime();
|
|
22
23
|
}
|
|
23
24
|
export function resetStreamingInstallForTests() {
|
|
24
25
|
installed = false;
|
package/dist/streaming-mode.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const STREAMING_BOUNDARY_ACTIVATE: unique symbol;
|
|
2
|
-
export declare
|
|
2
|
+
export declare let resumeStreamingRoot: ((element: Element) => boolean) | undefined;
|
|
3
|
+
export declare function registerStreamingRootResume(resume: typeof resumeStreamingRoot): void;
|
|
3
4
|
export declare const STREAMED_HOST_ATTR = "data-ws";
|
|
4
5
|
export declare const ACTIVATION_ACTIVATED = 1;
|
|
5
6
|
export declare const ACTIVATION_STATIC_HOST_OPT_OUT = 2;
|