You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduce @microsoft/webui-framework — a lightweight Web Component runtime
that hydrates server-rendered HTML using compiled template metadata and
direct DOM binding. No virtual DOM, no runtime template parsing, no
JavaScript runtime required on the server.
- WebUIElement base class with @observable, @attr, @volatile decorators
- Single-pass SSR hydration that seeds component state inline from markers
- Per-path targeted updates via a binding index (O(affected) not O(all))
- Template cloning cache for fast repeat instantiation (cloneNode not innerHTML)
- Zero-allocation update path with pre-merged wildcard bindings
- Event delegation (one listener per event type, not one closure per element)
- Keyed and sequential repeat reconciliation for @for loops
- Iterative condition AST evaluation for @if blocks (no recursion)
- CSS module stylesheet cache via adoptedStyleSheets
- Modular architecture: element.ts orchestrator + repeat, paths, conditions,
seed, types, and styles modules with file-level documentation
- Co-located unit tests (conditions, seed, types, template) and Playwright
e2e fixtures covering hydration, reactivity, repeats, conditionals, events,
refs, state seeding, CSS strategies, and performance benchmarks
- Shared test infrastructure via @microsoft/webui-test-support (private)
- HTML entity decoding in compiled text run static parts
- Comprehensive README with architecture diagrams, performance philosophy,
API reference, and contributor guidelines
- Hydration docs, copilot instructions, and webui-dev skill updated
description: Build interactive WebUI example apps with FAST-HTML hydration, template syntax, and component patterns.
3
+
description: Build interactive WebUI example apps with compiled-template hydration, template syntax, and component patterns.
4
4
---
5
5
6
-
# WebUI App Development with FAST-HTML
6
+
# WebUI App Development
7
7
8
8
Use this skill when building or modifying example apps under `examples/app/`.
9
9
10
-
WebUI server-renders HTML at request time. FAST-HTML hydrates the pre-rendered DOM on the client, making it interactive without a full re-render.
10
+
WebUI server-renders HTML at request time. The `@microsoft/webui-framework` runtime hydrates the pre-rendered DOM on the client using compiled template metadata and direct DOM binding — no virtual DOM, no runtime template parsing.
11
11
12
12
## Project structure
13
13
@@ -17,7 +17,7 @@ Every example app follows this layout:
17
17
examples/app/<name>/
18
18
├── src/
19
19
│ ├── index.html # HTML shell + CSS design tokens
20
-
│ ├── index.ts # Hydration bootstrap
20
+
│ ├── index.ts # Component registration
21
21
│ └── <component-name>/ # One directory per component
- Reflected to/from the HTML attribute on the element.
186
-
-**Use `!:` (no initializer)** for fields set in `prepare()`. Class field initializers run AFTER `super()`, overwriting values set during hydration.
178
+
-**Use `!:` (no initializer)** for fields seeded from SSR. Class field initializers run AFTER `super()`, overwriting values set during hydration.
187
179
- Hyphenated HTML attributes map via `@attr({ attribute: 'display-value' })`.
180
+
- Attribute values arrive as strings — use `@observable` for non-string state.
188
181
189
182
### `@observable` — Reactive properties
190
183
191
-
- Triggers FAST template re-rendering when changed.
192
-
- Use for data that drives `<for>` loops or conditional display.
193
-
-**Use `!:` (no initializer)** for fields set in `prepare()`.
184
+
- Triggers per-path targeted update when changed — only bindings referencing this property are visited.
185
+
- Use for data that drives `<for>` loops, `<if>` conditionals, or text/attribute bindings.
186
+
-**Use `!:` (no initializer)** for fields seeded from SSR.
187
+
188
+
### `@volatile` — Computed getters
194
189
195
-
### `prepare()` — Hydration hook
190
+
- Re-evaluated on every access (no caching).
191
+
- Automatically included in targeted updates via a wildcard binding.
196
192
197
-
Called during hydration to initialize component state from the pre-rendered DOM. This is **the** place to read server-rendered data:
193
+
### `$emit` — Custom events
198
194
199
195
```typescript
200
-
asyncprepare(): Promise<void> {
201
-
// Read @attr values from HTML attributes (initializers haven't run yet)
202
-
this.mode=this.getAttribute('mode') ||'default';
203
-
204
-
// Read child elements rendered by SSR
205
-
const items: ItemData[] = [];
206
-
for (constelofthis.shadowRoot!.querySelectorAll('my-item')) {
207
-
items.push({
208
-
id: el.getAttribute('id') ||'',
209
-
title: el.getAttribute('title') ||'',
210
-
});
211
-
}
212
-
this.items=items;
213
-
}
196
+
this.$emit('toggle-item', { id: this.id });
214
197
```
215
198
216
-
Rules:
217
-
218
-
- Always read `@attr` values from `this.getAttribute()` — decorators initialize AFTER `prepare()`.
219
-
- Read child element data from `this.shadowRoot.querySelectorAll()`.
220
-
- Guard with `if (!this.shadowRoot) return;` when appropriate.
221
-
- Avoid `<for>` loops over `@observable` string arrays — use object arrays or static HTML.
199
+
Events bubble through the shadow DOM boundary via `composed: true`.
222
200
223
-
### Component template (HTML)
201
+
### `w-ref` — Template refs
224
202
225
-
The `<template shadowrootmode="open">` wrapper is **optional** — the framework adds it automatically when absent. Only include it when you need to attach event listeners or other directives to the template root:
203
+
Bind a DOM element to a component property for direct access:
<!-- With root-level event listeners — wrapper required -->
235
-
<templateshadowrootmode="open"
236
-
@custom-event="{onCustomEvent(e)}"
237
-
>
238
-
<div>{{title}}</div>
239
-
</template>
206
+
<inputw-ref="myInput"@keydown="{onKeydown(e)}" />
240
207
```
241
208
242
-
### Component styles (CSS)
243
-
244
-
Scoped to the component via shadow DOM:
245
-
246
-
```css
247
-
:host {
248
-
display: block;
249
-
}
250
-
251
-
:host([state="active"]) .indicator {
252
-
background: var(--color-accent);
253
-
}
209
+
```typescript
210
+
myInput!: HTMLInputElement; // Populated during hydration
254
211
255
-
.content {
256
-
padding: var(--spacing-m);
212
+
onSubmit(): void {
213
+
const value =this.myInput.value;
214
+
this.myInput.value='';
215
+
this.myInput.focus();
257
216
}
258
217
```
259
218
260
-
Use `:host([attr="value"])` selectors to style based on attribute state.
219
+
Use refs only for DOM-only concerns (focus, measurement, selection). Application state belongs in `@observable` / `@attr`.
261
220
262
221
## Event handling
263
222
264
223
### Template event binding — `@event`
265
224
266
-
Bind DOM and custom events to component methods in the template:
267
-
268
225
```html
269
226
<!-- Standard DOM events -->
270
227
<button@click="{onClick()}">Click</button>
@@ -279,66 +236,69 @@ Bind DOM and custom events to component methods in the template:
279
236
280
237
Pass `e` to receive the event object. Omit it when not needed.
281
238
282
-
### Emitting custom events — `$emit`
239
+
Events use delegation internally — one listener per event type on the shadow root, not one closure per element.
283
240
284
-
Child components emit events to parent components:
241
+
## Component template (HTML)
285
242
286
-
```typescript
287
-
// Emit a custom event with detail data
288
-
this.$emit('toggle-item', { id: this.id });
243
+
The `<template shadowrootmode="open">` wrapper is **optional** — the framework adds it automatically when absent. Only include it when you need root-level event listeners:
289
244
290
-
// Parent catches it via @toggle-item="{onToggleItem(e)}" on its <template>
0 commit comments