diff --git a/.github/skills/create-example/SKILL.md b/.github/skills/create-example/SKILL.md new file mode 100644 index 00000000..19ca9419 --- /dev/null +++ b/.github/skills/create-example/SKILL.md @@ -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`. diff --git a/Cargo.lock b/Cargo.lock index 381f94d7..0fc4256b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1800,6 +1800,7 @@ dependencies = [ "microsoft-webui-handler", "microsoft-webui-parser", "microsoft-webui-protocol", + "rayon", "serde", "serde_json", "tempfile", diff --git a/DESIGN.md b/DESIGN.md index c223eee5..a01d64d4 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -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, `.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, ``, ``, attribute-template edges, and all nested +`` 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 `` +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 `
` 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)?; **CLI integration:** ```bash webui build ./templates --out ./dist --plugin= -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= ``` diff --git a/crates/webui-cli/README.md b/crates/webui-cli/README.md index b75c1b5a..c9175637 100644 --- a/crates/webui-cli/README.md +++ b/crates/webui-cli/README.md @@ -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 [--entry ] [--css ] [--plugin ] [--css-file-name-template