Skip to content

fix(microsoft-fast-build): normalize attribute keys and strip client-only bindings from SSR output#7396

Merged
janechu merged 14 commits intomainfrom
users/janechu/fix-kebab-property-bindings
Apr 6, 2026
Merged

fix(microsoft-fast-build): normalize attribute keys and strip client-only bindings from SSR output#7396
janechu merged 14 commits intomainfrom
users/janechu/fix-kebab-property-bindings

Conversation

@janechu
Copy link
Copy Markdown
Collaborator

@janechu janechu commented Apr 6, 2026

Summary

Fixes how the microsoft-fast-build Rust SSR crate handles custom element attributes when building child rendering state and producing declarative shadow DOM HTML. Rebased on top of #7398 (data-* → dataset mapping).

Attribute → child state key rules

Binding form Child state key Rationale
isEnabled="..." isenabled HTML attrs are case-insensitive; browsers lowercase
selected-user-id="42" selected-user-id Hyphens preserved
foo="{{bar}}" foo (resolved from parent state) Standard double-brace binding
data-date-of-birth="..." {"dataset": {"dateOfBirth": ...}} Handled by #7398
:myProp="{{expr}}" (skipped) Client-side only — FAST runtime sets JS properties
@click="{fn()}" (skipped) Client-side only

Attribute value rules

  • Boolean attributes (no value, e.g. disabled) → true in child state
  • All string attributes → stored as strings — count="42" is "42", not the number 42
  • Typed values must be passed via {{binding}} expressions so the resolved JsonValue flows from parent state

Client-only bindings — stripped from HTML, skipped from state

Both @event and :property bindings are removed from all rendered HTML (outer element open tag and all tags inside the declarative shadow DOM) and are not added to the child element's rendering scope. The data-fe-c binding count is preserved so the FAST runtime still allocates the correct number of binding slots.

Changes

  • directive.rs: @ and : prefixed attrs both skipped when building child state; all HTML attribute keys lowercased; attribute_to_json_value simplified (strings only, no numeric/boolean coercion for literals); dataset logic from feat(microsoft-fast-build): map data-* attributes to nested dataset state #7398 preserved
  • attribute.rs: strip_client_only_attrs removes @attr and :attr from any opening tag; refactored to use parse_element_attributes + read_tag_name
  • node.rs: strip_client_only_attrs wired into hydration tag scan so shadow template HTML is also cleaned
  • Tests updated; test_custom_element_colon_property_binding_skipped verifies property bindings are stripped from HTML and not passed to child scope; stale test names fixed
  • DESIGN.md and README.md updated to document the full attribute handling rules alongside the dataset mapping from feat(microsoft-fast-build): map data-* attributes to nested dataset state #7398

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates @microsoft/fast-build’s server-side renderer so custom element state-building better matches FAST runtime conventions (camelCase property access, : property bindings, and skipping @ event bindings), and also adds support for accessing .length on arrays in template expressions.

Changes:

  • Build child element state with :-prefix stripping, kebab-case → camelCase aliasing, and skipping @event attributes.
  • Add .length support when resolving nested properties on arrays.
  • Expand test coverage for the above, plus JSON array/object literals in custom element attributes.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
crates/microsoft-fast-build/src/directive.rs Adjusts custom element state construction (@ skip, : strip, kebab→camel alias) and extends attribute value parsing (JSON literals).
crates/microsoft-fast-build/src/context.rs Adds array .length handling in nested property resolution.
crates/microsoft-fast-build/tests/f_when.rs Adds f-when condition tests using items.length.
crates/microsoft-fast-build/tests/custom_elements.rs Adds tests for kebab→camel aliasing, : property bindings, @ skipping, and JSON literal attribute parsing.
crates/microsoft-fast-build/tests/bindings.rs Adds direct binding tests for items.length (including nested and empty cases).
crates/microsoft-fast-build/README.md Documents new custom-element attribute/state behaviors (:/@ handling, kebab→camel alias).
crates/microsoft-fast-build/DESIGN.md Updates design notes for state-building rules (skip @, strip :; camel alias).
change/@microsoft-fast-build-fix-kebab-bindings-1314e0de-ff4c-4dd5-8cc5-2e8fc04bc0aa.json Patch change file for kebab→camel and : binding fixes.
change/@microsoft-fast-build-fix-array-length-3d13d21c-1594-4fe1-babf-8123aa5e098e.json Patch change file for array .length support.

