Skip to content

Commit 1864ffa

Browse files
feat: add ESM static component assets (#360)
1 parent 0754b41 commit 1864ffa

61 files changed

Lines changed: 2965 additions & 386 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
name: create-example
3+
description: Create a runnable WebUI example with app structure, theme wiring, Playwright tests, demo metadata, and demo-shell registration.
4+
---
5+
6+
# Checklist
7+
8+
1. Create the app folder with `src/`, `data/state.json` when SSR state is needed, `package.json`, `tsconfig.json`, and a concise `demo.toml`.
9+
2. Use the shared theme for example UI: add `@microsoft/webui-examples-theme`, pass `--theme=@microsoft/webui-examples-theme`, and inject `/*{{{tokens.light}}}*/` plus `/*{{{tokens.dark}}}*/` in the entry template.
10+
3. Keep `package.json` scripts consistent: `build`, `start:client`, `start:server`, `start`, `test`, and `test:update-snapshots` when Playwright applies.
11+
4. Add Playwright coverage in `tests/` and a `playwright.config.ts` using the app's fixed dev port. Prefer behavior tests over screenshots unless the UI is the point.
12+
5. Register the app in `xtask/src/e2e.rs` when it has Playwright tests. Use `pre_script: Some("build")` for custom build pipelines that the generic esbuild step cannot reproduce.
13+
6. Add `demo.toml`, copy the app into `examples/demo/Dockerfile`, and list it in `examples/README.md`.
14+
7. Run the focused app test, then `cargo xtask check`.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DESIGN.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,64 @@ emit WebUI `templates` or `templateFns`.
339339

340340
**Cache control:** The server can include `cacheControl: { staleTime: number }` in the partial response to override the client's default stale time for this specific route.
341341

342+
**Static component assets:** `webui build --emit-component-assets mail-thread,compose-page`
343+
emits CDN-loadable component asset files next to `protocol.bin`. The flag is a
344+
strict comma-separated allowlist of root component tags; every tag must be a
345+
discovered lowercase kebab-case component with WebUI template metadata. Static
346+
component asset runtimes are framework-owned: the WebUI Framework loader lives at
347+
`@microsoft/webui-framework/component-asset.js`; a FAST runtime should define its
348+
own asset loader rather than making the core `@microsoft/webui` package know
349+
plugin details. Asset roots are parsed into the protocol through synthetic
350+
non-entry fragments, so they do not become reachable from the SSR entry tree and
351+
are not included in the initial SSR bootstrap unless the entry graph also
352+
references them. Asset generation is parallelized across requested roots. Each root produces one
353+
standard ESM module, `<tag>.webui.js`, by default. Use
354+
`--asset-file-name-template "[name]-[hash].[ext]"` for CDN-cacheable CSS and
355+
component asset names; `[hash]` is the emitted file's SHA-256 content hash
356+
truncated to 8 hex characters and `[ext]` resolves to `webui.js` for component
357+
assets. Programmatic Rust builds expose the rendered files through
358+
`BuildResult::component_asset_files`; `build_to_disk()` and the CLI validate
359+
protocol/CSS/component-asset filenames as one output set before writing any
360+
file. The module default-exports:
361+
362+
```js
363+
export default {
364+
type: "webui-component-asset",
365+
version: 1,
366+
components: ["mail-thread", "mail-message"],
367+
templateStyles: [],
368+
templates: {},
369+
templateFunctions: {
370+
"mail-thread": [function(v, s) { return !!v("hasMessages", s); }]
371+
}
372+
};
373+
```
374+
375+
The component list is the conservative dependency closure for the requested root:
376+
component edges, `<if>`, `<for>`, attribute-template edges, and all nested
377+
`<route>` branches are followed without evaluating runtime state. The JSON file
378+
is inert data and intentionally omits `inventory`: a build-time static asset does
379+
not know the page's current loaded bitset, so consumers must not replace
380+
`window.__webui.inventory` with asset-local state. Component-local condition
381+
closures are carried in the same ESM request as `templateFunctions`, so the
382+
template asset, component class chunk, and component data request can all start
383+
in parallel from the manifest. CSS module importmaps still use the page's current
384+
CSP nonce when materialized by the optional
385+
`@microsoft/webui-framework/component-asset.js` `defineComponentAssets()`
386+
manifest loader. This loader is not re-exported from the framework root package
387+
entrypoint, keeping it out of normal framework bundles unless an app imports the
388+
optional subpath. The loader uses the manifest tag as the registered-template
389+
fast path, so hashed asset filenames still skip importing when
390+
`window.__webui.templates[tag]` already exists. Otherwise it deduplicates
391+
in-flight imports by resolved asset URL and deduplicates module-style importmaps
392+
against `window.__webui.styles` plus previously injected asset styles.
393+
`defineComponentAssets().create(tag)` waits for the asset/module, mounts without
394+
blocking on data by default, and applies data later with `setState()`; callers
395+
can opt into bounded data blocking with `{ awaitData: true, dataTimeoutMs }`.
396+
397+
FAST plugin builds can emit the same ESM asset shape with trusted `<f-template>`
398+
payloads in `templates`; those assets require a FAST-owned runtime loader.
399+
342400
**Navigation cache:** The client router maintains a tagged navigation cache. Partial responses are stored keyed by request path and tagged with `cacheTags`. On revisit within `staleTime`, the cache is used and the network fetch is skipped. After a mutation action, `Router.invalidateTags()` evicts all entries whose tags overlap with the invalidated tags. Configuration: `Router.start({ cache: { staleTime, gcTime, maxEntries } })`.
343401

344402
**Mutation actions:** Components can declare `static action(ctx: RouteActionContext)` as the write counterpart to `static loader()`. The router intercepts `<form method="post">` submissions, finds the nearest route component's `static action()`, calls it, and auto-invalidates the cache using both the action's returned tags and the route's build-time `invalidates` attribute. This ensures the compiler-declared invalidation graph is always respected — developers cannot forget.
@@ -1048,7 +1106,9 @@ parser.parse("index.html", &html)?;
10481106
**CLI integration:**
10491107
```bash
10501108
webui build ./templates --out ./dist --plugin=<name>
1051-
webui build ./templates --out ./dist --css-file-name-template="[name]-[hash].[ext]" --css-public-base="https://cdn.example.com/assets"
1109+
webui build ./templates --out ./dist --asset-file-name-template="[name]-[hash].[ext]" --css-public-base="https://cdn.example.com/assets"
1110+
webui build ./templates --out ./dist --plugin=webui --emit-component-assets mail-thread,compose-page
1111+
webui build ./templates --out ./dist --plugin=webui --emit-component-assets mail-thread --asset-file-name-template="[name]-[hash].[ext]"
10521112
webui serve ./templates --state ./data/state.json --plugin=<name>
10531113
```
10541114

crates/webui-cli/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ This installs the `webui` binary.
1717
Build a WebUI application into a compiled protocol and CSS files.
1818

1919
```bash
20-
webui build [APP] --out <DIR> [--entry <FILE>] [--css <MODE>] [--plugin <NAME>] [--css-file-name-template <TEMPLATE>] [--css-public-base <BASE>]
20+
webui build [APP] --out <DIR> [--entry <FILE>] [--css <MODE>] [--plugin <NAME>] [--asset-file-name-template <TEMPLATE>] [--css-public-base <BASE>]
2121
```
2222

2323
| Option | Default | Description |
@@ -27,23 +27,23 @@ webui build [APP] --out <DIR> [--entry <FILE>] [--css <MODE>] [--plugin <NAME>]
2727
| `--entry` | `index.html` | Entry HTML file |
2828
| `--css` | `link` | CSS mode: `link` (external files) or `style` (inline) |
2929
| `--plugin` | *(none)* | Plugin identifier (see [Plugins](https://microsoft.github.io/webui/guide/concepts/plugins/) for available identifiers) |
30-
| `--css-file-name-template` | `[name].[ext]` | Link-mode CSS filename template. Tokens: `[name]`, `[hash]`, `[ext]` |
30+
| `--asset-file-name-template` | `[name].[ext]` | Emitted asset filename template. Tokens: `[name]`, `[hash]`, `[ext]` |
3131
| `--css-public-base` | *(none)* | Optional base URL/path prepended to Link-mode stylesheet hrefs |
3232

3333
```bash
3434
webui build ./src --out ./dist
3535
webui build ./src --out ./dist --plugin webui --css style
3636
webui build ./src --out ./dist/app1.bin
37-
webui build ./src --out ./dist --css-file-name-template "[name]-[hash].[ext]"
38-
webui build ./src --out ./dist --css-file-name-template "[name]-[hash].[ext]" --css-public-base "https://cdn.example.com/assets"
37+
webui build ./src --out ./dist --asset-file-name-template "[name]-[hash].[ext]"
38+
webui build ./src --out ./dist --asset-file-name-template "[name]-[hash].[ext]" --css-public-base "https://cdn.example.com/assets"
3939
```
4040

4141
### `webui serve`
4242

4343
Start a development server with live rebuild and HMR.
4444

4545
```bash
46-
webui serve [APP] [--state <FILE>] [--servedir <DIR>] [--port <PORT>] [--api-port <PORT>] [--plugin <NAME>] [--watch] [--css-file-name-template <TEMPLATE>] [--css-public-base <BASE>]
46+
webui serve [APP] [--state <FILE>] [--servedir <DIR>] [--port <PORT>] [--api-port <PORT>] [--plugin <NAME>] [--watch] [--asset-file-name-template <TEMPLATE>] [--css-public-base <BASE>]
4747
```
4848

4949
| Option | Default | Description |
@@ -55,7 +55,7 @@ webui serve [APP] [--state <FILE>] [--servedir <DIR>] [--port <PORT>] [--api-por
5555
| `--api-port` | *(none)* | Proxy API requests to this port |
5656
| `--plugin` | *(none)* | Plugin identifier (see [Plugins](https://microsoft.github.io/webui/guide/concepts/plugins/) for available identifiers) |
5757
| `--watch` | off | Enable file watching + HMR |
58-
| `--css-file-name-template` | `[name].[ext]` | Link-mode CSS filename template. Tokens: `[name]`, `[hash]`, `[ext]` |
58+
| `--asset-file-name-template` | `[name].[ext]` | Emitted asset filename template. Tokens: `[name]`, `[hash]`, `[ext]` |
5959
| `--css-public-base` | *(none)* | Optional base URL/path prepended to Link-mode stylesheet hrefs |
6060

6161
```bash

0 commit comments

Comments
 (0)