feat: discover and convert FAST f-templates through parser plugins - #378
feat: discover and convert FAST f-templates through parser plugins#378Jane Chu (janechu) wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends WebUI’s FAST integrations to support “authored” FAST component sources written as a single <f-template>, letting the authored name override the filename-derived tag and preserving the authored inner <template> as the client artifact while converting supported FAST declarative directives for the SSR parse view.
Changes:
- Introduces a framework-neutral
ParserPlugin::component_source_transformhook and wires it into component registration so plugins can resolve the registry key and provide distinct SSR/artifact template views. - Implements shared FAST
<f-template>scanning + conversion viamicrosoft-fast-convert, and updates FAST v2/v3 plugins to use it (plus treating:propertybindings as skipped-and-counted). - Updates DESIGN/docs/README to document authored FAST behavior, and adds regression tests across
fast,fast_v2, andfast_v3.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
README.md |
Documents authored FAST <f-template> behavior and the SSR vs retained-artifact flow. |
docs/guide/concepts/plugins/index.md |
Adds FAST plugin usage guidance and documents the new component_source_transform hook in the plugin API docs. |
DESIGN.md |
Specifies the new component-source transform extension point and FAST authored-template semantics. |
crates/webui/src/lib.rs |
Adds an integration test ensuring all FAST plugin variants build authored <f-template> components correctly. |
crates/webui-parser/src/plugin/mod.rs |
Adds ComponentSource* types and the ParserPlugin::component_source_transform default method. |
crates/webui-parser/src/plugin/fast_v2.rs |
Wires the shared transform into FAST v2 and updates attribute classification for :property bindings with tests. |
crates/webui-parser/src/plugin/fast_v3.rs |
Wires the shared transform into FAST v3 and updates attribute classification for :property bindings with tests. |
crates/webui-parser/src/plugin/fast_shared.rs |
New shared FAST <f-template> transform module: detection, conversion, artifact retention, and diagnostics. |
crates/webui-parser/src/lib.rs |
Installs the plugin transform into the component registry and allows registry-provided artifact source to drive artifact template generation. |
crates/webui-parser/src/component_registry.rs |
Adds transform application during registration, supports renaming the registry key, and stores optional retained artifact sources. |
crates/webui-parser/Cargo.toml |
Adds microsoft-fast-convert as a workspace dependency. |
Cargo.toml |
Centralizes microsoft-fast-convert under [workspace.dependencies]. |
Cargo.lock |
Locks the new dependency and its transitive requirements. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 14 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
crates/webui-parser/src/plugin/fast_shared.rs:31
- New diagnostic codes ("unsupported-multiple-f-templates" / "invalid-fast-template") are introduced as local string constants.
crates/webui-parser/src/diagnostic.rsdocumentsdiagnostic::codesas the centralized, stable API surface for machine-readable codes (see around lines 55-63). These FAST codes should be added there and referenced from this module so downstream tooling can rely on a single canonical set of codes.
/// Diagnostic code for a component source with multiple `<f-template>` blocks.
const UNSUPPORTED_MULTIPLE_F_TEMPLATES: &str = "unsupported-multiple-f-templates";
/// Diagnostic code for a FAST template that cannot be converted to WebUI syntax.
const INVALID_FAST_TEMPLATE: &str = "invalid-fast-template";
/// Placeholder `<f-template name>` supplied to the converter when the authored
/// source omits a usable name; the resolved registry key is the filename.
const CONVERTER_FALLBACK_NAME: &str = "webui-fallback";
/// Target dialect requested from `microsoft-fast-convert`.
const WEBUI_CONVERTER_SYNTAX: &str = "webui-prerelease";
crates/webui-parser/src/component_registry.rs:164
- The doc comment claims
Unchangedpreserves the tag name + HTML "without extra allocation", but theUnchangedbranch still allocates the tag name viatag_name.to_string()(line 184). Consider rephrasing to avoid implying a fully allocation-free path.
/// When no transform is installed, or it returns
/// [`ComponentSourceResult::Unchanged`], the filename-derived tag and the
/// authored HTML are preserved without extra allocation.
Mohamed Mansour (mohamedmansour)
left a comment
There was a problem hiding this comment.
To be webui compliant, we want component files to be authored a specific way, neutral way, so that we can control the dom strategy, css strategy. We are doing a lot of things with that component discovery so making it consistent is what we need now.
If we want component discovery to have discover different formats like f-templates, or .webui files (like vue/astro), then that is a different feature request, that we need to plan.
But I don't see any value for us to complicate the plugin process for parsing a different schema for component discovery. For WebUI the schema must follow the Web Standard Syntax, intentionally. If you really want this, maybe have a pre-install script that transforms f-templates into webui schema.
OK, let me update to use a pre-install script. My reasoning is that we can’t expect developers authoring FAST web components to also publish WebUI templates, as this would complicate testing. Similarly, if other web component libraries introduce their own declarative template syntaxes, aligning them with this integration pattern would be difficult. Instead, developers should be able to consistently use a single declarative template syntax or import templates from a published component library, avoiding this added complexity. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (2)
crates/webui-parser/src/plugin/fast_convert/directive.rs:24
- Directive conversion silently drops all attributes on
<f-when>/<f-repeat>exceptvalue:convert_directiveonly emits the converted open tag plus the unwrapped expression (fast_convert.rs:292-295), andvalidate_directive_attributesdoes not reject other attributes. This means authored attributes likeid,class, ordata-*on these directives would be accepted but lost in the SSR parser view.
pub(super) fn validate_directive_attributes<'a>(
tag: &Tag<'a>,
tag_offset: usize,
) -> Result<(), ConvertError<'a>> {
for attr in tag.attrs() {
if attr.name.starts_with("f-") {
return Err(ConvertError::new(
ConvertErrorKind::UnsupportedFAttribute {
attribute: attr.name,
},
tag_offset + attr.raw_range.start,
));
}
}
Ok(())
crates/webui-parser/src/plugin/fast_shared.rs:57
artifact_contentis currently set to the entire<f-template>body (converted.artifact), which can begin with non-<template>content (text/comments) even though conversion succeeds. Downstream artifact processing treats any artifact source that does not start with<templateas “dev omitted<template>” and wraps it (webui-parser/src/lib.rs:2673-2709), and FAST artifact generation also wraps non-<template>content in a<template>(fast_v2.rs:159-165). This can produce nested<template>elements inside the emitted<f-template>, breaking FAST authoring that includes any leading content before the inner<template>.
let resolved_tag = converted.name.unwrap_or(source.tag_name).to_string();
let artifact_content = html_content[converted.artifact].trim().to_string();
Ok(ComponentSourceResult::Transformed(
TransformedComponentSource {
tag_name: resolved_tag,
parser_content: converted.parser_content,
artifact_content: Some(artifact_content),
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edae75de-6059-41a3-808d-0ec4b4c85b08
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edae75de-6059-41a3-808d-0ec4b4c85b08
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edae75de-6059-41a3-808d-0ec4b4c85b08
Replace the external converter with an iterative in-tree implementation and preserve actionable FAST diagnostics. Update dependency metadata, tests, benchmarks, and user-facing documentation for the supported syntax. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed511a03-0959-4b9b-b067-0f77acedaa71
a61ff3f to
36ed174
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (7)
crates/webui-parser/src/plugin/fast_convert.rs:163
- The conversion walk also scans inside raw-text bodies. For example, a valid
<script>string containing<f-when ...></f-when>is rewritten to WebUI tags in the SSR source, while an unmatched example becomes an authoring error. Preserve raw-text/text element contents verbatim by advancing directly to their matching closing tags.
let remaining = &source[start..range.end];
if remaining.starts_with("<!--") {
crates/webui-parser/src/plugin/fast_convert.rs:294
- The expression is inserted into a double-quoted HTML attribute without escaping or choosing a safe delimiter. Valid FAST such as
<f-when value='{{name == "John"}}'>becomes<if condition="name == "John"">, so the SSR parser sees a truncated/malformed condition. Serialize the generated attribute quote-safely and add a regression test for quoted string literals.
state.output.push_str(kind.output_open());
state.output.push_str(expression);
state.output.push_str("\">");
crates/webui-parser/src/plugin/fast_convert.rs:246
- Closing FAST tags are copied unchanged whenever they do not match the current directive stack. Thus orphan
</f-when>tags and unsupported closers such as</f-choose>bypassinvalid-fast-template, even though the documented contract rejects malformed and unsupportedf-*elements. Reject unmatched known closers and every unsupportedf-*closer with a diagnostic at the closing-tag offset.
let Some(kind) = DirectiveKind::from_tag_name(tag.name) else {
state.output.push_str(raw);
return Ok(());
crates/webui-parser/src/component_registry.rs:264
resolve_component_sourcecan now returnParserError::Template, butwebui::build_protocol_innerstringifies errors from both registry call sites intoWebUIError::ComponentRegistration(crates/webui/src/lib.rs:541-547and560-571). The CLI only extracts diagnostics from typedWebUIErrorvariants, so--format jsonloses the new FAST code, location, snippet, and help. Preserve the parser error as a source/structured variant and update the CLI extractor.
let resolved = self.resolve_component_source(tag_name, html_content)?;
crates/webui-parser/src/plugin/fast_convert/scan.rs:24
- This scan skips comments but not HTML raw-text element bodies. An ordinary component containing a JavaScript/CSS string such as
"<f-template>...</f-template>"is therefore misidentified as a FAST-authored component and can fail registration. Skipscript,style, and the other raw-text/text elements while scanning, as the existing component scanner does incomponent_policy.rs:278-303.
while cursor < range.end {
let Some(relative) = source[cursor..range.end].find('<') else {
crates/webui-parser/src/plugin/fast_shared.rs:45
- FAST diagnostics attach line/column data but never set the owning component, even though
ComponentSource::tag_nameis available. Their location renders as--> 3:5and the JSONfilefield is null, which is ambiguous during multi-component discovery. Pass the source tag into the diagnostic builder and attach it with.component(...).
let Some(converted) =
convert_template(html_content).map_err(|error| converter_error(html_content, &error))?
docs/guide/concepts/plugins/index.md:109
- This adds a new component authoring form and FAST plugin selection behavior, but the authoring-focused
docs/ai/SKILL.mdremains webui-only and has no FAST or<f-template>guidance. Keep that single-page AI reference in sync with the new syntax and link to this detailed plugin section.
When a FAST plugin is selected, it installs a `component_source_transform` that
recognizes an HTML file authored as one wrapping `<f-template>`. With no
plugin, or with the `webui` plugin, `<f-template>` markup is not scanned or
converted and passes through like any other HTML:
| f_template_start, | ||
| ) | ||
| })?; | ||
| let artifact = f_template_end..f_template_close; |
Motivation
FAST component sources can be authored as
<f-template>files, but discovery and conversion belong to the FAST integration rather than WebUI's framework-neutral parser core.Summary
ParserPlugin::component_source_transformhook with minimal component-registry plumbing; plugins that do not install a transform keep filename-derived names and authored HTML unchangedfast,fast_v2, andfast_v3share plugin-owned<f-template>discovery, structured diagnostics, andmicrosoft-fast-convertconversion of supported FAST directives for SSR<f-template name>as the component name, fall back to the filename when it is absent or blank, and reject multiple templates or unsupported/malformed FAST syntaxf-templatebyte precheck so ordinary FAST sources skip the HTML walk<f-template>sources at depths 8 and 64webuipaths do not inspect or interpret<f-template>syntaxValidation
cargo xtask check