@microsoft/webui-framework 0.0.15 → 0.0.17

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 CHANGED
@@ -32,7 +32,7 @@ Outside the workspace:
32
32
  pnpm add @microsoft/webui-framework
33
33
  ```
34
34
 
35
- TypeScript must use legacy decorators:
35
+ TypeScript must enable decorator emit:
36
36
 
37
37
  ```json
38
38
  {
@@ -93,7 +93,7 @@ Build with `--dom=shadow` (default) to wrap in a declarative shadow root, or `--
93
93
  cargo run -p microsoft-webui-cli -- build ./src --out ./dist --plugin=webui
94
94
  ```
95
95
 
96
- The compiler/plugin generates the template metadata consumed by the runtime. In normal app code, you should not need to hand-author `window.__webui.templates`.
96
+ The compiler/plugin generates the template metadata and condition closure arrays consumed by the runtime. In normal app code, you should not need to hand-author `window.__webui.templates` or `window.__webui.templateFns`.
97
97
 
98
98
  ### Property binding lifecycle
99
99
 
@@ -311,7 +311,7 @@ When contributing to the runtime, avoid these patterns:
311
311
  │ │ │ (Rust/Go/C#/…) │ │ │
312
312
  │ HTML template │ │ │ │ SSR HTML (light or │
313
313
  │ + expressions │────▶│ TemplateMeta (JSON) │────▶│ shadow DOM) + │
314
- │ + @if / @for │ │ + state data │ │ __webui.state JSON │
314
+ │ + @if / @for │ │ + state data │ │ webui-data JSON │
315
315
  │ │ │ │ │ │
316
316
  │ Outputs: │ │ Renders: │ │ Hydrates: │
317
317
  │ • TemplateMeta │ │ • Full HTML page │ │ • Path-based DOM │
@@ -340,7 +340,7 @@ flowchart LR
340
340
  subgraph Serve ["Server (Any Language)"]
341
341
  M --> R[Route Handler]
342
342
  S[State Data] --> R
343
- R --> HTML["Full SSR HTML<br/>(shadow or light DOM)<br/>+ TemplateMeta &lt;script&gt;<br/>+ __webui.state &lt;script&gt;"]
343
+ R --> HTML["Full SSR HTML<br/>(shadow or light DOM)<br/>+ inert #webui-data"]
344
344
  end
345
345
 
346
346
  subgraph Browser ["Browser"]
@@ -366,7 +366,7 @@ graph TD
366
366
 
367
367
  TYPES["element/types.ts<br/><i>Shared Types</i><br/>TemplateInstance, TextBinding,<br/>AttrBinding, CondBinding,<br/>RepeatBinding, ScopeFrame,<br/>RepeatHost"]
368
368
 
369
- TMPL["template.ts<br/><i>Metadata Types + Registry</i><br/>TemplateMeta, getTemplate"]
369
+ TMPL["template.ts<br/><i>Metadata Types + Registry</i><br/>TemplateMeta, getTemplate,<br/>registerTemplateData"]
370
370
 
371
371
  DEC["decorators.ts<br/><i>Reactive Properties</i><br/>@observable, @attr, @volatile"]
372
372
 
@@ -388,7 +388,7 @@ graph TD
388
388
  ### SSR Hydration Path
389
389
 
390
390
  When the server renders a component, it emits HTML content (as a declarative
391
- shadow root or as light DOM children) along with a `window.__webui.state`
391
+ shadow root or as light DOM children) along with an inert `#webui-data`
392
392
  JSON payload. The browser parses this DOM before any JavaScript runs.
393
393
  When the component's JS loads and `connectedCallback` fires, the framework
394
394
  uses compiled template paths to resolve SSR DOM nodes without any marker
@@ -401,7 +401,7 @@ sequenceDiagram
401
401
  participant CE as Custom Element
402
402
  participant FW as Framework
403
403
 
404
- Server->>Browser: HTML (shadow or light DOM)<br/>+ __webui.state JSON
404
+ Server->>Browser: HTML (shadow or light DOM)<br/>+ inert #webui-data JSON
405
405
  Browser->>Browser: Parse HTML → DOM exists
406
406
  Browser->>CE: Custom element upgrade
407
407
  CE->>CE: attributeChangedCallback (pre-existing attrs)
@@ -486,19 +486,16 @@ Compiled metadata:
486
486
  }
