Skip to content

Commit 68c4bd9

Browse files
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

File tree

0 commit comments

Comments
 (0)