@microsoft/webui-framework 0.0.24 → 0.0.26
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 +209 -53
- package/dist/component-asset/asset.d.ts +4 -2
- package/dist/component-asset/asset.js +19 -3
- package/dist/component-asset/generated-manifest.d.ts +1 -0
- package/dist/component-asset/generated-manifest.js +32 -0
- package/dist/component-asset/loader.d.ts +2 -1
- package/dist/component-asset/loader.js +98 -22
- package/dist/component-asset/manifest.d.ts +2 -1
- package/dist/component-asset.d.ts +1 -1
- package/dist/component-asset.js +12 -2
- package/dist/element/link-styles.d.ts +20 -0
- package/dist/element/link-styles.js +1030 -0
- package/dist/element/markers.d.ts +4 -0
- package/dist/element/markers.js +60 -5
- package/dist/element/styles.d.ts +39 -1
- package/dist/element/styles.js +623 -13
- package/dist/element/types.d.ts +5 -3
- package/dist/element.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/interaction-hydration.d.ts +8 -0
- package/dist/interaction-hydration.js +167 -0
- package/dist/static-host.js +43 -1
- package/dist/streaming-activation.d.ts +2 -2
- package/dist/streaming-activation.js +4 -2
- package/dist/streaming-bootstrap.d.ts +2 -2
- package/dist/streaming-bootstrap.js +7 -1
- package/dist/streaming-cleanup.d.ts +1 -0
- package/dist/streaming-cleanup.js +66 -14
- package/dist/streaming-coordinator.d.ts +3 -2
- package/dist/streaming-coordinator.js +107 -47
- package/dist/streaming-deferred.d.ts +14 -4
- package/dist/streaming-deferred.js +200 -87
- package/dist/streaming-dom.d.ts +12 -3
- package/dist/streaming-dom.js +41 -27
- package/dist/streaming-mode.d.ts +5 -0
- package/dist/streaming-mode.js +4 -0
- package/dist/streaming-protocol.d.ts +19 -3
- package/dist/streaming-protocol.js +4 -3
- package/dist/streaming-spans.d.ts +9 -0
- package/dist/streaming-spans.js +200 -0
- package/dist/template-content.d.ts +17 -0
- package/dist/template-content.js +106 -0
- package/dist/template-element.d.ts +17 -2
- package/dist/template-element.js +493 -177
- package/dist/template-events.d.ts +7 -0
- package/dist/template-events.js +7 -1
- package/dist/template-types.d.ts +1 -2
- package/dist/template.d.ts +15 -8
- package/dist/template.js +79 -16
- package/package.json +9 -3
- package/dist/component-asset/resources.d.ts +0 -4
- package/dist/component-asset/resources.js +0 -83
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ This package is the browser-side runtime used by `webui build --plugin=webui`. I
|
|
|
7
7
|
- `WebUIElement` for SSR hydration and client-created elements
|
|
8
8
|
- `@observable`, `@attr`, and `@volatile` decorators
|
|
9
9
|
- direct DOM binding updates
|
|
10
|
-
-
|
|
10
|
+
- Shadow-default components with opt-in global Light and authored Shadow islands
|
|
11
11
|
- SSR state seeding
|
|
12
12
|
|
|
13
13
|
If you are building WebUI apps in this repo, this is the component model used by examples like `examples/app/todo-webui`, `examples/app/commerce`, and `examples/app/contact-book-manager`.
|
|
@@ -79,7 +79,9 @@ CounterCard.define('counter-card');
|
|
|
79
79
|
<button @click="{increment()}">Increment</button>
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Unwrapped components default to Shadow. A `dom: "light"` build renders them as
|
|
83
|
+
Light while preserving any sole top-level
|
|
84
|
+
`<template shadowrootmode="open">` component as Shadow.
|
|
83
85
|
|
|
84
86
|
### Use it from your page
|
|
85
87
|
|
|
@@ -128,10 +130,9 @@ rendered block size of one instance; WebUI emits it as
|
|
|
128
130
|
`contain-intrinsic-block-size: auto 18rem` before first layout. The SSR DOM
|
|
129
131
|
remains present, searchable, and accessible while the browser skips offscreen
|
|
130
132
|
style, layout, and paint work. The generated policy applies to instances in the
|
|
131
|
-
document, Light DOM, and standard Shadow DOM components. A
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
the boundary.
|
|
133
|
+
document, Light DOM, and standard Shadow DOM components. A Light component
|
|
134
|
+
inside a Shadow root receives the rule through its precomputed style
|
|
135
|
+
closure, which delivers the stylesheet into that root under every CSS strategy.
|
|
135
136
|
|
|
136
137
|
Import the optional coordinator entry once before component modules:
|
|
137
138
|
|
|
@@ -165,6 +166,52 @@ If the optional entry or `IntersectionObserver` is unavailable, hydration falls
|
|
|
165
166
|
back to eager; `content-visibility` remains browser-managed. See
|
|
166
167
|
[Lazy Hydration](https://microsoft.github.io/webui/guide/concepts/hydration#lazy-hydration).
|
|
167
168
|
|
|
169
|
+
Router applications should author `<template w-hydrate="interaction">` and use
|
|
170
|
+
the framework-agnostic `@microsoft/webui-router/preload.js` handle. FAST and
|
|
171
|
+
other hydration runtimes use that same handle with their own readiness signal.
|
|
172
|
+
Non-router apps use the lower-level framework entry:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import {
|
|
176
|
+
installInteractionHydration,
|
|
177
|
+
isInteractionReplay,
|
|
178
|
+
} from
|
|
179
|
+
'@microsoft/webui-framework/interaction-hydration.js';
|
|
180
|
+
|
|
181
|
+
installInteractionHydration({
|
|
182
|
+
load: () => import('./components.js'),
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Pointer-down, focus, and keyboard intent starts `load()` without cancellation.
|
|
187
|
+
An unmodified primary click waits and replays on its original composed-path
|
|
188
|
+
target; `load()` must resolve only after listeners are ready. Hover, modified
|
|
189
|
+
clicks, and previously cancelled clicks do not replay. Use
|
|
190
|
+
`isInteractionReplay(event)` to deduplicate ancestor capture work.
|
|
191
|
+
|
|
192
|
+
This opt-in trades first-interaction latency for lower startup JS and heap.
|
|
193
|
+
Prefer an eager root with lazy descendants when request-to-hydrated time matters.
|
|
194
|
+
Synthetic replay cannot preserve transient user activation or target controls
|
|
195
|
+
inside closed shadow roots; hydrate those paths eagerly.
|
|
196
|
+
|
|
197
|
+
One offscreen singleton boundary can retain browser rendering deferral while
|
|
198
|
+
also deferring its module graph:
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<template
|
|
202
|
+
w-render="lazy"
|
|
203
|
+
w-reserve-block-size="18rem"
|
|
204
|
+
w-hydrate="interaction"
|
|
205
|
+
>
|
|
206
|
+
<!-- Component content -->
|
|
207
|
+
</template>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Do not use the combined form for repeated items. A visible app root gains no
|
|
211
|
+
rendering benefit from `content-visibility`; keep interaction on that root and
|
|
212
|
+
put `w-render="lazy"` on offscreen descendants.
|
|
213
|
+
|
|
214
|
+
|
|
168
215
|
### Build with the WebUI plugin
|
|
169
216
|
|
|
170
217
|
```bash
|
|
@@ -199,12 +246,35 @@ authored `<boundary>` directives through
|
|
|
199
246
|
`@microsoft/webui-framework` entry has no dependency on the coordinator, so
|
|
200
247
|
normal applications pay no streaming bundle or initialization cost.
|
|
201
248
|
|
|
202
|
-
|
|
249
|
+
Boundaries may be authored in entries and reusable components, including
|
|
250
|
+
runtime conditions, outlets, and selected routes. A boundary-bearing subtree
|
|
251
|
+
reached from a `<for>` body fails the build with `boundary-in-repeat`. A whole
|
|
252
|
+
`<for>` may sit inside one boundary, and boundaries before or after a `<for>`
|
|
253
|
+
are valid. A component-local boundary uses a generated parent span, so an early
|
|
254
|
+
compiler-marked child can hydrate before the opaque parent tail in light or
|
|
255
|
+
shadow DOM. The server's boundary-only `resume` emits that checkpoint first;
|
|
256
|
+
`advance` emits the following parent tail, with no sibling boundary workaround.
|
|
257
|
+
Authored boundaries cannot nest.
|
|
258
|
+
|
|
259
|
+
Span resolution is entirely coordinator-owned: the generated `data-ws-span` and
|
|
260
|
+
`data-ws-enclosing` attributes, and the open-span registry that pairs them, live
|
|
261
|
+
only in the opt-in streaming entry. It resolves the one ancestor an entitled
|
|
262
|
+
early child may skip and passes that element to the activation hook, which
|
|
263
|
+
compares it by identity. The always-shipped entry therefore carries no span
|
|
264
|
+
attribute name and no span bookkeeping at all.
|
|
265
|
+
|
|
266
|
+
Each runtime occurrence receives an ephemeral state object directly during
|
|
203
267
|
activation. The coordinator does not publish that state to
|
|
204
|
-
`window.__webui.state`, and it removes generated checkpoint
|
|
205
|
-
commit.
|
|
206
|
-
|
|
207
|
-
|
|
268
|
+
`window.__webui.state`, and it removes generated checkpoint and span
|
|
269
|
+
scaffolding after commit. Updates apply state to retained roots and never insert
|
|
270
|
+
markup or rerun hydration.
|
|
271
|
+
|
|
272
|
+
The browser reads version-2
|
|
273
|
+
`[2, sequence, kind, target, payload]` records for final checkpoints,
|
|
274
|
+
updatable checkpoints, updates, span completions, and terminal. Every commit
|
|
275
|
+
also emits a `performance.mark()` - `webui:boundary:<id>`,
|
|
276
|
+
`webui:boundary:<id>:update`, `webui:span:<id>`, or
|
|
277
|
+
`webui:streaming:terminal` - which needs no flag or listener.
|
|
208
278
|
Set `window.__WEBUI_STREAMING_DEBUG__ = true` only when tooling needs the live
|
|
209
279
|
`webui:boundary-hydrated` event as well.
|
|
210
280
|
|
|
@@ -240,9 +310,14 @@ development-only and is dead-code-eliminated from production bundles via the
|
|
|
240
310
|
|
|
241
311
|
Override the protected `hydratedCallback()` hook for work that requires the
|
|
242
312
|
component's bindings, events, and `w-ref` references to be ready. It runs
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
313
|
+
exactly once with the first successful ordinary SSR hydration, client-created
|
|
314
|
+
mount, lazy activation, deferred streamed activation, or dormant static-host
|
|
315
|
+
wake. If CSP blocks the temporary Link-mode prepaint guard, a client-created
|
|
316
|
+
mount keeps non-style content detached and delays `$ready` and this callback
|
|
317
|
+
until its native links load and the content is appended. Reactive writes made
|
|
318
|
+
while detached are reconciled immediately before append. A synchronous
|
|
319
|
+
disconnect/reconnect preserves the pending mount; a lasting disconnect cancels
|
|
320
|
+
it. The callback's once-latch is set before author code runs, so a thrown
|
|
246
321
|
callback is not retried on reconnect.
|
|
247
322
|
|
|
248
323
|
`connectedCallback()` remains a native per-connection lifecycle. On ordinary
|
|
@@ -253,22 +328,29 @@ post-hydration signal. Descendants must not structurally mutate a containing
|
|
|
253
328
|
component's SSR subtree before it hydrates, because hydration relies on stable
|
|
254
329
|
compiled paths.
|
|
255
330
|
|
|
256
|
-
###
|
|
257
|
-
|
|
258
|
-
The `--dom` flag controls how the server renders component content:
|
|
331
|
+
### Light and Shadow DOM
|
|
259
332
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
333
|
+
An unwrapped component receives a generated open Shadow root by default. In a
|
|
334
|
+
`dom: "light"` build it renders as direct children of its host. A component
|
|
335
|
+
whose sole top-level element is a bare `<template>` explicitly renders as Light
|
|
336
|
+
and is unwrapped, even under the Shadow fallback. A sole
|
|
337
|
+
`<template shadowrootmode="open">` remains Shadow in either mode. Templates with
|
|
338
|
+
attributes and policy wrappers do not select a mode. Closed roots and invalid
|
|
339
|
+
values or placement are build errors; `<slot>` is rejected only for effective
|
|
340
|
+
Light components.
|
|
264
341
|
|
|
265
342
|
The runtime auto-detects which mode was used at hydration time:
|
|
266
343
|
- If a `shadowRoot` already exists → shadow DOM SSR path
|
|
267
344
|
- If `childNodes` exist but no shadow root → light DOM SSR path
|
|
268
345
|
- If neither → client-created path (uses `meta.sd` to decide)
|
|
269
346
|
|
|
270
|
-
Light
|
|
271
|
-
|
|
347
|
+
Light components use authored/global ordinary CSS in the owning CSS tree.
|
|
348
|
+
Shadow components keep native Shadow scoping. `:host`, `:host-context`, and
|
|
349
|
+
`::slotted` fail in effective Light CSS.
|
|
350
|
+
The Link, Style, and Module delivery strategies all support both modes.
|
|
351
|
+
|
|
352
|
+
In a Light build, add open wrappers to slot, native-encapsulation, or CSS-heavy
|
|
353
|
+
frequently restyled components.
|
|
272
354
|
|
|
273
355
|
---
|
|
274
356
|
|
|
@@ -318,15 +400,38 @@ The asset graph keeps entry-owned templates external, leaves single-root
|
|
|
318
400
|
dependencies inline, and emits dependencies shared by multiple roots once as
|
|
319
401
|
flat dynamic chunks. Component assets cannot be combined with `<route>`. Load
|
|
320
402
|
the normal entry bundle first so external prerequisites are registered.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
403
|
+
Current assets require version 3 and an atomically validated
|
|
404
|
+
`componentStyles` catalog; any other version is rejected as unsupported.
|
|
405
|
+
|
|
406
|
+
The compiler records final Link stylesheet filenames in the protocol. For
|
|
407
|
+
Shadow builds, the handler emits that finite manifest as inert JSON in the
|
|
408
|
+
document head; body-only host protocols emit it at the start of their rendered
|
|
409
|
+
body fragment. Light builds emit the same hrefs as deduplicated document
|
|
410
|
+
stylesheets because their CSS must apply globally.
|
|
411
|
+
Automatic Shadow intent preloading therefore requires HTML rendered through the
|
|
412
|
+
WebUI handler or `Protocol`, which emits `#webui-component-assets`. A shell that
|
|
413
|
+
uses build artifacts without rendering the protocol still mounts safely through
|
|
414
|
+
the native stylesheet guard, but it does not receive the earlier
|
|
415
|
+
compiler-owned style preload.
|
|
416
|
+
Shared chunk and content-hashed stylesheet filenames are generated and must not
|
|
417
|
+
be copied into authored code. Each root asset carries its own dynamic imports;
|
|
418
|
+
`--metafile` remains available for analysis and build tooling.
|
|
419
|
+
|
|
420
|
+
In Shadow builds, `preload(tag)` reads the compiler-owned style metadata and
|
|
421
|
+
starts Link styles beside the authored root asset, component module, and
|
|
422
|
+
optional data request. Only the stable root asset URL remains in application
|
|
423
|
+
code; shared chunks and content-hashed CSS stay compiler-owned.
|
|
424
|
+
|
|
425
|
+
Bundler-generated loaders can use `asset: () => import('./settings-dialog.webui.js')`
|
|
426
|
+
instead of a URL. This keeps chunk naming and public-path rewriting inside the
|
|
427
|
+
bundler while preserving the same `preload(tag)` and `create(tag)` lifecycle.
|
|
428
|
+
Concurrent roots share in-flight chunk and stylesheet work. `create(tag)`
|
|
429
|
+
creates the element after template/module work is ready and does not block on
|
|
430
|
+
optional data by default. Use
|
|
431
|
+
`create(tag, { awaitData: true, dataTimeoutMs: 150 })` only when a component
|
|
432
|
+
must wait briefly for state before mounting. A rejected root asset or authored
|
|
433
|
+
module is evicted from the registry so a later `preload(tag)` or `create(tag)`
|
|
434
|
+
retries it.
|
|
330
435
|
|
|
331
436
|
### `@observable`
|
|
332
437
|
|
|
@@ -445,10 +550,11 @@ resource-constrained devices.
|
|
|
445
550
|
`$update(path)` only visits bindings that reference the changed property.
|
|
446
551
|
Everything else is skipped via a per-path index built once at hydration time.
|
|
447
552
|
|
|
448
|
-
2. **Zero allocations during updates.**
|
|
553
|
+
2. **Zero framework allocations during ordinary updates.**
|
|
449
554
|
Targeted updates are a single `Map.get()` → direct array iteration.
|
|
450
555
|
No intermediate arrays, no object creation, no spread operators on the
|
|
451
|
-
update path.
|
|
556
|
+
update path. A changed raw HTML binding necessarily parses and creates its
|
|
557
|
+
replacement DOM nodes inside its pre-resolved ownership range.
|
|
452
558
|
|
|
453
559
|
3. **Parse once, clone forever.**
|
|
454
560
|
Compiled template HTML is parsed via `innerHTML` once per component tag
|
|
@@ -527,9 +633,8 @@ is driven by data (template metadata + state values), not code. Any language
|
|
|
527
633
|
that can read the compiled metadata and produce HTML can serve as the SSR
|
|
528
634
|
backend. No comment markers or data attributes are needed — the runtime
|
|
529
635
|
resolves ordinary buffered SSR nodes via the lockstep hydration walk.
|
|
530
|
-
Progressive streaming uses temporary checkpoint
|
|
531
|
-
|
|
532
|
-
commit.
|
|
636
|
+
Progressive streaming uses temporary checkpoint and generated-span scaffolding
|
|
637
|
+
to activate complete runtime regions; it removes that scaffolding after commit.
|
|
533
638
|
|
|
534
639
|
### Build → Serve → Hydrate → Update
|
|
535
640
|
|
|
@@ -664,7 +769,6 @@ interface TemplateMeta {
|
|
|
664
769
|
r?: [collection, itemVar, blockIdx, slot][]; // Repeat blocks
|
|
665
770
|
eg?: [event, [[handler, argSpecs, targetIndex, usesEvent?]]][]; // Events
|
|
666
771
|
b?: TemplateBlockMeta[]; // Nested block metadata
|
|
667
|
-
sa?: string; // Adopted stylesheet specifier
|
|
668
772
|
sd?: 1; // Shadow DOM flag for client-created
|
|
669
773
|
re?: [event, handler, argSpecs][]; // Root-level events
|
|
670
774
|
tr?: string[]; // Template state roots
|
|
@@ -770,7 +874,13 @@ State seeding uses `window.__webui.state` loaded from the server-emitted
|
|
|
770
874
|
`@observable` and `@attr` keys from reachable authored components select
|
|
771
875
|
initial state; HTML-only dormant components and authored template-only roots
|
|
772
876
|
contribute no startup keys. Without projection metadata, the server preserves
|
|
773
|
-
full state.
|
|
877
|
+
full state. The startup state is not a permanent application store. Eager
|
|
878
|
+
components consume it during hydration, lazy components copy their projected
|
|
879
|
+
roots before deferral, and the framework releases the global handoff when
|
|
880
|
+
`webui:hydration-complete` fires on a page without a route chain. Routed pages
|
|
881
|
+
retain it for router-owned lazy startup. Normalized template closure entries are
|
|
882
|
+
released as soon as their functions are embedded in template metadata. During
|
|
883
|
+
`$mount()`, `$applySSRState()` writes matching decorated keys
|
|
774
884
|
directly to observable backing fields before any bindings are wired:
|
|
775
885
|
|
|
776
886
|
```mermaid
|
|
@@ -829,10 +939,50 @@ The framework supports three CSS delivery strategies:
|
|
|
829
939
|
|
|
830
940
|
| Strategy | How it works |
|
|
831
941
|
|----------|-------------|
|
|
832
|
-
| **Link** | `<link>` tag baked into `meta.h
|
|
833
|
-
| **
|
|
942
|
+
| **Link** | `<link>` tag baked into `meta.h`; the first client-created shadow instance authorizes shared constructable sheets through native loading, then warm instances adopt them before paint |
|
|
943
|
+
| **Style** | `<style>` tag baked into `meta.h` — no external request |
|
|
834
944
|
| **Module** | `<script type="importmap">{"imports":{"tag-name":"data:text/css,..."}}</script>` in the HTML payload registers the CSS as a module under `tag-name`. The framework imports it via `import(tag, { with: { type: 'css' } })` and applies the resulting `CSSStyleSheet` via `adoptedStyleSheets` for shadow DOM isolation |
|
|
835
945
|
|
|
946
|
+
Link promotion is progressive enhancement. Registration performs a bounded
|
|
947
|
+
`<link rel="preload" as="style">` using the stylesheet's CORS, integrity, and
|
|
948
|
+
referrer-policy attributes, so the native stylesheet link can reuse the same
|
|
949
|
+
style-destination request. Preload bytes are never applied directly, and a
|
|
950
|
+
preload cannot inspect response MIME type; the first client instance's native
|
|
951
|
+
link remains authoritative for CSP, MIME, integrity, CORS, redirects, and
|
|
952
|
+
service workers. The framework releases the instance's paint guard as soon as
|
|
953
|
+
every original link loads, before constructing the shared ordered set from
|
|
954
|
+
native CSSOM. It then adopts that set before existing sheets with one assignment
|
|
955
|
+
and shares it with later instances. Promoted links remain disabled in place so
|
|
956
|
+
reconnect hydration retains the compiled element indexes. Classes with an authored
|
|
957
|
+
`hydratedCallback()` still take the guarded native path on warm mounts, allowing
|
|
958
|
+
lifecycle-added `<style>` elements to preserve native cascade order. If
|
|
959
|
+
construction is unsupported, native CSSOM or
|
|
960
|
+
unredirected, non-service-worker timing is unavailable, or `@import`, unsafe URL
|
|
961
|
+
syntax, link attributes, bindings, compiled events, or authored DOM `<style>`
|
|
962
|
+
cascade semantics cannot be preserved, the original links remain. An anonymous
|
|
963
|
+
first-layer shadow guard prevents component CSS from overriding the loading
|
|
964
|
+
gate and cancels host transitions before hiding. When CSP blocks that guard,
|
|
965
|
+
non-style content, `$ready`, and `hydratedCallback()` remain deferred; current
|
|
966
|
+
reactive state is reconciled before append. If that reconciliation changes a
|
|
967
|
+
request-affecting bound link value, the framework waits for the replacement
|
|
968
|
+
native load before append. Disconnecting permanently cancels
|
|
969
|
+
the pending mount, while a synchronous reconnect preserves it. A link error
|
|
970
|
+
is reported, leaves the browser's native links in place, releases the temporary
|
|
971
|
+
guard, and completes deferred hydration. The component may be unstyled after a
|
|
972
|
+
definitive stylesheet failure, but it remains visible and usable. SSR hydration,
|
|
973
|
+
Style, Module, and authored/global Light DOM behavior are unchanged.
|
|
974
|
+
|
|
975
|
+
### Intent-time Link preloading for component assets
|
|
976
|
+
|
|
977
|
+
In a Shadow build, call `assets.preload(tag)` from pointer, focus, or other
|
|
978
|
+
intent handling. The framework reads the compiler-owned head manifest, so
|
|
979
|
+
application code never derives or hardcodes content-hashed CSS names. Link
|
|
980
|
+
styles begin before the root asset executes, and later template registration
|
|
981
|
+
reuses the same style-destination request. Repeated intent is deduplicated. An
|
|
982
|
+
intent that never mounts the component may still produce the browser's standard
|
|
983
|
+
unused-preload warning. Light builds load component-asset CSS as document
|
|
984
|
+
stylesheets at the structural head boundary instead.
|
|
985
|
+
|
|
836
986
|
CSS module stylesheets are cached so each component instance adopts the same
|
|
837
987
|
parsed sheet without re-parsing CSS. The `meta.sa` field specifies the
|
|
838
988
|
stylesheet specifier for a component.
|
|
@@ -843,7 +993,8 @@ stylesheet specifier for a component.
|
|
|
843
993
|
|
|
844
994
|
Unlike frameworks that use comment markers or data attributes to locate each
|
|
845
995
|
dynamic binding, this framework uses **compiled element indices** — each
|
|
846
|
-
binding names its element by pre-order position within its compiled section.
|
|
996
|
+
binding names its element by pre-order position within its compiled section.
|
|
997
|
+
Progressive streaming's temporary boundary and span markers locate complete
|
|
847
998
|
activation regions, not individual bindings.
|
|
848
999
|
|
|
849
1000
|
### Client-created resolution (`collectTemplateElements`)
|
|
@@ -862,24 +1013,28 @@ const target = elements[index];
|
|
|
862
1013
|
### SSR resolution (`buildSSRIndex`)
|
|
863
1014
|
|
|
864
1015
|
SSR DOM differs from the compiled template: the renderer strips inter-element
|
|
865
|
-
whitespace,
|
|
1016
|
+
whitespace, `<if>` / `<for>` bodies are rendered inline between structural
|
|
1017
|
+
markers, and raw HTML values can contribute arbitrary element runs.
|
|
866
1018
|
`buildSSRIndex` walks the SSR DOM and the compiled template DOM **in lockstep**,
|
|
867
1019
|
skipping whole marker ranges, and numbers the result the same way:
|
|
868
1020
|
|
|
869
1021
|
```typescript
|
|
870
1022
|
// Elements are paired positionally and numbered in pre-order.
|
|
871
|
-
// Structural ranges are skipped whole
|
|
872
|
-
// the
|
|
1023
|
+
// Structural and raw HTML ranges are skipped whole - that content
|
|
1024
|
+
// is not part of the enclosing section's static element numbering.
|
|
873
1025
|
// Text is the exception: whitespace stripping means text nodes do
|
|
874
|
-
// not line up, so text
|
|
1026
|
+
// not line up, so each text slot resolves from its compiled
|
|
1027
|
+
// right-hand static or marker boundary.
|
|
875
1028
|
```
|
|
876
1029
|
|
|
877
|
-
This eliminates
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1030
|
+
This eliminates annotations for ordinary bindings: no `data-w-*` attributes and
|
|
1031
|
+
no comments around escaped text. The SSR server emits five structural comments
|
|
1032
|
+
for `<if>` / `<for>` bodies plus paired `<!--wN-->` / `<!--/wN-->` markers around each raw HTML
|
|
1033
|
+
binding. Structural closing/item markers are removed after hydration. Raw HTML
|
|
1034
|
+
anchors remain so updates can replace zero or multiple direct sibling nodes
|
|
1035
|
+
without touching content outside the binding's range. These exact marker
|
|
1036
|
+
comments are framework-reserved and must not appear in the trusted raw value
|
|
1037
|
+
inside that range.
|
|
883
1038
|
|
|
884
1039
|
---
|
|
885
1040
|
|
|
@@ -897,7 +1052,8 @@ blocks on. They are removed once hydration completes.
|
|
|
897
1052
|
|
|
898
1053
|
- **No virtual DOM** — no tree copy, no diff algorithm
|
|
899
1054
|
- **No runtime template parsing** — the Rust compiler handles all syntax
|
|
900
|
-
- **No `innerHTML` on updates** —
|
|
1055
|
+
- **No parent-wide `innerHTML` on updates** — raw HTML parses into its bounded
|
|
1056
|
+
contextual range; escaped text and attributes patch direct node references
|
|
901
1057
|
- **No `querySelector` on updates** — all nodes are pre-resolved references
|
|
902
1058
|
- **No recursion in hot paths** — conditions use iterative stack evaluation
|
|
903
1059
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CompiledConditionFn, TemplateMeta } from '../template.js';
|
|
2
|
+
import { type ComponentStyles } from '../element/styles.js';
|
|
2
3
|
export interface ComponentAssetImport {
|
|
3
4
|
components: string[];
|
|
4
5
|
href: string;
|
|
@@ -6,17 +7,18 @@ export interface ComponentAssetImport {
|
|
|
6
7
|
}
|
|
7
8
|
export interface ComponentAsset {
|
|
8
9
|
type: 'webui-component-asset';
|
|
9
|
-
version:
|
|
10
|
+
version: 3;
|
|
10
11
|
kind: 'root' | 'chunk';
|
|
11
12
|
root?: string;
|
|
12
13
|
components: string[];
|
|
13
14
|
requiredComponents: string[];
|
|
14
15
|
externalComponents: string[];
|
|
15
16
|
imports: ComponentAssetImport[];
|
|
16
|
-
|
|
17
|
+
componentStyles: ComponentStyles;
|
|
17
18
|
templates: Record<string, TemplateMeta>;
|
|
18
19
|
templateFunctions?: Record<string, CompiledConditionFn[]>;
|
|
19
20
|
}
|
|
21
|
+
export declare function prepareAssetComponentStyles(value: unknown): ComponentStyles;
|
|
20
22
|
export declare function readComponentAssetModule(module: unknown): unknown;
|
|
21
23
|
export declare function validateAsset(value: unknown, expectedKind: ComponentAsset['kind']): asserts value is ComponentAsset;
|
|
22
24
|
export declare function sameComponents(left: readonly string[], right: readonly string[]): boolean;
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
import { requireComponentStyles, } from '../element/styles.js';
|
|
1
2
|
const ASSET_TYPE = 'webui-component-asset';
|
|
2
|
-
const
|
|
3
|
+
const COMPONENT_STYLES_ASSET_VERSION = 3;
|
|
4
|
+
const preparedAssetStyles = new WeakMap();
|
|
5
|
+
export function prepareAssetComponentStyles(value) {
|
|
6
|
+
if (typeof value === 'object' && value !== null) {
|
|
7
|
+
const cached = preparedAssetStyles.get(value);
|
|
8
|
+
if (cached)
|
|
9
|
+
return cached;
|
|
10
|
+
const prepared = requireComponentStyles(value);
|
|
11
|
+
preparedAssetStyles.set(value, prepared);
|
|
12
|
+
return prepared;
|
|
13
|
+
}
|
|
14
|
+
return requireComponentStyles(value);
|
|
15
|
+
}
|
|
3
16
|
export function readComponentAssetModule(module) {
|
|
4
17
|
if (!isObject(module) || !isObject(module.default)) {
|
|
5
18
|
throw new Error('[WebUI] Component asset module must default-export an asset object.');
|
|
@@ -14,9 +27,12 @@ export function validateAsset(value, expectedKind) {
|
|
|
14
27
|
if (asset.type !== ASSET_TYPE) {
|
|
15
28
|
throw new Error(`[WebUI] Invalid component asset type: ${String(asset.type)}`);
|
|
16
29
|
}
|
|
17
|
-
if (asset.version !==
|
|
30
|
+
if (asset.version !== COMPONENT_STYLES_ASSET_VERSION) {
|
|
18
31
|
throw new Error(`[WebUI] Unsupported component asset version: ${String(asset.version)}`);
|
|
19
32
|
}
|
|
33
|
+
if (asset.componentStyles === undefined) {
|
|
34
|
+
throw new Error('[WebUI] Version 3 component assets require componentStyles.');
|
|
35
|
+
}
|
|
20
36
|
if (asset.kind !== expectedKind) {
|
|
21
37
|
throw new Error(`[WebUI] Expected component asset kind "${expectedKind}", received "${String(asset.kind)}".`);
|
|
22
38
|
}
|
|
@@ -26,7 +42,7 @@ export function validateAsset(value, expectedKind) {
|
|
|
26
42
|
if (!Array.isArray(asset.imports)) {
|
|
27
43
|
throw new Error('[WebUI] Component asset imports must be an array.');
|
|
28
44
|
}
|
|
29
|
-
|
|
45
|
+
prepareAssetComponentStyles(asset.componentStyles);
|
|
30
46
|
if (!isObject(asset.templates)) {
|
|
31
47
|
throw new Error('[WebUI] Component asset templates must be an object.');
|
|
32
48
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function takeGeneratedComponentAssetStyles(tag: string): readonly string[] | undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const COMPONENT_ASSET_MANIFEST_ID = 'webui-component-assets';
|
|
2
|
+
let manifestLoaded = false;
|
|
3
|
+
export function takeGeneratedComponentAssetStyles(tag) {
|
|
4
|
+
if (typeof window !== 'object' || typeof document !== 'object')
|
|
5
|
+
return undefined;
|
|
6
|
+
const existing = window.__webui?.componentAssetStyles;
|
|
7
|
+
if (existing) {
|
|
8
|
+
const styles = existing[tag];
|
|
9
|
+
if (styles)
|
|
10
|
+
delete existing[tag];
|
|
11
|
+
return styles;
|
|
12
|
+
}
|
|
13
|
+
if (manifestLoaded)
|
|
14
|
+
return undefined;
|
|
15
|
+
const element = document.getElementById(COMPONENT_ASSET_MANIFEST_ID);
|
|
16
|
+
if (!element) {
|
|
17
|
+
manifestLoaded = true;
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const text = element.textContent;
|
|
21
|
+
element.remove();
|
|
22
|
+
const manifest = text
|
|
23
|
+
? JSON.parse(text)
|
|
24
|
+
: {};
|
|
25
|
+
const runtime = window.__webui ?? (window.__webui = {});
|
|
26
|
+
runtime.componentAssetStyles = manifest;
|
|
27
|
+
manifestLoaded = true;
|
|
28
|
+
const styles = manifest[tag];
|
|
29
|
+
if (styles)
|
|
30
|
+
delete manifest[tag];
|
|
31
|
+
return styles;
|
|
32
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ComponentAssetSource } from './manifest.js';
|
|
2
|
+
export declare function loadComponentAsset(tag: string, source: ComponentAssetSource): Promise<void>;
|