487
487
  ```
488
488
 
489
- ### Condition AST
489
+ ### Condition references
490
490
 
491
- Conditions are emitted as compact tuples:
491
+ Conditions are emitted as `[functionIndex, paths]` references. The index points
492
+ to a component-local closure in `window.__webui.templateFns[tagName]`, while
493
+ `paths` lets the runtime build targeted reactive indexes without parsing
494
+ function source.
492
495
 
493
- | Tuple | Meaning | Example |
494
- |-------|---------|---------|
495
- | `[0, path]` | Identifier (truthy check) | `@if(visible)` |
496
- | `[1, left, op, right]` | Comparison predicate | `@if(count > 0)` |
497
- | `[2, inner]` | Logical NOT | `@if(!visible)` |
498
- | `[3, left, op, right]` | Compound AND/OR | `@if(a && b)` |
499
-
500
- The runtime evaluates these iteratively (stack-based, no recursion) to avoid
501
- call-stack depth in hot update paths.
496
+ The runtime normalizes each condition reference into `[fn, paths]` once before
497
+ hydration or client-created wiring, so hot update paths call the closure
498
+ directly.
502
499
 
503
500
  ---
504
501
 
@@ -561,15 +558,15 @@ browser sees `42` in the DOM but the JavaScript property `this.count` is still
561
558
  `0` (the class default). Without seeding, the first `$update()` would
562
559
  overwrite the SSR content with the wrong value.
563
560
 
564
- State seeding uses `window.__webui.state` — a JSON object emitted by the
565
- server handler as a `<script>` tag. Like Preact's props, this delivers the
561
+ State seeding uses `window.__webui.state` — a JSON object loaded from the
562
+ server-emitted `#webui-data` block. Like Preact's props, this delivers the
566
563
  same data used for SSR rendering to the client. During `$mount()`,
567
564
  `$applySSRState()` writes matching keys directly to observable backing fields
568
565
  before any bindings are wired:
569
566
 
570
567
  ```mermaid
571
568
  flowchart LR
572
- SCRIPT["&lt;script&gt;<br/>window.__webui.state = {<br/> count: 42,<br/> title: 'Hello'<br/>}"] --> APPLY["$applySSRState()"]
569
+ SCRIPT["&lt;script type='application/json' id='webui-data'&gt;<br/>{ state: { count: 42, title: 'Hello' } }"] --> APPLY["$applySSRState()"]
573
570
  APPLY --> SEED["Write to backing fields:<br/>this._count = 42<br/>this._title = 'Hello'"]
574
571
  SEED --> HYDRATE["$hydrate() — bindings match<br/>server-rendered DOM"]
575
572
  ```
@@ -3,8 +3,8 @@
3
3
  /**
4
4
  * Reactive decorators for WebUIElement properties.
5
5
  *
6
- * Uses legacy/experimental TypeScript decorators (`experimentalDecorators: true`)
7
- * for compatibility with the FAST ecosystem conventions.
6
+ * Uses TypeScript's `experimentalDecorators` emit, matching the FAST ecosystem
7
+ * conventions.
8
8
  */
9
9
  // ---------------------------------------------------------------------------
10
10
  // Internal helpers
package/dist/element.js CHANGED
@@ -540,7 +540,7 @@ export class WebUIElement extends HTMLElement {
540
540
  const parent = parentPath.length > 0 ? this.$resolve(root, parentPath) : root;
541
541
  if (!parent || (parent.nodeType !== 1 && parent.nodeType !== 11))
542
542
  continue;
543
- condRefs.push({ parent, ref: parent.childNodes[beforeIndex] || null, condition, blockIndex });
543
+ condRefs.push({ parent, ref: parent.childNodes[beforeIndex] || null, condition: condition, blockIndex });
544
544
  }
545
545
  }
546
546
  const repRefs = [];
@@ -719,7 +719,7 @@ export class WebUIElement extends HTMLElement {
719
719
  }
720
720
  }
721
721
  instance.conds.push({
722
- condition, blockIndex,
722
+ condition: condition, blockIndex,
723
723
  anchor: condAnchor,
724
724
  scope, instance: condInstance,
725
725
  });
package/dist/index.d.ts CHANGED
@@ -19,6 +19,6 @@
19
19
  */
20
20
  export { WebUIElement } from './element.js';
21
21
  export { observable, attr } from './decorators.js';
22
- export { getTemplate } from './template.js';
22
+ export { getTemplate, registerTemplateData } from './template.js';
23
23
  export type { TemplateMeta } from './template.js';
24
24
  export { hydrationStart, hydrationEnd } from './lifecycle.js';
package/dist/index.js CHANGED
@@ -21,5 +21,5 @@
21
21
  */
22
22
  export { WebUIElement } from './element.js';
23
23
  export { observable, attr } from './decorators.js';
24
- export { getTemplate } from './template.js';
24
+ export { getTemplate, registerTemplateData } from './template.js';
25
25
  export { hydrationStart, hydrationEnd } from './lifecycle.js';
