This document describes the declarative rendering lifecycle inside
@microsoft/fast-element.
The FAST Element rendering lifecycle involves a coordinated process between the core runtime and its declarative entrypoint:
@microsoft/fast-element: Provides the coreFASTElementbase class and element definition system.@microsoft/fast-element/declarative.js: Provides thef-templatecustom element that processes HTML templates and attaches them to FAST elements as aViewTemplatein lieu of anhtmltemplate created during a subclassdefine()call. The preferred path usesdeclarativeTemplate()so the subclassdefine()call waits for the matching declarative template and keeps the definition concrete before registration completes.
Given a DOM which includes an f-template and a component:
<my-component text="Hello World">
<template shadowrootmode="open">
<h1><!--fe:b-->Hello World<!--fe:/b--></h1>
</template>
</my-component>
<f-template name="my-component">
<template>
<h1>{{text}}</h1>
</template>
</f-template>The following phases will then be kicked off once the JavaScript is parsed.
Custom elements begin their lifecycle by composing a definition that points at
declarativeTemplate(). The resolver waits for a matching declarative template
and returns a concrete ViewTemplate before the platform registration step.
// Custom element class definition
class MyComponent extends FASTElement {
@attr text: string = "";
}
// Register with the declarative template bridge
MyComponent.define({
name: "my-component",
template: declarativeTemplate(),
});Key characteristics of this phase:
- The element definition stays unresolved until a matching declarative template is available
- The resolved template is concrete before platform registration completes
declarativeTemplate() from @microsoft/fast-element/declarative.js
automatically ensures that f-template is defined in the same registry as the
FAST element being composed.
When an f-template element is connected to the DOM, it initiates the template attachment process.
The lifecycle flow during this phase:
- Template Discovery: The resolver waits for a matching
<f-template name="...">in the same registry as the element definition. - Template Element Connection: The matching
f-templateelement'sconnectedCallback()registers it with the declarative template bridge. - Template Processing: The bridge reads and transforms the markup, builds
the schema, applies
observerMap()/attributeMap()behavior, and resolves data bindings, directives, and other template features into theViewTemplatemodel which is also used by the@microsoft/fast-elementhtmltag template. - Template Attachment: The concrete
ViewTemplateis returned to the subclassdefine()call, which assigns it to the definition before platform registration completes.
Once the template is attached to the partial definition, the element completes its composition:
compose()Execution: The element definition internally completes its composition process- Platform Registration: The completed element definition is fully registered with the platform's custom element registry
When custom elements are instantiated in the DOM, the following occurs:
- Element Creation: The platform creates instances of the custom element
- Prerendered Content Detection:
ElementControllerdetects the existing shadow root from SSR —isPrerenderedresolvestrue - Hydration Check: If
enableHydration()was called and the template is hydratable, the element hydrates —isHydratedresolvestrue. Otherwise it falls back to client-side rendering. - Concrete Template Ready: Because
declarativeTemplate()resolved during definition,connect()starts with the final template already attached. - Hydration:
ElementControllerusestemplate.hydrate()to create aHydrationViewthat maps existing DOM nodes to binding targets usingfe:b/fe:/bmarkers
The DOM after hydration should look like this:
<my-component text="Hello World">
<template shadowrootmode="open">
<h1><!---->Hello World<!----></h1>
</template>
</my-component>The fastElementRegistry serves as the central coordination point between the
two packages and is available to consumers from
@microsoft/fast-element/registry.js:
- Stores partial element definitions created by
define() - Provides lookup mechanism via
register()for template attachment - Resolves
fastElementRegistry.whenRegistered(tagName)when a matching definition is registered - Maintains the registry of all FAST element definitions
Both packages use the Observable pattern for coordination:
- Element definition template assignment notifies observers that are waiting for a concrete template
- Template attachment triggers observable notifications to complete the lifecycle
The lifecycle includes error handling for missing components:
- Template elements throw errors if no corresponding element definition is found
- Element definitions can exist without templates (partial state)
The asynchronous nature of the lifecycle provides several performance benefits:
- Progressive Enhancement: Elements can be registered before templates are loaded
- Code Splitting: Templates can be loaded separately from element definitions
- Reduced Blocking: Template processing doesn't block element registration
- Hydration Optimization: Server-side rendered content can be hydrated efficiently
This coordinated lifecycle enables powerful scenarios like server-side rendering, progressive enhancement, and dynamic template loading while maintaining the reactive capabilities of FAST Element.
FAST Element exposes promises for hydration readiness points that are useful to application code.
Hydration must be explicitly opted into by calling enableHydration(). Await
the returned controller's whenHydrated() promise when code needs to run after
the active hydration batch completes. Pass a tag name to whenHydrated(tagName)
when code needs to wait for a specific FAST element.
import { enableHydration } from "@microsoft/fast-element/hydration.js";
const hydration = enableHydration();
await hydration.whenHydrated("my-component");
await hydration.whenHydrated();By default, hydration no-ops for later prerendered batches after the initial
batch completes. Set stopHydration: StopHydration.never in
enableHydration() when streaming Declarative Shadow DOM should continue
hydrating after the initial batch. In that mode, hydration.whenHydrated()
intentionally remains pending because hydration has no global completion point.