Skip to content

Replace vapor_split with split_template, built on Vize's L2 IR - #8

Merged
dannote merged 1 commit into
mainfrom
split-template
Oct 3, 2026
Merged

dannote merged 1 commit into
mainfrom
split-template

Conversation

@dannote

@dannote dannote commented Oct 3, 2026

Copy link
Copy Markdown
Member

For vize 0.17.0. Breaking: Vize.vapor_split/1 is replaced by Vize.split_template/2.

Why

vapor_split built the split from Vapor's generated template HTML. A hand-rolled tag scanner in html_inject.rs located each element in that HTML and matched it to the IR through child/next offsets. Each bug fixed in it so far was the scanner and the IR disagreeing:

  • slots dropped after text nodes (0.16.1);
  • components placed before earlier siblings;
  • a root v-if rendered twice;
  • implicit default slot content lost beside a named <template #x>.

Vize already has the right input. vize_l2, its semantic IR, is the representation Vize's SSR compiler builds its string plan from. It has elements, components, text, interpolations, v-if, v-for and slot outlets, with static attributes and typed bindings in authored order, and a source span on every op.

What changed

  • split_template/2 lowers the template with vize_l1 and vize_l1_to_l2::lower, then walks the L2 ops, printing static HTML and emitting a slot for everything that depends on data.
  • html_inject.rs, vapor_split.rs and its marker splicing are gone.
  • Escaping: entities are decoded with Vize's own decode_ssr_static_text and decode_html_attribute_entities, and escaped with html-escape.
  • New slot shapes (see the guide's "Splitting templates" section):
    • Expressions are source strings, and every slot has a :position.
    • An :attr slot is a whole attribute, so a renderer can leave it out for null and false boolean attributes, as Vue does. A static attribute of the same name comes with it.
    • Components carry their props, events, and the content of each slot. <slot> outlets are slots.
    • v-show, v-model on <input>/<textarea>, v-html, v-text, v-bind objects and dynamic attribute names are slots.
    • Events and v-models are :on/:model bindings with where their element's start tag ends.
  • root_attrs: true: gathers a component template's root element attributes into one slot, for fallthrough attribute merging.
  • Errors:
    • Errors are Vize.Error with a positioned Vize.Diagnostic each, such as 1:7: can't parse the expression \a +`, and the error message lists them. Warnings, such as custom directives, are in the split's :diagnostics`.
    • Vize.Diagnostic gains :severity.
    • Vize.Diagnostic.to_code_diagnostic/2 converts a diagnostic to Elixir's Code.diagnostic shape for a file, offset to where the template starts in it.
  • RustQ atom generation: atoms from raw identifiers such as atoms::r#if() are deduplicated against {:if, "if"} declarations.

Verification

  • VIZE_EX_BUILD=1 mix ci passes: credo, ex_dna, reach, dialyzer, clippy, and 130 tests.
  • test/vize/split_template_test.exs covers each slot kind, entity round-tripping, attribute merging, slot placement among siblings, component slots including v-slot on the component, root_attrs, and diagnostics with positions.
  • PhoenixVapor's playground branch renders its test suite (307 tests, e2e included) and demo (14 e2e tests) on this split.

split_template/2 lowers a template to Vize's L2 semantic IR, the op family
Vize's own SSR compiler builds on, and prints it: static HTML, and a slot for
everything that depends on data. It replaces vapor_split, which located
elements in Vapor's template HTML with a hand-rolled tag scanner, and so
dropped slots after text nodes, placed components before their siblings,
rendered a root v-if twice, and lost default slot content beside a named
slot.

Slots carry source positions. An attribute is a whole slot, so null and
false boolean attributes can be left out. Components carry their props,
events and slot content, and <slot> outlets are slots. The root_attrs option
gathers a component template's root attributes for fallthrough merging.

Errors are Vize.Error structs with a positioned Vize.Diagnostic each, and
Vize.Diagnostic.to_code_diagnostic/2 converts one to Elixir's diagnostic
shape.
@dannote
dannote merged commit 1627881 into main Oct 3, 2026
2 checks passed
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.

1 participant