Skip to content

feat: discover and convert FAST f-templates through parser plugins - #378

Open
Jane Chu (janechu) wants to merge 6 commits into
mainfrom
users/janechu/update-plugin-detect-f-templates
Open

feat: discover and convert FAST f-templates through parser plugins#378
Jane Chu (janechu) wants to merge 6 commits into
mainfrom
users/janechu/update-plugin-detect-f-templates

Conversation

@janechu

@janechu Jane Chu (janechu) commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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

  • add a generic, optional ParserPlugin::component_source_transform hook with minimal component-registry plumbing; plugins that do not install a transform keep filename-derived names and authored HTML unchanged
  • have fast, fast_v2, and fast_v3 share plugin-owned <f-template> discovery, structured diagnostics, and microsoft-fast-convert conversion of supported FAST directives for SSR
  • use a non-empty <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 syntax
  • classify FAST client-only event/property/ref/slotted/children bindings in the FAST plugins so they are omitted from SSR while still counted for hydration
  • retain the authored inner template for the client artifact, including client bindings, while applying normal wrapper normalization, legal-comment handling, and the selected CSS strategy
  • use an allocation-free, ASCII-case-insensitive f-template byte precheck so ordinary FAST sources skip the HTML walk
  • add a committed Criterion benchmark that directly times FAST-plugin registration/source transformation for ordinary and authored <f-template> sources at depths 8 and 64
  • document and test the shared behavior, including that no-plugin and webui paths do not inspect or interpret <f-template> syntax

Validation

  • cargo xtask check

Copilot AI left a comment

Copy link
Copy Markdown

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 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_transform hook 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 via microsoft-fast-convert, and updates FAST v2/v3 plugins to use it (plus treating :property bindings as skipped-and-counted).
  • Updates DESIGN/docs/README to document authored FAST behavior, and adds regression tests across fast, fast_v2, and fast_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.

@janechu Jane Chu (janechu) changed the title Support authored FAST f-template components feat: discover and convert FAST f-templates through parser plugins Jul 27, 2026
Copilot AI review requested due to automatic review settings July 28, 2026 18:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.rs documents diagnostic::codes as 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 Unchanged preserves the tag name + HTML "without extra allocation", but the Unchanged branch still allocates the tag name via tag_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.

@janechu
Jane Chu (janechu) marked this pull request as ready for review July 30, 2026 18:05

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread Cargo.toml Outdated
@janechu

Copy link
Copy Markdown
Contributor Author

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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> except value: convert_directive only emits the converted open tag plus the unwrapped expression (fast_convert.rs:292-295), and validate_directive_attributes does not reject other attributes. This means authored attributes like id, class, or data-* 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_content is 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 <template as “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),

Jane Chu (janechu) and others added 6 commits August 11, 2026 09:54
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
@janechu
Jane Chu (janechu) force-pushed the users/janechu/update-plugin-detect-f-templates branch from a61ff3f to 36ed174 Compare August 11, 2026 17:02
@janechu
Jane Chu (janechu) requested a balanced review from Copilot August 12, 2026 19:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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> bypass invalid-fast-template, even though the documented contract rejects malformed and unsupported f-* elements. Reject unmatched known closers and every unsupported f-* 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_source can now return ParserError::Template, but webui::build_protocol_inner stringifies errors from both registry call sites into WebUIError::ComponentRegistration (crates/webui/src/lib.rs:541-547 and 560-571). The CLI only extracts diagnostics from typed WebUIError variants, so --format json loses 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. Skip script, style, and the other raw-text/text elements while scanning, as the existing component scanner does in component_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_name is available. Their location renders as --> 3:5 and the JSON file field 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.md remains 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;
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.

3 participants