@@ -16,19 +16,19 @@ export type CompiledAttrGroupMeta = [
16
16
  count: number
17
17
  ];
18
18
  /**
19
- * Compiled condition — a pre-compiled JS function plus the paths it references.
20
- * The Rust compiler emits the function body at build time so the runtime
21
- * doesn't need a condition AST interpreter.
19
+ * Compiled condition — JSON metadata carries a function index plus the paths it
20
+ * references. The Rust compiler emits the actual function bodies in a separate
21
+ * closure array, and the runtime normalizes indexes to functions once.
22
22
  *
23
- * - `[0]` — evaluator function: `(resolve, scope) => boolean`
23
+ * - `[0]` — evaluator function or component-local function index
24
24
  * - `[1]` — referenced paths for the reactive path index
25
25
  */
26
- export type CompiledCondition = [
27
- fn: (v: (path: string, s?: unknown) => unknown, s?: unknown) => boolean,
28
- paths: string[]
29
- ];
30
- export type CompiledConditionalMeta = [condition: CompiledCondition, blockIndex: number, slot: TemplateSlotPath];
31
- export type CompiledAttrMeta = [name: string, kind: 0, value: string] | [name: string, kind: 1, value: string] | [name: string, kind: 2, condition: CompiledCondition] | [name: string, kind: 3, parts: CompiledAttrPart[]];
26
+ export type CompiledConditionFn = (v: (path: string, s?: unknown) => unknown, s?: unknown) => boolean;
27
+ export type CompiledCondition = [fn: CompiledConditionFn, paths: string[]];
28
+ export type SerializedCompiledCondition = [fnIndex: number, paths: string[]];
29
+ export type TemplateCondition = CompiledCondition | SerializedCompiledCondition;
30
+ export type CompiledConditionalMeta = [condition: TemplateCondition, blockIndex: number, slot: TemplateSlotPath];
31
+ export type CompiledAttrMeta = [name: string, kind: 0, value: string] | [name: string, kind: 1, value: string] | [name: string, kind: 2, condition: TemplateCondition] | [name: string, kind: 3, parts: CompiledAttrPart[]];
32
32
  export type CompiledRepeatMeta = [collection: string, itemVar: string, blockIndex: number, slot: TemplateSlotPath];
33
33
  export type CompiledEventArg = ['e'] | ['p', string] | ['s', string] | ['n', number] | ['b', number] | ['z'];
34
34
  export type CompiledEventArgs = CompiledEventArg[];
@@ -6,26 +6,26 @@
6
6
  * - `tx` — text runs `[slot, parts]` for text binding positions
7
7
  * - `a` — attribute binding metadata
8
8
  * - `ag` — attribute target groups `[path, startIndex, count]`
9
- * - `c` — conditional blocks `[conditionAst, blockIndex]`
10
- * - `cl` — conditional anchor slots
11
- * - `r` — repeat/for blocks `[collection, itemVar, blockIndex]`
12
- * - `rl` — repeat anchor slots
9
+ * - `c` — conditional blocks `[conditionRef, blockIndex, slot]`
10
+ * - `r` — repeat/for blocks `[collection, itemVar, blockIndex, slot]`
13
11
  * - `e` — element events `[eventName, handlerName, argSpecs, targetPath]`
14
12
  * - `b` — nested compiled block metadata
15
13
  * - `sa` — adopted stylesheet specifier for CSS module strategy
16
14
  * - `sd` — shadow DOM flag for client-created components
17
15
  * - `re` — root events on the host element
18
16
  */
19
- export type { CompiledAttrGroupMeta, CompiledAttrMeta, CompiledAttrPart, CompiledCondition, CompiledConditionalMeta, CompiledEventArg, CompiledEventArgs, CompiledTextRunMeta, TemplateBlockMeta, TemplateMeta, TemplateNodePath, TemplateSlotPath, } from './template-types.js';
20
- import type { TemplateMeta } from './template-types.js';
17
+ export type { CompiledAttrGroupMeta, CompiledAttrMeta, CompiledAttrPart, CompiledCondition, CompiledConditionFn, CompiledConditionalMeta, CompiledEventArg, CompiledEventArgs, SerializedCompiledCondition, TemplateCondition, CompiledTextRunMeta, TemplateBlockMeta, TemplateMeta, TemplateNodePath, TemplateSlotPath, } from './template-types.js';
18
+ import type { CompiledConditionFn, TemplateMeta } from './template-types.js';
21
19
  declare global {
22
20
  interface Window {
23
- /** Consolidated SSR bootstrap object — single script block. */
21
+ /** Consolidated SSR metadata loaded from `#webui-data` or partial responses. */
24
22
  __webui?: {
25
23
  state?: Record<string, unknown>;
26
24
  templates?: Record<string, TemplateMeta>;
25
+ templateFns?: Record<string, CompiledConditionFn[]>;
27
26
  [key: string]: unknown;
28
27
  };
29
28
  }
30
29
  }
