feat(parser): propagate template host attrs onto declarative shadow DOM host (FAST plugins)#305
Conversation
mohamedmansour
left a comment
There was a problem hiding this comment.
We cannot support this magic, I don't believe it is the right call. This will cause more maintenance overhead maintaining an allow list of attributes we don't want to propagate. This is like extending all the elements with the additional attributes they add to which doesn't conform with the w3c spec. In WebUI we are closely aligning to w3c spec, and all we are doing is an SSR for html elements with no magic.
Instead we need to build a plugin based solution where it allows the plugin to mutate the rendered element. Before you do that, we need to discuss in an issue regarding this topic.
The issue shows up in the use of I will say we are also scanning the Right now CSR works with reflecting attributes because it is fast enough, however that's just a quirk of the browser, in theory it should not work. |
|
@mohamedmansour actually the reason it might be working in CSR is that we don't define the component until we have evaluated the template and applied attributes. |
…ST plugins use them) Refactor of #305 in response to review feedback: the WebUI parser core no longer contains FAST-specific knowledge. Three generic plugin extension points now let any framework plugin mutate the rendered custom-element opening tag and the inner <template> wrapper. The FAST v2 and FAST v3 plugins use these hooks to implement the host-attribute propagation behavior; the WebUI plugin uses the safe defaults. New ParserPlugin trait hooks (all default no-op): fn on_template_root_attributes( &mut self, tag_name: &str, attributes: &[TemplateRootAttribute], ) {} fn host_element_attributes( &mut self, tag_name: &str, author_attr_names: &[&str], ) -> Option<String> { None } fn template_element_attributes(&mut self, tag_name: &str) -> Option<String> { None } TemplateRootAttribute carries each attribute's source-preserved name, optional value (quotes stripped), and verbatim raw text (without a leading space — the parser inserts a single separator). Parser-core changes (crates/webui-parser/src/lib.rs): * Removed FAST-specific helpers: TEMPLATE_INTERNAL_HOST_ATTRS, CLIENT_ONLY_HOST_ATTR_NAMES, is_client_only_host_attr_prefix, normalize_author_attr_name_for_conflict, collect_author_attr_names_normalized, extract_template_host_attrs, template_host_attrs_for, propagate_template_host_attrs_to_host, TemplateHostAttr. * Added a generic extract_template_root_attributes helper that performs structural extraction only (no skip-list policy, no normalization). * Added a generic collect_author_attr_names helper returning source-preserved attribute names. * Added a captured_template_root_attrs HashSet so the parser invokes on_template_root_attributes at most once per unique component tag. * process_component_directive now captures and notifies BEFORE emitting the host opening tag so first-usage host_element_attributes calls see populated plugin state (fix for an ordering bug in the previous implementation). * build_component_template/process_component_template now thread tag_name through and splice template_element_attributes(tag_name) into the inner <template shadowrootmode="open"> wrapper. FAST plugin changes (crates/webui-parser/src/plugin/fast_v2.rs, fast_v3.rs): * New shared crates/webui-parser/src/plugin/fast_host_attrs.rs module owns all FAST-specific policy: skip lists (@/:/?, f-ref/f-slotted/ f-children, shadowroot*), {{ ... }} dynamic skip, Shadow-DOM gate, author-conflict normalization, and per-tag caching. * FastV2ParserPlugin and FastV3ParserPlugin now compose a FastHostAttrs instance and expose set_dom_strategy(). * on_template_root_attributes captures via FastHostAttrs::capture. * host_element_attributes delegates to FastHostAttrs::produce_for_host, which returns None outside Shadow DOM and suppresses any propagated attribute whose name conflicts with an author attribute at the usage site. WebUI plugin change: imports updated; new test verifies the default no-injection behavior of the generic hooks. webui crate (crates/webui/src/lib.rs): build_protocol_inner now calls set_dom_strategy(options.dom) on FAST v2 and FAST v3 plugin instances so the propagation gate is wired through the public build pipeline. Tests: * fast_host_attrs.rs: 10 unit tests covering the capture/produce flow, skip list, conflict normalization, DOM strategy gate, and dynamic {{ ... }} suppression. * fast_v2.rs, fast_v3.rs: replace the prior propagate_template_host_attrs bool test with 4 new tests per plugin exercising the new hooks directly (capture+inject, light-DOM skip, author conflict suppression, FAST client-only filtering). * webui.rs: replaces the prior opt-out test with one verifying both new hooks default to None. * lib.rs end-to-end: extract_template_host_attrs_* removed in favor of extract_template_root_attributes_* tests targeting the generic structural extractor; the existing FAST E2E propagation tests are re-wired through the new hooks and continue to cover Shadow-DOM propagation, WebUI no-propagation, author conflict (including ? boolean-binding normalization), @event-handler non-suppression, Light-DOM gating, and per-tag capture-once semantics (now observed via captured_template_root_attrs instead of the removed cache field). Docs: * DESIGN.md: replaced the trait sketch with the three new hooks and updated the hook invocation table. * docs/guide/concepts/plugins/index.md: replaced the propagate_template_host_attrs reference with on_template_root_attributes, host_element_attributes, and template_element_attributes. cargo xtask check passes locally (license-headers, fmt, clippy, deny, test, build, examples, bench validate, docs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f44c3ba to
5b4b335
Compare
|
@mohamedmansour Thanks for the review — agreed completely on the 'magic in the parser core' concern. I've refactored to remove all FAST-specific knowledge from the WebUI parser. The parser now exposes three generic plugin extension points that any framework plugin can use to mutate the rendered custom-element opening tag and the inner fn on_template_root_attributes(
&mut self,
tag_name: &str,
attributes: &[TemplateRootAttribute],
) {}
fn host_element_attributes(
&mut self,
tag_name: &str,
author_attr_names: &[&str],
) -> Option<String> { None }
fn template_element_attributes(&mut self, tag_name: &str) -> Option<String> { None }What moved out of the parser core:
All of that now lives in a shared Also fixed an ordering bug in the previous commit:
|
…ST plugins use them) Refactor of #305 in response to review feedback: the WebUI parser core no longer contains FAST-specific knowledge. Three generic plugin extension points now let any framework plugin mutate the rendered custom-element opening tag and the inner <template> wrapper. The FAST v2 and FAST v3 plugins use these hooks to implement the host-attribute propagation behavior; the WebUI plugin uses the safe defaults. New ParserPlugin trait hooks (all default no-op): fn on_template_root_attributes( &mut self, tag_name: &str, attributes: &[TemplateRootAttribute], ) {} fn host_element_attributes( &mut self, tag_name: &str, author_attr_names: &[&str], ) -> Option<String> { None } fn template_element_attributes(&mut self, tag_name: &str) -> Option<String> { None } TemplateRootAttribute carries each attribute's source-preserved name, optional value (quotes stripped), and verbatim raw text (without a leading space — the parser inserts a single separator). Parser-core changes (crates/webui-parser/src/lib.rs): * Removed FAST-specific helpers: TEMPLATE_INTERNAL_HOST_ATTRS, CLIENT_ONLY_HOST_ATTR_NAMES, is_client_only_host_attr_prefix, normalize_author_attr_name_for_conflict, collect_author_attr_names_normalized, extract_template_host_attrs, template_host_attrs_for, propagate_template_host_attrs_to_host, TemplateHostAttr. * Added a generic extract_template_root_attributes helper that performs structural extraction only (no skip-list policy, no normalization). * Added a generic collect_author_attr_names helper returning source-preserved attribute names. * Added a captured_template_root_attrs HashSet so the parser invokes on_template_root_attributes at most once per unique component tag. * process_component_directive now captures and notifies BEFORE emitting the host opening tag so first-usage host_element_attributes calls see populated plugin state (fix for an ordering bug in the previous implementation). * build_component_template/process_component_template now thread tag_name through and splice template_element_attributes(tag_name) into the inner <template shadowrootmode="open"> wrapper. FAST plugin changes (crates/webui-parser/src/plugin/fast_v2.rs, fast_v3.rs): * New shared crates/webui-parser/src/plugin/fast_host_attrs.rs module owns all FAST-specific policy: skip lists (@/:/?, f-ref/f-slotted/ f-children, shadowroot*), {{ ... }} dynamic skip, Shadow-DOM gate, author-conflict normalization, and per-tag caching. * FastV2ParserPlugin and FastV3ParserPlugin now compose a FastHostAttrs instance and expose set_dom_strategy(). * on_template_root_attributes captures via FastHostAttrs::capture. * host_element_attributes delegates to FastHostAttrs::produce_for_host, which returns None outside Shadow DOM and suppresses any propagated attribute whose name conflicts with an author attribute at the usage site. WebUI plugin change: imports updated; new test verifies the default no-injection behavior of the generic hooks. webui crate (crates/webui/src/lib.rs): build_protocol_inner now calls set_dom_strategy(options.dom) on FAST v2 and FAST v3 plugin instances so the propagation gate is wired through the public build pipeline. Tests: * fast_host_attrs.rs: 10 unit tests covering the capture/produce flow, skip list, conflict normalization, DOM strategy gate, and dynamic {{ ... }} suppression. * fast_v2.rs, fast_v3.rs: replace the prior propagate_template_host_attrs bool test with 4 new tests per plugin exercising the new hooks directly (capture+inject, light-DOM skip, author conflict suppression, FAST client-only filtering). * webui.rs: replaces the prior opt-out test with one verifying both new hooks default to None. * lib.rs end-to-end: extract_template_host_attrs_* removed in favor of extract_template_root_attributes_* tests targeting the generic structural extractor; the existing FAST E2E propagation tests are re-wired through the new hooks and continue to cover Shadow-DOM propagation, WebUI no-propagation, author conflict (including ? boolean-binding normalization), @event-handler non-suppression, Light-DOM gating, and per-tag capture-once semantics (now observed via captured_template_root_attrs instead of the removed cache field). Docs: * DESIGN.md: replaced the trait sketch with the three new hooks and updated the hook invocation table. * docs/guide/concepts/plugins/index.md: replaced the propagate_template_host_attrs reference with on_template_root_attributes, host_element_attributes, and template_element_attributes. cargo xtask check passes locally (license-headers, fmt, clippy, deny, test, build, examples, bench validate, docs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
5b4b335 to
2c4f87c
Compare
Summary
Mirrors microsoft/fast#7521 in webui. When the FAST v2 or FAST v3 parser plugin is active and the Shadow DOM strategy is in use, static attributes declared on the inner root
<template>of a component definition are now propagated onto every host custom-element opening tag during SSR.Behavior
:or?from the author name so binding forms (:foo,?disabled) suppress the static propagation.@event prefixes do not suppress (they target a different namespace).@,:, or?, and the literal namesf-ref,f-slotted,f-children.shadowrootmode,shadowrootadoptedstylesheets.{{ ... }}host attribute values are out of scope for this pass — only fully static template host attrs propagate.<template>wrapper of the component definition is consulted; nested templates are ignored.false— propagation is FAST-only by design.Implementation
falsehook onParserPlugin:true.HtmlParser::template_host_attrs_cache: HashMap<String, Vec<TemplateHostAttr>>parses each component's host attributes exactly once per unique tag name.extract_template_host_attrs,propagate_template_host_attrs_to_host, etc.) use tree-sitter with iterative traversal — no recursion, no regex per the performance contract.process_tag_attributesand before the plugin'sfinish_elementhook so FAST binding markers remain after the real host attributes on the rendered opening tag.Docs
DESIGN.md— documented the new trait hook and the propagation contract under the Parser Plugin System section.docs/guide/concepts/plugins/index.md— added the hook to the publicParserPlugintrait reference with an opt-in description.Tests
extract_template_host_attrs: no template wrapper, static attr extraction, skipping of@/:/?/f-*/shadowroot*names, skipping of dynamic{{…}}values, root-only behavior.?normalization),@clickdoes not suppress an unrelated static attribute, Light DOM gating, and per-tag caching observed via the cache field.Quality gate
cargo xtask checkpasses locally (license-headers → fmt → clippy → deny → test → build → examples → bench validate → docs).Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com