Engine: Introduce SlotVisitor for slot markers - #1986
Draft
marcoroth wants to merge 4 commits into
Draft
Conversation
SlotVisitor for slot markers
馃尶 Interactive Playground and Documentation PreviewA preview deployment has been built for this pull request. Try out the changes live in the interactive playground: 馃尡 Grown from commit |
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces
Herb::Engine::SlotVisitor, which assigns a stable index to every dynamic insertion point in a template and delimits it in the compiled output, so that a consumer can address a specific part of the rendered result without scanning the DOM for matching content.Motivation
Herb can already tell what changed between two syntax trees (#1518) and which templates and nodes a piece of state reaches (#1667). What has been missing is a way to find the corresponding position in the rendered output. Diff operations carry a path into the template tree, but an ERB node occupies one index there while producing any number of nodes in the browser, so those paths do not survive rendering. The dev server works around this today by matching on content, which is ambiguous whenever the same text or attribute value appears twice.
A slot marker gives that position a name. Because indices are assigned in document order at compile time and recorded against the same
node_paththatHerb::ActionView::TemplateDependenciesreports, a state lookup and a marker in the output refer to the same thing by construction.Slots
A slot is one dynamic insertion point, typed after the Part taxonomy from the DOM Templating API proposal:
child<%= %>output and<%= yield %>in child positionconditionalif/unless/case, counting anelsif/elsechain as one slotcollectionERBIterationBlockNode, so a repeating region is told apart from a block that merely wraps its bodyattributeblockform_with door@user.tap doEnabling
slotsturns on theiteration_nodesparser option, which is what makes thecollectiondistinction possible. Without it@users.each do |user|andform_with model: @user do |form|are both an opaqueERBBlockNode, and keyed reconciliation needs the stronger fact.Markers
Slots are delimited with HTML comments:
Comments rather than wrapper elements, because a comment is legal in
<head>and inside SVG and is invisible to CSS sibling combinators and Tailwindpeer-*variants.DebugVisitorwraps ERB output in<span style="display: contents">for the same purpose, which is the cause of a long tail of layout problems (marcoroth/reactionview#49, marcoroth/reactionview#98, marcoroth/reactionview#103, #1810, #1111, #1052). No marker strategy here introduces an element, and the test suite asserts that.An HTML comment cannot sit inside a tag, so attribute slots are anchored on the enclosing element instead:
A conditional that renders nothing still leaves its position behind:
The client learns that a position exists without learning what would fill it. Nested slots become addressable once their parent branch renders, so
<!--herb-slot:1-->above appears only when@adminis true.The marker syntax lives behind a
SlotMarkersobject so it can be swapped for the native range markers from Chrome's declarative partial updates (<?start name="...">/<?end>) once those are unflagged. Those parse into comment nodes through the HTML parser's bogus-comment state, so the migration is a change of spelling rather than of node type.Schema
Each template gets a schema: the ordered list of slot indices and types, plus a version hash over that layout. The version covers structure, not content, so editing an expression keeps the same slot index while adding or removing an ERB tag changes the hash. That is what lets a consumer detect a template whose layout it no longer matches.
Usage
The visitor is off by default and enabled with the
slotsengine option, mirroringdebug:It runs ahead of the debug visitor so that slots are assigned against the template as written rather than against the wrapper elements debug mode injects.
Related #1518, #1667, #1912.