@janechu janechu marked this pull request as draft April 6, 2026 16:57
@janechu janechu changed the title fix(fast-build): convert kebab-case attrs to camelCase and handle property bindings fix(fast-build): normalise attribute keys to lowercase when building custom element state Apr 6, 2026
@janechu janechu changed the title fix(fast-build): normalise attribute keys to lowercase when building custom element state fix(fast-build): strip event binding attributes from rendered HTML and simplify child element state handling Apr 6, 2026
@janechu janechu changed the title fix(fast-build): strip event binding attributes from rendered HTML and simplify child element state handling fix(fast-build): strip client-only binding attributes from rendered HTML and simplify child element state handling Apr 6, 2026
@janechu janechu changed the title fix(fast-build): strip client-only binding attributes from rendered HTML and simplify child element state handling fix(microsoft-fast-build): normalize attribute/property bindings and strip client-only attrs from rendered HTML Apr 6, 2026
@janechu janechu changed the title fix(microsoft-fast-build): normalize attribute/property bindings and strip client-only attrs from rendered HTML fix(microsoft-fast-build): normalize attribute keys and strip client-only bindings from SSR output Apr 6, 2026
@janechu janechu marked this pull request as ready for review April 6, 2026 22:47
janechu and others added 13 commits April 6, 2026 15:55
…ssions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…perty bindings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The JSON array/object literal attribute parsing belongs on the fix-json-literal-attributes branch (PR #7395), not here.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…custom element state

Replace camelCase aliasing with simple lowercase normalisation: all
attribute keys have their colon prefix stripped, hyphens removed, and are
converted to lowercase before being stored in child element state.

This mirrors the default browser behaviour for case-insensitive HTML
attribute names, so selectedUserId, selected-user-id, and SelectedUserID
all resolve to the same selecteduserid key. Templates must reference the
lowercase form.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rmalisation

Hyphens are kept in attribute keys; only the case is lowercased.
selected-user-id stays as selected-user-id while selectedUserId
becomes selecteduserid.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ibute value types

- Add strip_event_attrs() in attribute.rs — removes @attr="{...}" from
  rendered element tags while preserving the data-fe-c binding count so
  the FAST runtime still allocates the correct number of binding slots.
  Applied to both the outer custom element tag and all tags inside the
  rendered shadow DOM template.

- Simplify attribute_to_json_value: literal attribute values are always
  String (only a valueless boolean attribute produces Bool(true)).
  Booleans and numbers must arrive via {{binding}} expressions.

- Attribute keys are kept exactly as written after stripping ':' — no
  case transformation. HTML attributes are always lowercase/kebab-case;
  property bindings preserve their original casing.

- Update tests and docs accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ercase attr keys

- Extend strip_client_only_attrs (renamed from strip_event_attrs) to also
  remove :attr property binding attributes from rendered HTML output, in
  addition to @attr event bindings. Both are FAST client-only constructs.

- Add .to_lowercase() back to attribute key normalisation: HTML attribute
  names are case-insensitive and browsers store them lowercase, so isEnabled
  becomes isenabled. Hyphens are preserved (selected-user-id unchanged).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the raw byte-scanner with a declarative approach that reuses
the existing parse_element_attributes / read_tag_name helpers:
- extract tag name via read_tag_name
- parse attributes via parse_element_attributes
- filter out @ / : prefixed names
- rebuild the tag, preserving >, />, or no-closing as appropriate

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Property bindings (:attr) represent JavaScript property names which are
case-sensitive. Strip the leading : but preserve the original casing so
:myProp -> key "myProp" in child state. Plain HTML attributes still get
lowercased (browsers normalise attribute names to lowercase).

- directive.rs: split key normalisation — :prop preserves casing, HTML
  attr lowercases
- tests: add test_custom_element_camel_property_binding; update existing
  colon binding test comment
- README.md: update attribute→state table and description; fix @click
  examples to show it stripped from rendered HTML
- DESIGN.md: update step 4 to describe separate normalisation rules

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…keys

Switch parent templates in bool-attr hydration tests from plain HTML
attributes to property bindings (:isEnabled, :activeGroup, :currentGroup)
so the child scope receives keys with their original JS property name
{{activeGroup == currentGroup}} instead of their lowercased equivalents.

Also update the README boolean binding example to show camelCase property
names in the shadow template.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Property bindings (:prop) are client-side only — like @event bindings they
are resolved by the FAST client runtime and have no meaning in SSR state.
They are already stripped from rendered HTML; now they are also not added
to the child element's rendering scope.

- directive.rs: extend the client-only skip to include : prefix alongside @
- tests/custom_elements.rs: replace colon binding tests with
  test_custom_element_colon_property_binding_skipped; remove camel test
- tests/hydration.rs: revert parent templates to plain HTML attrs
  (isEnabled, activeGroup, currentGroup) and child templates back to
  lowercase state keys (isenabled, activegroup, currentgroup)
- README.md: update table and description; fix bool-attr example back to
  lowercase state keys
- DESIGN.md: update steps 4 and 7 to describe unified client-only skip

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- test_custom_element_kebab_attr_camel_in_template →
  test_custom_element_kebab_attr_hyphens_preserved
  (no camel conversion; hyphens are kept)
- test_custom_element_multi_word_kebab_to_camel →
  test_custom_element_multi_word_kebab_attrs
  (no camel conversion; kebab keys passed through as-is)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu force-pushed the users/janechu/fix-kebab-property-bindings branch from ad2d43d to e524480 Compare April 6, 2026 23:00
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu merged commit 7665593 into main Apr 6, 2026
14 checks passed
@janechu janechu deleted the users/janechu/fix-kebab-property-bindings branch April 6, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants