Skip to content

fix: whitespace in closing template tag breaks SSR output in fast-build and fast-test-harness #7573

@radium-v

Description

@radium-v

🐛 Bug Report

A <template> or <f-template> closing tag that contains whitespace before the > (for example </template\n\t>) is not recognized by the SSR template parser. The content gets dropped or the malformed tag is left in the output. This affects the declarative HTML pipeline used by @microsoft/fast-build (the microsoft-fast-build crate) and @microsoft/fast-test-harness.

💻 Repro or Code Sample

A template whose closing tag wraps onto a new line:

<f-template name="my-element" shadowrootmode="open">
  <template>
    <span>hello</span>
  </template
    >
</f-template>

Run it through convertTemplate in generate-templates.ts, or Locator::from_patternsparse_f_templates in the crate, and inspect the output.

Minimal Rust reproduction (added as a unit test in crates/microsoft-fast-build/src/locator.rs):

#[test]
fn test_parse_f_templates_closing_tag_with_whitespace() {
    let html = "<f-template name=\"my-button\"><template><button>{{label}}</button></template\n\t></f-template>";
    let results = parse_f_templates(html);
    assert_eq!(results.len(), 1);
    assert_eq!(results[0].name, Some("my-button".to_string()));
    assert_eq!(results[0].content, "<button>{{label}}</button>");
}

This test fails. extract_template_content never finds the literal </template>, falls back to the trimmed inner string, and the wrapper plus the malformed closing tag leak into content:

left:  "<template><button>{{label}}</button></template\n\t>"
right: "<button>{{label}}</button>"

🤔 Expected Behavior

</template\n\t>, </template >, and </f-template\n> should parse the same as </template> and </f-template>.

The WHATWG HTML Living Standard, section 13.1.2.2 (End tags), defines an end tag as: <, then /, then the tag name, then optionally one or more ASCII whitespace, then >. Whitespace between the tag name and > is conforming HTML, so </template\n\t> is valid and must be handled. (Whitespace is not permitted between <, /, and the tag name, and end tags carry no attributes.)

😯 Current Behavior

Both parsers search for the exact closing-tag strings.

In crates/microsoft-fast-build/src/locator.rs, parse_f_templates calls html[inner_start..].find("</f-template>") and extract_template_content calls html[tag_end..].find("</template>"). When the real tag contains whitespace, these find calls return None and fall back to the untrimmed inner string. The malformed tag stays in the output, and the template is dropped entirely when </f-template> cannot be located.

In packages/fast-test-harness/src/build/generate-templates.ts, the wrapper strip html.replace(/<\/?template[^>]*>/g, "") does not match closing tags whose whitespace falls outside the [^>]* boundary in every code path.

Templates with whitespace in the closing tag produce invalid generated HTML.

💁 Possible Solution

Match closing tags with a whitespace-tolerant scan rather than a literal substring search: the tag name followed by zero or more ASCII whitespace, then >. In the crate, replace the find("</template>") and find("</f-template>") calls in locator.rs with a helper that, after matching </template / </f-template, skips ASCII whitespace and expects >. In the harness, narrow the regex to /<\/(f-)?template\s*>/g so it tolerates trailing whitespace without accepting non-conforming forms. I'm willing to contribute the fix.

🔦 Context

I author component templates as standalone HTML files and render them through the FAST SSR and test-harness pipeline. Formatters routinely wrap long closing tags onto a new line, which produces </template\n\t>. The parser rejects that whitespace, so spec-conforming HTML silently renders broken output, and the cause is hard to track down. I found no open or closed issue covering this case.

🌍 Your Environment

  • OS & Device: macOS
  • Browser: N/A (build/SSR-time issue)
  • Version: @microsoft/fast-build / @microsoft/fast-test-harness (v3 prerelease)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions