Skip to content

Commit bc78e98

Browse files
janechuCopilot
andcommitted
refactor: remove deferred template option
Drop the templateOptions defer-and-hydrate API from fast-element so the core resolver pipeline no longer preserves the old deferred-template registration path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 15b9fa6 commit bc78e98

54 files changed

Lines changed: 101 additions & 379 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "major",
3+
"comment": "Remove TemplateOptions from fast-element definitions and drop templateOptions-based connection/define waiting.",
4+
"packageName": "@microsoft/fast-element",
5+
"email": "7559015+janechu@users.noreply.github.com",
6+
"dependentChangeType": "none"
7+
}

packages/fast-element/DECLARATIVE_DESIGN.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -533,21 +533,21 @@ tagged templates produce.
533533
| `children(prop)` | FAST directive used for `f-children` |
534534
| `ref(prop)` | FAST directive used for `f-ref` |
535535

536-
### Deferred template attachment via define
536+
### Template attachment after define
537537

538-
Standard `FASTElement.define()` returns a `Promise` that resolves immediately when a template is provided at definition time. When `templateOptions` is `"defer-and-hydrate"` and no template is provided, the `Promise` resolves after a `<f-template>` supplies one via `register()`. This unified API replaces the previous `defineAsync()` / `composeAsync()` methods.
538+
Standard `FASTElement.define()` returns a `Promise` that resolves immediately once the definition has been composed and any async template resolver has settled. Declarative HTML can define a host element without an initial template and let `<f-template>` attach the template later through `FASTElementDefinition.template`. This unified API replaces the previous `defineAsync()` / `composeAsync()` methods.
539539

540540
---
541541

542542
## Hydration Model
543543

544-
When `templateOptions: "defer-and-hydrate"` is used, the server must render:
544+
For declarative hydration, the server must render:
545545

546546
1. The custom element tag with its attributes and initial state.
547547
2. A `<template shadowrootmode="open">` containing pre-rendered HTML annotated with FAST's hydration markers.
548548
3. An `<f-template>` element somewhere in the page that carries the template definition.
549549

550-
Connection gating is handled by the template-pending guard in `ElementController.connect()`. When `templateOptions` is `"defer-and-hydrate"` and no template is available yet, `connect()` returns early. An Observable subscription on `"template"` retriggers `connect()` when the template arrives. The `defer-hydration` and `needs-hydration` attributes are no longer needed in server-rendered markup.
550+
If a template is attached after an element has already connected, the observable `template` update recreates the controller so hydration can proceed against the existing prerendered markup. The `defer-hydration` and `needs-hydration` attributes are no longer needed in server-rendered markup.
551551

552552
### Hydration marker formats
553553

packages/fast-element/DECLARATIVE_HTML.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ the package-level hydration overview, see the
2020
After registering the declarative entrypoint as shown in the README, templates
2121
are associated with an element through
2222
`<f-template name="[custom-element-name]"><template>...</template></f-template>`.
23-
The host custom element should be defined with
24-
`templateOptions: "defer-and-hydrate"`.
23+
The host custom element should be defined before the declarative runtime
24+
processes the matching `<f-template>`.
2525

2626
Example:
2727
```html

packages/fast-element/DECLARATIVE_MIGRATION.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,16 @@ See the [`@microsoft/fast-element` MIGRATION.md](../fast-element/MIGRATION.md#hy
3838
1. Replace `RenderableFASTElement(MyComponent).defineAsync({...})` with `MyComponent.define({...})`.
3939

4040
```typescript
41-
// Before
42-
import { RenderableFASTElement } from "@microsoft/fast-html";
43-
RenderableFASTElement(MyComponent).defineAsync({
44-
name: "my-component",
45-
templateOptions: "defer-and-hydrate",
46-
});
47-
48-
// After
49-
MyComponent.define({
50-
name: "my-component",
51-
templateOptions: "defer-and-hydrate",
52-
});
41+
// Before
42+
import { RenderableFASTElement } from "@microsoft/fast-html";
43+
RenderableFASTElement(MyComponent).defineAsync({
44+
name: "my-component",
45+
});
46+
47+
// After
48+
MyComponent.define({
49+
name: "my-component",
50+
});
5351
```
5452

5553
2. Remove `prepare()` methods. Move any initialization logic to `connectedCallback`:

packages/fast-element/DECLARATIVE_RENDERING.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,10 @@ export class MyComponent extends FASTElement {
9191

9292
MyComponent.define({
9393
name: "my-component",
94-
templateOptions: "defer-and-hydrate",
9594
});
9695
```
9796

