fix(fast-element): return compose() synchronously unless a template resolver is provided#7601
Conversation
There was a problem hiding this comment.
Pull request overview
Restores ergonomic synchronous usage of FASTElement.compose() / FASTElementDefinition.compose() in the v3 RC by introducing overloads that return a FASTElementDefinition directly for synchronous inputs, while retaining a Promise return for template-resolver scenarios, and keeping define() as the async boundary.
Changes:
- Added overloads so
compose()returns synchronously except when a template resolver is provided. - Updated
define()to accept either a direct definition or a promise of one. - Updated API reports/docs and added Playwright coverage for the new overload behavior.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| sites/website/src/docs/3.x/resources/export-sizes.md | Updates published bundle size numbers. |
| sites/website/src/docs/3.x/api/fast-element/declarative/fast-element.fastelementdefinition.md | Adds API doc entry for the additional compose overload page. |
| sites/website/src/docs/3.x/api/fast-element/declarative/fast-element.fastelementdefinition.compose.md | Updates declarative API docs to reflect the resolver-specific overload. |
| sites/website/src/docs/3.x/api/fast-element/declarative/fast-element.fastelementdefinition.compose_1.md | New generated doc page for the synchronous compose overload (declarative export). |
| sites/website/src/docs/3.x/api/fast-element.fastelementdefinition.md | Adds API doc entry for the additional compose overload page. |
| sites/website/src/docs/3.x/api/fast-element.fastelementdefinition.compose.md | Updates API docs to reflect the resolver-specific overload. |
| sites/website/src/docs/3.x/api/fast-element.fastelementdefinition.compose_1.md | New generated doc page for the synchronous compose overload. |
| sites/website/src/docs/3.x/api/fast-element.fastelementconstructor.md | Updates API docs to show multiple compose overload entries. |
| sites/website/src/docs/3.x/api/fast-element.fastelementconstructor.compose.md | Updates API docs for the resolver-bearing compose overload. |
| sites/website/src/docs/3.x/api/fast-element.fastelementconstructor.compose_3.md | New generated doc page for the synchronous compose(type, ...) overload. |
| sites/website/src/docs/3.x/api/fast-element.fastelementconstructor.compose_2.md | New generated doc page for the resolver-bearing compose(type, ...) overload. |
| sites/website/src/docs/3.x/api/fast-element.fastelementconstructor.compose_1.md | Updates generated doc page for the synchronous compose(this, ...) overload. |
| packages/fast-element/src/components/fast-element.ts | Implements compose() overloads + adjusts define() to handle sync/async compose results. |
| packages/fast-element/src/components/fast-element.pw.spec.ts | Adds tests validating sync vs async return behavior for FASTElement.compose(). |
| packages/fast-element/src/components/fast-definitions.ts | Implements FASTElementDefinition.compose() overloads and conditional Promise return. |
| packages/fast-element/src/components/fast-definitions.pw.spec.ts | Adds tests validating sync vs async return behavior for FASTElementDefinition.compose(). |
| packages/fast-element/SIZES.md | Updates package bundle size numbers. |
| packages/fast-element/docs/declarative/api-report.api.md | Updates declarative API report to include compose overload signatures. |
| packages/fast-element/docs/api-report.api.md | Updates main API report to include compose overload signatures. |
| examples/csr/todo-mobx-app/src/main.ts | Updates example to call .define() directly after compose() becomes sync. |
| change/@microsoft-fast-element-269e7b6d-2eaf-45a5-9643-391c9d9fde68.json | Beachball change file describing the API behavior change. |
janechu
left a comment
There was a problem hiding this comment.
Please provide concrete use cases for this change.
…ynchronous and asynchronous behavior
|
@janechu I updated the migration, design, and skill docs to describe the new behavior, and if this goes in I'll update my documentation PR to go into more detail. I'm not actually sure if we ever even used By making the In Fluent, we have two side effect-only import pathways: one for CSR and one for SSR. With this change, the CSR pathway can remain untouched: // accordion.definition.ts
import { tagName } from './accordion.options.js';
import { Accordion } from './accordion.js';
import { styles } from './accordion.styles.js';
import { template } from './accordion.template.js';
export const definition = Accordion.compose({
name: tagName,
template, // <--- ViewTemplate object, not async: `definition` becomes a bare `FASTElementDefinition`
styles,
});// define.ts
import { FluentDesignSystem } from '../fluent-design-system.js';
import { definition } from './accordion.definition.js';
definition.define(FluentDesignSystem.registry); // <--- no change necessary hereOn the SSR side of the coin, since we now have the // accordion.definition-async.ts
import { declarativeTemplate } from '@microsoft/fast-element/declarative.js';
import { tagName } from './accordion.options.js';
import { Accordion } from './accordion.js';
export const declarativeDefinition = Accordion.compose({ // <-- using compose here is new
name: tagName,
template: declarativeTemplate(), // <--- resolver function, `declarativeDefinition` becomes a `Promise<FASTElementDefinition>`
});// define-async.ts
import { FluentDesignSystem } from '../fluent-design-system.js';
import { declarativeDefinition } from './accordion.definition-async.js';
const definition = await declarativeDefinition; // <-- the returned promise needs to be awaited or chained with `then()`
definition.define(FluentDesignSystem.registry);This way, when we expand the functionality and feature-set of |
Pull Request
📖 Description
The v3 RC changed
FASTElement.compose()andFASTElementDefinition.compose()to always returnPromise<FASTElementDefinition>. Composition does no asynchronous work, so the Promise resolved immediately. That forced every caller toawaitor.then()a result with nothing to wait for, which broke synchronous call sites.This restores a synchronous return through method overloads.
compose()returns aFASTElementDefinitiondirectly, and returns aPromiseonly when the definition'stemplateis a resolver, which is the one input that can be asynchronous. Concrete templates, string names, and bare definitions compose synchronously.define()accepts either result and stays the async registration boundary.🎫 Issues
👩💻 Reviewer Notes
templateis a resolver function. The Promise-returning overloads inlinePartialFASTElementDefinition<TType> & { template: FASTElementTemplateResolver<TType> }instead of exporting a named type, which keeps the public surface smaller.PartialFASTElementDefinition.compose()does not resolve the template. Resolution still happens atdefine()time, which existing tests confirm by assertingdefinition.template === undefinedafter compose.📑 Test Plan
fast-definitions.pw.spec.tsandfast-element.pw.spec.ts. The tests verify thatcompose()returns synchronously for string names and concrete templates, returns aPromiseonly when given a template resolver, and leaves the template unresolved untildefine().fast-elementChromium tests pass, and the full repo build succeeds.✅ Checklist
General
$ npm run change