feat(fast-html): add AttributeMap class for automatic @attr definitions #7354
Merged
feat(fast-html): add AttributeMap class for automatic @attr definitions #7354
Conversation
1e71068 to
28fd2c1
Compare
28fd2c1 to
c31bc76
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds an AttributeMap feature to @microsoft/fast-html to automatically define @attr-style reactive properties for leaf template bindings, enabled via TemplateElement.options({ ..., attributeMap: "all" }).
Changes:
- Introduces
AttributeMap+attributeMapelement option and wires it intoTemplateElementprocessing. - Adds a new fixture and Playwright coverage validating attribute/property identity mapping and re-render behavior via
setAttribute. - Updates package docs and fixture build script to document and include the new capability.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/fast-html/src/components/template.ts | Adds attributeMap option plumbing and invokes AttributeMap.defineProperties() during template processing. |
| packages/fast-html/src/components/index.ts | Exports AttributeMap and AttributeMapOption from the public components entrypoint. |
| packages/fast-html/src/components/attribute-map.ts | Implements schema-driven detection of leaf bindings and defines them as @attr accessors. |
| packages/fast-html/src/components/attribute-map.spec.ts | Adds Playwright tests validating generated accessors, exclusions, reflection, and lookup patch behavior. |
| packages/fast-html/scripts/build-fixtures.js | Registers the new attribute-map fixture in the fixture build list. |
| packages/fast-html/test/fixtures/attribute-map/entry.html | Adds the fixture entry page for E2E tests. |
| packages/fast-html/test/fixtures/attribute-map/index.html | Adds the generated SSR fixture output HTML for the new fixture. |
| packages/fast-html/test/fixtures/attribute-map/main.ts | Defines test custom elements and configures TemplateElement.options({ attributeMap: "all" }). |
| packages/fast-html/test/fixtures/attribute-map/state.json | Initial fixture state for leaf bindings (foo, foo-bar). |
| packages/fast-html/test/fixtures/attribute-map/templates.html | Fixture templates exercising leaf bindings and click handlers. |
| packages/fast-html/test/fixtures/attribute-map/attribute-map.spec.ts | Adds fixture-based Playwright E2E tests (DOM behavior + reflection). |
| packages/fast-html/README.md | Documents the new attributeMap option, behavior, and usage. |
| packages/fast-html/DESIGN.md | Adds design/behavior notes explaining AttributeMap and “leaf binding” rules. |
| change/@microsoft-fast-html-90d4b1e4-b272-4691-9b04-e6290ebc8b2f.json | Adds the change file for release notes/versioning. |
AttributeMap inspects the JSON schema generated by TemplateElement for a custom element and defines @attr properties on the class prototype for all leaf-level bindings (simple {{foo}} and attribute {{bar}} paths that have no nested properties, no type, and no anyOf). - Add AttributeMap class in packages/fast-html/src/components/attribute-map.ts - Reads root properties from the Schema - Skips properties with nested 'properties', 'type', or 'anyOf' (not leaves) - Skips properties that already have an @attr or @observable accessor - Converts camelCase property names to dash-case (fooBar -> foo-bar) - Creates AttributeDefinition instances via Observable.defineProperty - Updates FASTElementDefinition.attributeLookup and propertyLookup - Integrate AttributeMap into TemplateElement (template.ts / index.ts) - Add AttributeMapOption constant and type - Add attributeMap option to ElementOptions interface - TemplateElement.options() stores attributeMap option - connectedCallback instantiates AttributeMap when attributeMap === 'all' - defineProperties() called after schema is fully populated - Add tests in attribute-map.spec.ts (browser E2E tests) - Add fixture in test/fixtures/attribute-map/ Usage: TemplateElement.options({ 'my-element': { attributeMap: 'all' }, }); Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…emplate updates Update AttributeMap.defineProperties() to also push each newly created attribute name to the existing observedAttributes array on the class. For all f-template-registered elements, registry.define() (which causes the browser to cache observedAttributes) is called AFTER defineProperties() runs, because composeAsync() waits for definition.template to be set before resolving. This creates a reliable window to mutate the array so the browser observes the dynamically-added attributes. Update tests to use element.setAttribute() inside page.evaluate instead of button clicks, testing both directions: - setAttribute() → attributeChangedCallback() → property → template re-render - property assignment → attribute reflection via DOM Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…om attribute-map fixture Tests no longer need window.Observable or window.__FAST__ to verify AttributeMap behaviour. Use Object.getOwnPropertyDescriptor to check that accessor get/set was added to the prototype, and verify attribute lookup via setAttribute behaviour instead of inspecting internal registry state. Also update the beachball change type to prerelease. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…by AttributeMap Add an AttributeMapWithExistingAttrElement fixture element with a pre-defined @attr foo property (default value 'original'). After f-template processes with attributeMap: 'all', tests confirm that: - the @attr default value is preserved (accessor was not re-defined) - setAttribute() still routes through the original @attr definition Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Attribute names are no longer converted from camelCase to dash-case.
The binding key as written in the template is used as-is for both the
attribute name and the property name (e.g. {{foo-bar}} → attribute
foo-bar, property foo-bar).
Because HTML attributes are case-insensitive, binding keys should use
lowercase names (optionally dash-separated). Properties containing
dashes must be accessed via bracket notation (element["foo-bar"]).
- Remove camelCaseToDashCase method from AttributeMap
- Update fixture template to use {{foo-bar}} instead of {{fooBar}}
- Update all tests to use bracket notation for dash-case properties
- Add AttributeMap documentation to DESIGN.md and README.md
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ixture Split the hand-written index.html into entry.html, templates.html, and state.json to match the pattern used by all other fixtures. Add attribute-map to the build-fixtures.js list so index.html is generated by npm run build:fixtures with SSR-rendered shadow DOM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove async from test.describe callbacks in both spec files (Playwright requires synchronous describe callbacks) - Convert existingAccessors to a Set for O(1) lookup instead of O(N) - Update definition.attributes array alongside attributeLookup and propertyLookup to keep the definition's canonical attribute list in sync - Add .define() call to attributeMap README usage snippet - Add comment explaining why observedAttributes mutation via push is safe (FAST uses a concrete array, and registry.define() runs after this method) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1dd91d2 to
64b8760
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
📖 Description
Adds an
AttributeMapclass to@microsoft/fast-htmlthat automatically defines@attrproperties on a custom element's class prototype based on the JSON schema generated byTemplateElement.When
attributeMap: "all"is configured for an element viaTemplateElement.options(),AttributeMapinspects the schema after template processing and creates reactive properties for all leaf bindings — simple template expressions like{{foo}}orid="{{foo-bar}}"that have no nested properties, no array type, and no child element references.Key behaviours:
{{foo-bar}}→ attributefoo-bar, propertyfoo-bar)element["foo-bar"]){{user.name}}result inuserhaving sub-propertiesin the schema and are excluded@attror@observableare left untouchedFASTElementDefinition:attributeLookupandpropertyLookupare patched soattributeChangedCallbackcorrectly delegates to the newAttributeDefinitionObservable.definePropertywith anAttributeDefinitioninstance directlyUsage
This registers
greeting(attributegreeting, propertygreeting) andfirst-name(attributefirst-name, propertyfirst-name) as@attrproperties, enablingsetAttribute("first-name", "Jane")to trigger a template re-render automatically.This mirrors the existing
ObserverMapintegration pattern.📑 Test Plan
Tests were added across two spec files:
packages/fast-html/src/components/attribute-map.spec.ts— verifies accessor registration, no-normalization identity mapping, event handler exclusion, andFASTElementDefinitionlookup updatespackages/fast-html/test/fixtures/attribute-map/attribute-map.spec.ts— end-to-end tests verifying template re-rendering when properties are set via JavaScript orsetAttributeThe attribute-map fixture follows the standard fixture pattern (
entry.html,templates.html,state.json) and itsindex.htmlis generated bynpm run build:fixtureswith SSR-rendered shadow DOM.All existing tests continue to pass.
✅ Checklist
General
$ npm run change⏭ Next Steps
observedAttributescan be updated after element registration to fully support the DOM attribute → property direction for elements registered before the template is processed