98-
When the element connects, `ElementController` automatically detects the existing shadow root from SSR and sets `isPrerendered = true`. The template-pending guard in `ElementController.connect()` ensures the element waits for its template before hydrating. The `defer-hydration` and `needs-hydration` attributes are no longer needed — connection gating is handled internally by the template-pending guard.
97+
When the element connects, `ElementController` automatically detects the existing shadow root from SSR and sets `isPrerendered = true`. If the template is attached after the element has already connected, the observable `template` update recreates the controller so hydration can proceed. The `defer-hydration` and `needs-hydration` attributes are no longer needed.
9998

10099
## Hydration Comments and Datasets
101100

packages/fast-element/DECLARATIVE_RENDERING_LIFECYCLE.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ core runtime and its declarative entrypoint:
1313
- **`@microsoft/fast-element/declarative.js`**: Provides the `f-template`
1414
custom element that processes HTML templates and attaches them to FAST
1515
elements as a `ViewTemplate` in lieu of an `html` template created during
16-
`FASTElement.define()`. When using `f-template` the `FASTElement.define()`
17-
method is called with `templateOptions: "defer-and-hydrate"` to defer
18-
template attachment.
16+
`FASTElement.define()`. When using `f-template`, the host element can be
17+
defined without an initial template and the declarative runtime attaches the
18+
template later.
1919

2020
## Lifecycle Phases
2121

@@ -39,23 +39,24 @@ The following phases will then be kicked off once the JavaScript is parsed.
3939

4040
### Phase 1: Partial Element Registration
4141

42-
Custom elements begin their lifecycle by registering as partial definitions with the FAST Element Registry using the `define()` method with `templateOptions: "defer-and-hydrate"`. This allows the element to be registered before its template is available.
42+
Custom elements begin their lifecycle by registering with FAST via the
43+
`define()` method before their declarative template is available.
4344

4445
```typescript
4546
// Custom element class definition
4647
class MyComponent extends FASTElement {
4748
@attr text: string = "";
4849
}
4950

50-
// Register as partial definition - element is registered but incomplete
51+
// Register the host element before the declarative template is attached
5152
MyComponent.define({
5253
name: "my-component",
5354
});
5455
```
5556

