Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: modularize hydration and expose lifecycle callbacks via enableHydration() and declarativeTemplate()",
"packageName": "@microsoft/fast-element",
"email": "7559015+janechu@users.noreply.github.com",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Update imports for styles.js subpath export",
"packageName": "@microsoft/fast-router",
"email": "7559015+janechu@users.noreply.github.com",
"dependentChangeType": "none"
}
2 changes: 1 addition & 1 deletion examples/todo-app/src/todo-app.styles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { css } from "@microsoft/fast-element";
import { css } from "@microsoft/fast-element/styles.js";

export const styles = css`
:host {
Expand Down
2 changes: 1 addition & 1 deletion examples/todo-app/src/todo-form.styles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { css } from "@microsoft/fast-element";
import { css } from "@microsoft/fast-element/styles.js";

export const styles = css`
form {
Expand Down
123 changes: 74 additions & 49 deletions packages/fast-element/DECLARATIVE_RENDERING_LIFECYCLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ Once the template is attached to the partial definition, the element completes i
When custom elements are instantiated in the DOM, the following occurs:

1. **Element Creation**: The platform creates instances of the custom element
2. **Prerendered Content Detection**: `ElementController` detects the existing shadow root from SSR and sets `isPrerendered = true`
3. **Concrete Template Ready**: Because `declarativeTemplate()` resolved during
2. **Prerendered Content Detection**: `ElementController` detects the existing shadow root from SSR โ€” `isPrerendered` resolves `true`
3. **Hydration Check**: If `enableHydration()` was called and the template is hydratable, the element hydrates โ€” `isHydrated` resolves `true`. Otherwise it falls back to client-side rendering.
4. **Concrete Template Ready**: Because `declarativeTemplate()` resolved during
definition, `connect()` starts with the final template already attached.
4. **Hydration**: `ElementController` uses `template.hydrate()` to create a
5. **Hydration**: `ElementController` uses `template.hydrate()` to create a
`HydrationView` that maps existing DOM nodes to binding targets using `fe:b`
/ `fe:/b` markers

Expand Down Expand Up @@ -154,23 +155,21 @@ FAST HTML provides a set of lifecycle callbacks that allow you to hook into vari

### Available Callbacks

The lifecycle callbacks are organized into three categories:
The lifecycle callbacks are split between two APIs:

**Template Registration Callbacks:**
**Per-element callbacks** โ€” passed to `declarativeTemplate()`:
- `elementDidRegister(name: string)` - Called after the JavaScript class definition has been registered as a partial definition
- `templateWillUpdate(name: string)` - Called before the template has been evaluated and assigned to the definition

**Template Processing Callbacks:**
- `templateDidUpdate(name: string)` - Called after the template has been assigned to the definition
- `elementDidDefine(name: string)` - Called after the custom element has been fully defined with the platform

**Hydration Callbacks:**
- `hydrationStarted()` - Called once when the first prerendered element begins hydrating
- `elementWillHydrate(source: HTMLElement)` - Called before an element begins hydration
- `elementDidHydrate(source: HTMLElement)` - Called after an element completes hydration

**Global hydration callbacks** โ€” passed to `enableHydration()`:
- `hydrationStarted()` - Called once when the first prerendered element begins hydrating
- `hydrationComplete()` - Called once after all prerendered elements have completed hydration

Hydration callbacks are tracked at the element level by `ElementController`. The `hydrationComplete` callback fires only after every prerendered element has finished binding.
The `hydrationComplete` callback fires only after every prerendered element has finished binding.

### Callback Execution Order

Expand All @@ -186,7 +185,7 @@ Template Processing Phase (asynchronous):
4. templateDidUpdate(name)
5. elementDidDefine(name)

Hydration Phase (per element):
Hydration Phase (per element, only when enableHydration() has been called):
6. hydrationStarted() [once, on first element]
7. elementWillHydrate(source)
8. [Hydration occurs]
Expand All @@ -200,64 +199,90 @@ Completion (called once for all elements):

### Configuring Callbacks

Configure callbacks using `TemplateElement.config()` before defining the template element:
Hydration must be explicitly opted into by calling `enableHydration()`. Per-element
callbacks are passed directly to `declarativeTemplate()`:

```typescript
import { TemplateElement, type HydrationLifecycleCallbacks } from "@microsoft/fast-element/declarative.js";
import { enableHydration } from "@microsoft/fast-element/hydration.js";
import { declarativeTemplate } from "@microsoft/fast-element/declarative.js";

TemplateElement.config({
elementDidRegister(name) {
console.log(`${name} registered`);
},
templateWillUpdate(name) {
console.log(`${name} template updating`);
},
templateDidUpdate(name) {
console.log(`${name} template updated`);
},
elementDidDefine(name) {
console.log(`${name} fully defined`);
},
elementWillHydrate(source) {
console.log(`${source.localName} starting hydration`);
},
elementDidHydrate(source) {
console.log(`${source.localName} hydrated`);
// Global hydration events
enableHydration({
hydrationStarted() {
console.log("Hydration started");
},
hydrationComplete() {
console.log('All elements hydrated');
}
console.log("All elements hydrated");
},
});

// Per-element lifecycle callbacks
MyComponent.define({
name: "my-component",
template: declarativeTemplate({
elementDidRegister(name) {
console.log(`${name} registered`);
},
templateWillUpdate(name) {
console.log(`${name} template updating`);
},
templateDidUpdate(name) {
console.log(`${name} template updated`);
},
elementDidDefine(name) {
console.log(`${name} fully defined`);
},
elementWillHydrate(source) {
console.log(`${source.localName} starting hydration`);
},
elementDidHydrate(source) {
console.log(`${source.localName} hydrated`);
},
}),
});
```

### Use Cases

**Performance Monitoring:**
```typescript
TemplateElement.config({
elementWillHydrate(source) {
performance.mark(`${source.localName}-hydration-start`);
},
elementDidHydrate(source) {
performance.mark(`${source.localName}-hydration-end`);
performance.measure(`${source.localName}-hydration`, `${source.localName}-hydration-start`, `${source.localName}-hydration-end`);
},
import { enableHydration } from "@microsoft/fast-element/hydration.js";
import { declarativeTemplate } from "@microsoft/fast-element/declarative.js";

enableHydration({
hydrationComplete() {
const measures = performance.getEntriesByType('measure');
const measures = performance.getEntriesByType("measure");
// Send metrics to analytics
}
},
});

MyComponent.define({
name: "my-component",
template: declarativeTemplate({
elementWillHydrate(source) {
performance.mark(`${source.localName}-hydration-start`);
},
elementDidHydrate(source) {
performance.mark(`${source.localName}-hydration-end`);
performance.measure(
`${source.localName}-hydration`,
`${source.localName}-hydration-start`,
`${source.localName}-hydration-end`,
);
},
}),
});
```

**Loading State Management:**
```typescript
TemplateElement.config({
enableHydration({
hydrationStarted() {
document.body.classList.add('hydrating');
document.body.classList.add("hydrating");
},
hydrationComplete() {
document.body.classList.remove('hydrating');
document.body.classList.add('interactive');
}
document.body.classList.remove("hydrating");
document.body.classList.add("interactive");
},
});
```
Loading
Loading
โšก