Commit 68c4bd9
fix(parser): preserve developer-authored <template> wrapper verbatim (#307)
`process_component_template` used to extract only the inner content of a
developer-authored `<template>` wrapper and then re-wrap it with
framework-controlled attributes (or drop the wrapper entirely in
--dom=light). The dev's attributes — `foo="bar"`, `shadowrootmode="closed"`,
signal fragments like `foo="{{x}}"`, and any custom data-*/aria-* — were
silently discarded. The framework was overriding developer intent.
The new contract treats the dev's wrapper as the source of truth:
* Dev supplied `<template …>`: preserved verbatim in BOTH --dom=light and
--dom=shadow. The only modification is stripping runtime-only attrs
(`@event`, `:bind`, `?cond`), since those are protocol metadata and never
appear in HTML output. If the active CSS strategy emits a snippet, it is
spliced immediately inside the dev's opening tag so styles still apply.
`adopted_specifier` is intentionally ignored — when the dev manages the
wrapper, they manage CSS adoption.
* Dev omitted `<template>`:
- --dom=shadow wraps with `<template shadowrootmode="open">` (plus the
optional `shadowrootadoptedstylesheets="<tag>"` for CSS modules).
- --dom=light emits the content as-is (with the CSS snippet prepended,
if any).
Performance (mission-critical per the framework's perf rules):
* Zero recursion, zero regex.
* New fast-path byte scan `tag_scan::opening_tag_has_runtime_prefix` lets
`strip_runtime_attrs_from_template` skip the tree-sitter parse entirely
when no `@`/`:`/`?` attributes exist on the opening tag — the common case
for most components. The scan is quote-aware and bounded to the opening
tag (stops at the first unquoted `>`).
* Output buffers are pre-sized with `String::with_capacity` using exact
length math; the intermediate `inner` String allocation was removed in
favour of slicing.
* No new clones, no `Cow` indirection at the public boundary.
Consolidation:
The codebase had four near-duplicate quote-aware tag scanners
(`find_tag_end` in plugin/webui.rs, `find_tag_close` in plugin/fast_v2.rs,
`find_tag_close` in plugin/fast_v3.rs, and the two helpers introduced for
this fix). They have been unified into `crates/webui-parser/src/tag_scan.rs`
with one canonical `find_tag_close` plus the new
`opening_tag_has_runtime_prefix` fast-path helper.
Latent bug fixed by the consolidation: the old `find_tag_close` in
plugin/fast_v2.rs and plugin/fast_v3.rs only tracked double quotes. An
input like `<a x='a>b'>` would return the `>` at position 7 instead of 10.
The shared `find_tag_close` tracks both `"` and `'`, so FAST v2/v3 now
silently inherit the fix without any call-site changes.
Tests:
* 8 new inline unit tests in webui-parser/src/lib.rs cover all four
cells of the {dev-template, dom-strategy} matrix plus signal-fragment
attrs, multiple attrs, and dev-supplied `shadowrootmode="closed"`.
* 10 new unit tests in webui-parser/src/tag_scan.rs cover both helpers
(single/double quoted `>`, unterminated tags, prefix-in-tag-name
ignored, quoted prefix chars ignored, stop-at-unquoted-`>` semantics).
* Three existing tests that asserted the old destructive behaviour
(`test_component_no_double_wrap_template`,
`test_component_styled_no_double_wrap`,
`test_component_strip_runtime_attrs`) have been updated to assert the
new correct contract — they previously enforced the bug.
* Duplicate `find_tag_close_skips_quoted_gt` tests in fast_v2 and fast_v3
removed; the canonical tests now live in tag_scan.rs.
`cargo xtask check` passes (license-headers, fmt, clippy, proto-drift,
deny, test, build, wasm, examples, bench, docs).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 386ebaa commit 68c4bd9
5 files changed
Lines changed: 438 additions & 141 deletions
0 commit comments