5657
Key characteristics of this phase:
57-
- Element is in a "partial" state waiting for template attachment
58-
- `templateOptions` allows for hydration options to be provided. TBD see [this issue](https://github.com/orgs/microsoft/projects/240/views/17?pane=issue&itemId=127653173&issue=microsoft%7Cfast%7C7173).
58+
- Element is registered before template attachment
59+
- The definition is completed later when `<f-template>` assigns `definition.template`
5960

6061
### Phase 2: Template Element Definition
6162

@@ -82,20 +83,22 @@ The lifecycle flow during this phase:
8283
3. **Template Processing**: Processes the HTML template, resolving data bindings, directives, and other template features into the `ViewTemplate` model which is also used by the `@microsoft/fast-element` `html` tag template
8384
4. **Template Attachment**: Attaches the processed template to the partial element definition via `registeredFastElement.template = resolvedTemplate`
8485

85-
### Phase 4: Composition Completion
86+
### Phase 4: Template Activation
8687

87-
Once the template is attached to the partial definition, the element completes its composition:
88+
Once the template is attached to the registered definition, FAST activates it
89+
for both future and already-connected elements:
8890

89-
1. **`compose()` Execution**: The element definition internally completes its composition process
90-
2. **Platform Registration**: The completed element definition is fully registered with the platform's custom element registry
91+
1. **Definition Update**: `TemplateElement` assigns the parsed `ViewTemplate` to `registeredFastElement.template`
92+
2. **Observable Notification**: Connected elements observing the definition recreate their controller when the `template` property changes
93+
3. **Future Connections**: New element instances use the attached template immediately
9194

9295
### Phase 5: Element Instantiation and Hydration
9396

9497
When custom elements are instantiated in the DOM, the following occurs:
9598

9699
1. **Element Creation**: The platform creates instances of the custom element
97100
2. **Prerendered Content Detection**: `ElementController` detects the existing shadow root from SSR and sets `isPrerendered = true`
98-
3. **Template-Pending Guard**: If `templateOptions` is `"defer-and-hydrate"` and no template is available yet, `connect()` returns early. An Observable subscription on `"template"` retriggers `connect()` when the template arrives.
101+
3. **Late Template Attachment**: If an element connected before its template was attached, the observable `template` change recreates its controller.
99102
4. **Hydration**: Once the template is available, `ElementController` uses `template.hydrate()` to create a `HydrationView` that maps existing DOM nodes to binding targets using `fe:b` / `fe:/b` markers
100103

101104
The DOM after hydration should look like this:
@@ -123,7 +126,7 @@ The `fastElementRegistry` serves as the central coordination point between the t
123126
Both packages use the Observable pattern for coordination:
124127

125128
- `FASTElementDefinition.register()` uses `Observable.getNotifier()` to notify when elements are registered
126-
- Template attachment triggers observable notifications to complete the lifecycle
129+
- Template attachment triggers observable `template` notifications so connected elements can complete rendering or hydration
127130

128131
## Error Handling
129132

packages/fast-element/DESIGN.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ registry.
9494
- Holds the element's `FASTElementDefinition` (name, template, styles, observed attributes).
9595
- Manages a `Stages` state machine: `disconnected → connecting → connected → disconnecting → disconnected`.
9696
- Exposes `isPrerendered: Promise<boolean>` which resolves to `true` after prerendered content has been hydrated, or `false` when the component is client-side rendered. The `ViewController` interface also exposes `isPrerendered` as `Promise<boolean>` for custom directives. Attribute-skip logic during the hydration bind uses an internal `_skipAttrUpdates` flag that is never exposed as a public boolean.
97-
- On `connect()`: restores pre-upgrade observable values, calls `connectedCallback` on all `HostBehavior`s, renders the template into the shadow root, and applies styles. When `templateOptions` is `"defer-and-hydrate"` and no template is available yet, `connect()` returns early; an Observable subscription on `"template"` retriggers `connect()` when the template arrives (template-pending guard).
97+
- On `connect()`: restores pre-upgrade observable values, calls `connectedCallback` on all `HostBehavior`s, renders the current template into the shadow root when one is available, and applies styles.
9898
- Rendering is split into two modular paths via `renderPrerendered()` and `renderClientSide()`:
9999
- **Prerendered**: `renderPrerendered()` registers the element in the static hydration tracker, swaps `onAttributeChangedCallback` to a no-op so the upgrade-time burst of callbacks is discarded, hydrates the existing DOM via `template.hydrate()`, then restores the standard handler and removes the element from the tracker. After this point, all future attribute changes flow through the real handler with zero overhead.
100100
- **Client-side**: `renderClientSide()` clones the compiled fragment, binds, and appends to the host — the standard path with no prerender logic.
@@ -103,7 +103,7 @@ registry.
103103
- `onAttributeChangedCallback()` is the standard handler that processes attribute changes. During the prerendered bind, it is temporarily swapped to a no-op (see above) to avoid redundant processing of server-rendered attribute values.
104104
- Exposes `addBehavior` / `removeBehavior` for dynamic `HostBehavior` management (used by `ElementStyles`).
105105

106-
`FASTElementDefinition` wraps all the metadata for a custom element class: its tag name, template, styles, and observed attribute list. It is created by `FASTElement.compose()` (which returns `Promise<FASTElementDefinition>`, always resolving immediately) and registered globally via `fastElementRegistry`. `PartialFASTElementDefinition.template` may be either a concrete `ElementViewTemplate` or a `FASTElementTemplateResolver` function that receives the composed definition and returns the concrete template (sync or async). `FASTElement.define()` returns `Promise<TType>` — resolving immediately for complete definitions, deferring when `templateOptions` is `"defer-and-hydrate"` and no template is provided, and resolving template resolver functions only after extensions have had a chance to update the definition. `FASTElementDefinition.register()` returns `Promise<Function>` — resolving when a definition with the given name has been registered.
106+
`FASTElementDefinition` wraps all the metadata for a custom element class: its tag name, template, styles, and observed attribute list. It is created by `FASTElement.compose()` (which returns `Promise<FASTElementDefinition>`, always resolving immediately) and registered globally via `fastElementRegistry`. `PartialFASTElementDefinition.template` may be either a concrete `ElementViewTemplate` or a `FASTElementTemplateResolver` function that receives the composed definition and returns the concrete template (sync or async). `FASTElement.define()` returns `Promise<TType>` — resolving immediately for complete definitions or definitions without an initial template, and resolving async template resolver functions only after extensions have had a chance to update the definition. `FASTElementDefinition.register()` returns `Promise<Function>` — resolving when a definition with the given name has been registered.
107107

108108
#### Extensions
109109

@@ -375,9 +375,7 @@ flowchart TD
375375
CTOR[constructor] --> EC[ElementController.forCustomElement creates or locates the controller]
376376
EC --> ATTACH[Controller captures element + definition, sets $fastController]
377377
378-
CONN[connectedCallback] --> TGUARD{templateOptions = defer-and-hydrate\nAND no template yet?}
379-
TGUARD -->|yes| WAIT[Return early — Observable subscription\non 'template' retriggers connect]
380-
TGUARD -->|no| STAGE[stage = connecting]
378+
CONN[connectedCallback] --> STAGE[stage = connecting]
381379
STAGE --> PRERENDER{Existing shadow root\nfrom SSR/DSD?}
382380
PRERENDER -->|yes| SETFLAG[isPrerendered = true]
383381
PRERENDER -->|no| NORMAL[isPrerendered = false]
@@ -499,7 +497,7 @@ Below is a conceptual map of the major subsystems and their relationships:
499497
1. Developer writes a class extending `FASTElement`, decorates properties with `@observable` / `@attr`, and calls `FASTElement.define({ name, template, styles })`.
500498
2. `FASTElement.define``FASTElementDefinition.compose(...).define()` registers the element with the Custom Element Registry.
501499
3. When the browser upgrades the element, `ElementController.forCustomElement(element)` is called in the constructor.
502-
4. On `connectedCallback`, the controller renders the template into the shadow root. If the element already has a shadow root from SSR (prerendered content), `renderPrerendered()` uses `template.hydrate()` to map existing DOM nodes to binding targets instead of cloning new DOM. If `templateOptions` is `"defer-and-hydrate"` and no template is available yet, `connect()` returns early and retriggers when the template arrives. Compilation is lazy: the first render call triggers `Compiler.compile()`, subsequent calls clone the already-compiled `DocumentFragment`.
500+
4. On `connectedCallback`, the controller renders the template into the shadow root. If the element already has a shadow root from SSR (prerendered content), `renderPrerendered()` uses `template.hydrate()` to map existing DOM nodes to binding targets instead of cloning new DOM. If no template is available yet, the element connects without rendering until a later `definition.template` update recreates the controller. Compilation is lazy: the first render call triggers `Compiler.compile()`, subsequent calls clone the already-compiled `DocumentFragment`.
503501
5. `HTMLView.bind(source)` wires up each `ViewBehavior`. `oneWay` bindings create `ExpressionNotifier`s that track observable dependencies automatically.
504502
6. When an observed property changes, its notifier fans out to all subscribers. Each binding enqueues a DOM update via `Updates`. The next animation frame drains the queue and applies the mutations.
505503
7. On `disconnectedCallback`, `HTMLView.unbind()` tears down all bindings; behaviors disconnect; styles are removed.
@@ -550,7 +548,7 @@ src/
550548
├── components/
551549
│ ├── fast-element.ts # FASTElement, @customElement
552550
│ ├── element-controller.ts # ElementController, Stages
553-
│ ├── fast-definitions.ts # FASTElementDefinition, TemplateOptions
551+
│ ├── fast-definitions.ts # FASTElementDefinition
554552
│ └── attributes.ts # AttributeDefinition, @attr, converters
555553
├── di/
556554
│ └── di.ts # DI container, decorators, resolvers, Registration

0 commit comments

Comments
 (0)