31
30
  export declare function getTemplate(name: string): TemplateMeta | undefined;
31
+ export declare function registerTemplateData(templates: Record<string, TemplateMeta>, templateFns?: Record<string, CompiledConditionFn[]>): void;
package/dist/template.js CHANGED
@@ -1,5 +1,96 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT license.
3
+ const WEBUI_DATA_ID = 'webui-data';
4
+ const normalizedTemplates = new WeakSet();
5
+ let webuiDataLoaded = false;
3
6
  export function getTemplate(name) {
4
- return window.__webui?.templates?.[name];
7
+ let meta = window.__webui?.templates?.[name];
8
+ if (!meta) {
9
+ loadWebUIDataBlock();
10
+ meta = window.__webui?.templates?.[name];
11
+ }
12
+ if (meta)
13
+ normalizeTemplate(name, meta);
14
+ return meta;
15
+ }
16
+ export function registerTemplateData(templates, templateFns) {
17
+ const w = window;
18
+ if (!w.__webui)
19
+ w.__webui = {};
20
+ if (!w.__webui.templates)
21
+ w.__webui.templates = {};
22
+ if (templateFns) {
23
+ if (!w.__webui.templateFns)
24
+ w.__webui.templateFns = {};
25
+ const fnNames = Object.keys(templateFns);
26
+ for (let i = 0; i < fnNames.length; i++) {
27
+ const tag = fnNames[i];
28
+ w.__webui.templateFns[tag] = templateFns[tag];
29
+ }
30
+ }
31
+ const names = Object.keys(templates);
32
+ for (let i = 0; i < names.length; i++) {
33
+ const tag = names[i];
34
+ const meta = templates[tag];
35
+ w.__webui.templates[tag] = meta;
36
+ normalizeTemplate(tag, meta);
37
+ }
38
+ }
39
+ function loadWebUIDataBlock() {
40
+ if (webuiDataLoaded || window.__webui?.state !== undefined || typeof document === 'undefined')
41
+ return;
42
+ const el = document.getElementById(WEBUI_DATA_ID);
43
+ if (!el) {
44
+ webuiDataLoaded = true;
45
+ return;
46
+ }
47
+ const text = el.textContent;
48
+ if (text) {
49
+ const templateFns = window.__webui?.templateFns;
50
+ const parsed = JSON.parse(text);
51
+ if (templateFns)
52
+ parsed.templateFns = templateFns;
53
+ window.__webui = parsed;
54
+ }
55
+ el.remove();
56
+ webuiDataLoaded = true;
57
+ }
58
+ function normalizeTemplate(name, meta) {
59
+ if (normalizedTemplates.has(meta))
60
+ return;
61
+ const fns = window.__webui?.templateFns?.[name] ?? [];
62
+ const stack = [meta];
63
+ while (stack.length > 0) {
64
+ const block = stack.pop();
65
+ if (!block)
66
+ continue;
67
+ if (block.a) {
68
+ for (let i = 0; i < block.a.length; i++) {
69
+ const attr = block.a[i];
70
+ if (attr[1] === 2)
71
+ normalizeCondition(name, attr[2], fns);
72
+ }
73
+ }
74
+ if (block.c) {
75
+ for (let i = 0; i < block.c.length; i++) {
76
+ normalizeCondition(name, block.c[i][0], fns);
77
+ }
78
+ }
79
+ const children = block.b;
80
+ if (children) {
81
+ for (let i = 0; i < children.length; i++)
82
+ stack.push(children[i]);
83
+ }
84
+ }
85
+ normalizedTemplates.add(meta);
86
+ }
87
+ function normalizeCondition(tagName, condition, fns) {
88
+ const first = condition[0];
89
+ if (typeof first === 'function')
90
+ return;
91
+ const fn = fns[first];
92
+ if (typeof fn !== 'function') {
93
+ throw new Error(`[WebUI] Missing condition closure ${first} for <${tagName}>.`);
94
+ }
95
+ condition[0] = fn;
5
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/webui-framework",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "description": "WebUI Framework Next — Preact-inspired lightweight Web Component runtime with SSR hydration. 15KB minified, compiled-template path mapping, no hydration markers.",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  "@playwright/test": "^1.58.2",
20
20
  "@types/node": "^25.3.5",
21
21
  "typescript": "^5.9.3",
22
- "@microsoft/webui-test-support": "0.0.15"
22
+ "@microsoft/webui-test-support": "0.0.17"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc",