Engine: Compile-time optimizations for Action View tag helpers#1613
Engine: Compile-time optimizations for Action View tag helpers#1613
Conversation
commit: |
🌿 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. |
This pull request adds `framework` and `template_engine` configuration options to the `.herb.yml`. These top-level settings tell Herb what environment it's running in and what template engine is being used for compilation. This enables context-aware behavior across the toolchain. Configuration in `.herb.yml`: ```yml framework: actionview # ruby | actionview | hanami | sinatra (default: ruby) template_engine: herb # erubi | erb | herb (default: erubi) ``` Valid values are defined once in `config/options.yml` and code-generated into the relevant configuration files in all bindings. This allows us in the future to have more specific features tailored for each target framework and engine. This pull request is meant to lay the groundwork for: - Framework-aware linter rules, so that we can automatically enable/disable rules based on the framework (see #480) - Framework-aware compile-time optimizations. Knowing the framework context determines which helpers are available for optimization (like #1613) - Template engine compatibility, surfacing invalid syntax or other target engine's conventions across the toolchain (for example no ERB block support)
|
@marcoroth did you test this against cases where the consumer re-defines an ActionView helper, say to add additional security features to |
|
Hey @joelhawksley, this feature is very experimental and opt-in. This currently also only applies for the helpers that the registry marks as "supported": ❯ bin/console
irb(main):001> require "herb/action_view/helper_registry"
=> true
irb(main):002> Herb::ActionView::HelperRegistry.supported.map(&:name)
=> ["image_tag", "javascript_include_tag", "javascript_tag", "content_tag", "tag", "link_to", "turbo_frame_tag"]
irb(main):003>But yes, there is currently a gap when you overwrite So if you had this: <%= tag.div do %>
Content
<% end %>it would currently compile this template with the compile-time optimizations to this:
__herb = ::Herb::Engine; _buf = ::String.new; _buf << '<div>
Content
</div>'.freeze;
_buf.to_sand I was thinking of adding a pre- or postamble in development/test to either raise, or have it collect the warnings and show them like we just did in #1660: __herb = ::Herb::Engine; _buf = ::String.new; _buf << '<div>
Content
</div>'.freeze;
_buf.to_s
+
+ raise "Helper was overwritten" if method(:tag).owner != Herb::ActionView::HelperRegistry.get(:tag).source |
|
Ah yep, that makes sense. I just poked around a little bit and we have overrides for about a dozen or so of the helpers, so it's definitely something we'd want to leave turned off by default until we can be 100% confident in avoiding a mismatch. In general, I'd argue that a runtime check is insufficient, as it assumes that every line of ERB is executed in CI. |
|
@joelhawksley I just opened #1664 so we don't loose track of this, thanks for the input!
Yeah, I agree. Though, it wouldn't need to execute every line of ERB since it's either added to the start/end of the file, so rendering the template once in any way would be sufficient to fire the check at least once. But there might also be a potential other way to do this in a more static manner by instantiating an empty |
This pull request adds compile-time optimization support for Action View tag helpers to
Herb::Engine.When
optimize: trueis passed toHerb::Engine, the parser transforms Action View helper calls (tag.*,content_tag,link_to,image_tag,javascript_tag,javascript_include_tag,turbo_frame_tag) into their HTML equivalents at compile time, producing optimized Ruby output that avoids runtime helper dispatch.When compile-time optimizations are enabled, the engine checks whether the template contains supported Action View helpers via the
Herb::ActionView::HelperRegistry.supportedmethod. If helpers are detected, the parser is invoked withaction_view_helpers: trueandtransform_conditionals: true, which also handles postfix if/unless and ternary conditionals containing helper calls.The pull request also includes comprehensive snapshot-based tests for all supported helpers, an
assert_optimized_output_matchhelper that validates optimized output against both standard Herb and ActionView+Erubi to ensure the rendered output matches.Additionally, we added a YAML-driven benchmark framework in
bench/action_view/with abin/benchCLI for compile-time and render-time comparisons. Early benchmarks show render-time improvements of 3-22x depending on the template, with a realistic 192-line page layout rendering 22x faster, and a helper-heavy template with all static values reaching up to 150x faster.The trade-off is compile time: Herb's parser is currently 10-90x slower than Erubi at compile time, so optimization adds overhead that needs to be amortized over multiple renders. But this is typically won back with 15-100 renders depending on template complexity and could also be done ahead of time, instead of the current on-the-fly approach used in Action View today.
Warning
Compile-time optimizations are experimental. Output may differ from standard ActionView rendering.
Resolves #653
Enables #1111