Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/skills/create-example/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: create-example
description: Create a runnable WebUI example with app structure, theme wiring, Playwright tests, demo metadata, and demo-shell registration.
---

# Checklist

1. Create the app folder with `src/`, `data/state.json` when SSR state is needed, `package.json`, `tsconfig.json`, and a concise `demo.toml`.
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.
3. Keep `package.json` scripts consistent: `build`, `start:client`, `start:server`, `start`, `test`, and `test:update-snapshots` when Playwright applies.
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.
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.
6. Add `demo.toml`, copy the app into `examples/demo/Dockerfile`, and list it in `examples/README.md`.
7. Run the focused app test, then `cargo xtask check`.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 61 additions & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,64 @@ emit WebUI `templates` or `templateFns`.

**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.

**Static component assets:** `webui build --emit-component-assets mail-thread,compose-page`
emits CDN-loadable component asset files next to `protocol.bin`. The flag is a
strict comma-separated allowlist of root component tags; every tag must be a
discovered lowercase kebab-case component with WebUI template metadata. Static
component asset runtimes are framework-owned: the WebUI Framework loader lives at
`@microsoft/webui-framework/component-asset.js`; a FAST runtime should define its
own asset loader rather than making the core `@microsoft/webui` package know
plugin details. Asset roots are parsed into the protocol through synthetic
non-entry fragments, so they do not become reachable from the SSR entry tree and
are not included in the initial SSR bootstrap unless the entry graph also
references them. Asset generation is parallelized across requested roots. Each root produces one
standard ESM module, `<tag>.webui.js`, by default. Use
`--asset-file-name-template "[name]-[hash].[ext]"` for CDN-cacheable CSS and
component asset names; `[hash]` is the emitted file's SHA-256 content hash
truncated to 8 hex characters and `[ext]` resolves to `webui.js` for component
assets. Programmatic Rust builds expose the rendered files through
`BuildResult::component_asset_files`; `build_to_disk()` and the CLI validate
protocol/CSS/component-asset filenames as one output set before writing any
file. The module default-exports:

```js
export default {
type: "webui-component-asset",
version: 1,
components: ["mail-thread", "mail-message"],
templateStyles: [],
templates: {},
templateFunctions: {
"mail-thread": [function(v, s) { return !!v("hasMessages", s); }]
}
};
```

The component list is the conservative dependency closure for the requested root:
component edges, `<if>`, `<for>`, attribute-template edges, and all nested
`<route>` branches are followed without evaluating runtime state. The JSON file
is inert data and intentionally omits `inventory`: a build-time static asset does
not know the page's current loaded bitset, so consumers must not replace
`window.__webui.inventory` with asset-local state. Component-local condition
closures are carried in the same ESM request as `templateFunctions`, so the
template asset, component class chunk, and component data request can all start
in parallel from the manifest. CSS module importmaps still use the page's current
CSP nonce when materialized by the optional
`@microsoft/webui-framework/component-asset.js` `defineComponentAssets()`
manifest loader. This loader is not re-exported from the framework root package
entrypoint, keeping it out of normal framework bundles unless an app imports the
optional subpath. The loader uses the manifest tag as the registered-template
fast path, so hashed asset filenames still skip importing when
`window.__webui.templates[tag]` already exists. Otherwise it deduplicates
in-flight imports by resolved asset URL and deduplicates module-style importmaps
against `window.__webui.styles` plus previously injected asset styles.
`defineComponentAssets().create(tag)` waits for the asset/module, mounts without
blocking on data by default, and applies data later with `setState()`; callers
can opt into bounded data blocking with `{ awaitData: true, dataTimeoutMs }`.

FAST plugin builds can emit the same ESM asset shape with trusted `<f-template>`
payloads in `templates`; those assets require a FAST-owned runtime loader.

**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 } })`.

**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.
Expand Down Expand Up @@ -1048,7 +1106,9 @@ parser.parse("index.html", &html)?;
**CLI integration:**
```bash
webui build ./templates --out ./dist --plugin=<name>
webui build ./templates --out ./dist --css-file-name-template="[name]-[hash].[ext]" --css-public-base="https://cdn.example.com/assets"
webui build ./templates --out ./dist --asset-file-name-template="[name]-[hash].[ext]" --css-public-base="https://cdn.example.com/assets"
webui build ./templates --out ./dist --plugin=webui --emit-component-assets mail-thread,compose-page
webui build ./templates --out ./dist --plugin=webui --emit-component-assets mail-thread --asset-file-name-template="[name]-[hash].[ext]"
webui serve ./templates --state ./data/state.json --plugin=<name>
```

Expand Down
12 changes: 6 additions & 6 deletions crates/webui-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This installs the `webui` binary.
Build a WebUI application into a compiled protocol and CSS files.

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

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

```bash
webui build ./src --out ./dist
webui build ./src --out ./dist --plugin webui --css style
webui build ./src --out ./dist/app1.bin
webui build ./src --out ./dist --css-file-name-template "[name]-[hash].[ext]"
webui build ./src --out ./dist --css-file-name-template "[name]-[hash].[ext]" --css-public-base "https://cdn.example.com/assets"
webui build ./src --out ./dist --asset-file-name-template "[name]-[hash].[ext]"
webui build ./src --out ./dist --asset-file-name-template "[name]-[hash].[ext]" --css-public-base "https://cdn.example.com/assets"
```

### `webui serve`

Start a development server with live rebuild and HMR.

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

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

```bash
Expand Down
Loading
Loading