Repository navigation
Engine: Implement ComponentVisitor - #2032
Merged
Merged
Conversation
# Conflicts: # lib/herb/location.rb
Adapts the proof-of-concept to the changes that landed on main since it was written: - `ERBContentNode` gained a `prism_node` field, so the synthesized node needs the additional argument - the engine no longer emits a newline after the preamble, so the expected compiled sources and the snapshots were regenerated - nilable node fields and `Steep` need the attribute lookups to be guarded instead of chained `ComponentVisitor` also gets a stable `#inspect`, because the snapshot key includes the engine options and the default `#inspect` embeds the object address, which made the snapshot file name change on every run.
Warns once per process on the first instantiation, matching how the experimental `optimize` engine option announces itself, and documents the visitor with a warning callout.
ComponentVisitor
🌿 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 ✅ Preview deployment has been cleaned up. |
commit: |
This branch was successfully deployed
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 implements an experimental
Herb::Engine::ComponentVisitor, an opt-in transform visitor that rewrites capitalized HTML tags intorendercalls before compilation, so that a component or a partial can be written as a tag instead of an ERB expression.Warning
ComponentVisitoris experimental and a proof of concept. The generatedrendercalls, the attribute mapping, and the class itself may change or be removed without a major version bump. It warns the first time it is instantiated in a process, and how a tag is resolved is expected to move to an explicit registry later.Like the other transform visitors, it isn't loaded when you
require "herb".Resolution
How a tag resolves is decided entirely from the tag name. There is no constant lookup while compiling and no lookup at render time, so the same template always compiles to the same output:
<Card />render Card.new<Users::Card />::, a constantrender Users::Card.new<Users.Card />., a pathrender "users/card"<Admin.Users.ProfileCard />., a pathrender "admin/users/profile_card"A tag is only transformed when every segment of its name is CamelCase, so
<DIV>,<BR>,<A>and<My-Component />are left as HTML. Uppercase tags are valid HTML, and web components like<my-stimulus-element />have to keep working.Dot notation requires the
dot_notation_tagsparser option for the tag name to parse in the first place.Attributes
Attribute names are converted from kebab-case to snake_case and become keyword arguments for a component, or locals for a partial:
name="hello"name: "hello"#{}are escaped withString#dump:count="@count"count: @count:prefix is used as Ruby codename="<%= @user.name %>"name: "#{@user.name}"disableddisabled: trueitem-id="7"item_id: "7"An attribute whose name isn't a valid keyword argument, such as
@click, is skipped, and the first of a repeated attribute wins, the same way HTML resolves duplicates.Body
A tag with a body becomes a block, and the body is kept as AST nodes rather than being reconstructed as a string, so the compiler emits it the way it emits any other template content:
A partial with a body is rendered as a layout, so the body reaches the partial through
yield, sincerender "path" do ... endwould drop the block:Resolvers
The visitor itself only walks the AST and looks up a resolver. What a tag renders to lives in
Resolversubclasses, and the list is injectable:A resolver claims a tag name through
handles?and returns Ruby source fromrender_code, so it isn't limited torender:A tag that no resolver claims is left as HTML.