diff --git a/.rubocop.yml b/.rubocop.yml index 48c105aca..4208dd570 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -62,6 +62,7 @@ Metrics/CyclomaticComplexity: - lib/herb/prism_inspect.rb - lib/herb/engine/**/*.rb - templates/template.rb + - bench/**/* Metrics/MethodLength: Max: 20 @@ -76,6 +77,7 @@ Metrics/MethodLength: - test/fork_helper.rb - test/snapshot_utils.rb - bin/**/* + - bench/**/* Metrics/AbcSize: Exclude: @@ -93,6 +95,8 @@ Metrics/AbcSize: - test/snapshot_utils.rb - test/engine/rails_compatibility_test.rb - bin/**/* + - bench/**/* + - test/engine/action_view/action_view_test_helper.rb Metrics/ClassLength: Exclude: @@ -112,6 +116,7 @@ Metrics/ModuleLength: - test/**/*.rb - templates/**/*.rb - bin/lib/compare_helpers.rb + - bench/**/* Metrics/BlockLength: Max: 30 @@ -144,6 +149,7 @@ Metrics/PerceivedComplexity: - templates/template.rb - test/**/*.rb - bin/**/* + - bench/**/* Layout/LineLength: Enabled: false @@ -176,6 +182,7 @@ Security/Eval: - bin/erubi-render - bin/compare-render - bin/compare-render + - bench/action_view/benchmark_helper.rb Security/MarshalLoad: Exclude: @@ -191,3 +198,6 @@ Lint/UnusedMethodArgument: Style/IfUnlessModifier: Enabled: false + +Style/DocumentDynamicEvalDefinition: + Enabled: false diff --git a/Gemfile b/Gemfile index cccc77ecc..4eac9dd42 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ gemspec gem "prism", github: "ruby/prism", tag: "v1.9.0" gem "actionview", "~> 8.0" +gem "benchmark" +gem "charm" gem "digest", "~> 3.2" gem "erubi" gem "irb", "~> 1.16" @@ -17,9 +19,11 @@ gem "rake", "~> 13.2" gem "rake-compiler", "~> 1.3" gem "rake-compiler-dock", "~> 1.11" gem "rbs-inline", "~> 0.12" +gem "reactionview", "~> 0.3.0" gem "reline", "~> 0.6" gem "rubocop", "~> 1.71" gem "sorbet" +gem "turbo-rails", "~> 2.0" # TODO: Remove once https://github.com/ruby/rbs/pull/2850 is merged and released gem "rbs", github: "marcoroth/rbs", branch: "psych-load-unsafe-file" diff --git a/Gemfile.lock b/Gemfile.lock index 8eca680a0..8a11ff529 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -46,6 +46,16 @@ PATH GEM remote: https://rubygems.org/ specs: + actionpack (8.1.2) + actionview (= 8.1.2) + activesupport (= 8.1.2) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) actionview (8.1.2) activesupport (= 8.1.2) builder (~> 3.1) @@ -67,8 +77,40 @@ GEM uri (>= 0.13.1) ast (2.4.3) base64 (0.3.0) + benchmark (0.5.0) bigdecimal (4.0.1) + bubbles (0.1.1) + bubbletea + harmonica + lipgloss + bubbletea (0.1.4-aarch64-linux-gnu) + lipgloss (~> 0.1) + bubbletea (0.1.4-aarch64-linux-musl) + lipgloss (~> 0.1) + bubbletea (0.1.4-arm64-darwin) + lipgloss (~> 0.1) + bubbletea (0.1.4-x86_64-darwin) + lipgloss (~> 0.1) + bubbletea (0.1.4-x86_64-linux-gnu) + lipgloss (~> 0.1) + bubbletea (0.1.4-x86_64-linux-musl) + lipgloss (~> 0.1) + bubblezone (0.1.2-aarch64-linux-gnu) + bubblezone (0.1.2-aarch64-linux-musl) + bubblezone (0.1.2-arm64-darwin) + bubblezone (0.1.2-x86_64-darwin) + bubblezone (0.1.2-x86_64-linux-gnu) + bubblezone (0.1.2-x86_64-linux-musl) builder (3.3.0) + charm (0.1.0) + bubbles + bubbletea + bubblezone + glamour + gum + harmonica + lipgloss + ntcharts concurrent-ruby (1.3.6) connection_pool (3.0.2) crass (1.0.6) @@ -95,6 +137,17 @@ GEM ffi (1.17.3-x86_64-linux-gnu) ffi (1.17.3-x86_64-linux-musl) fileutils (1.8.0) + glamour (0.2.2-aarch64-linux-gnu) + glamour (0.2.2-aarch64-linux-musl) + glamour (0.2.2-arm64-darwin) + glamour (0.2.2-x86_64-darwin) + glamour (0.2.2-x86_64-linux-gnu) + glamour (0.2.2-x86_64-linux-musl) + gum (0.3.2-aarch64-linux) + gum (0.3.2-arm64-darwin) + gum (0.3.2-x86_64-darwin) + gum (0.3.2-x86_64-linux) + harmonica (0.1.1) i18n (1.14.8) concurrent-ruby (~> 1.0) io-console (0.8.2) @@ -102,15 +155,21 @@ GEM pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.18.0) + json (2.19.2) language_server-protocol (3.17.0.5) lint_roller (1.1.0) + lipgloss (0.2.2-aarch64-linux-gnu) + lipgloss (0.2.2-aarch64-linux-musl) + lipgloss (0.2.2-arm64-darwin) + lipgloss (0.2.2-x86_64-darwin) + lipgloss (0.2.2-x86_64-linux-gnu) + lipgloss (0.2.2-x86_64-linux-musl) listen (3.10.0) logger rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) - loofah (2.25.0) + loofah (2.25.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) lz_string (0.3.0) @@ -120,18 +179,24 @@ GEM minitest-difftastic (0.2.1) difftastic (~> 0.6) mutex_m (0.3.0) - nokogiri (1.19.0-aarch64-linux-gnu) + nokogiri (1.19.2-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.0-aarch64-linux-musl) + nokogiri (1.19.2-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.19.0-arm64-darwin) + nokogiri (1.19.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.19.0-x86_64-darwin) + nokogiri (1.19.2-x86_64-darwin) racc (~> 1.4) - nokogiri (1.19.0-x86_64-linux-gnu) + nokogiri (1.19.2-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.0-x86_64-linux-musl) + nokogiri (1.19.2-x86_64-linux-musl) racc (~> 1.4) + ntcharts (0.1.2-aarch64-linux-gnu) + ntcharts (0.1.2-aarch64-linux-musl) + ntcharts (0.1.2-arm64-darwin) + ntcharts (0.1.2-x86_64-darwin) + ntcharts (0.1.2-x86_64-linux-gnu) + ntcharts (0.1.2-x86_64-linux-musl) parallel (1.27.0) parser (3.3.10.1) ast (~> 2.4.1) @@ -145,13 +210,30 @@ GEM date stringio racc (1.8.1) + rack (3.2.5) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.1.2) + actionpack (= 8.1.2) + activesupport (= 8.1.2) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.1) rake-compiler (1.3.1) @@ -167,6 +249,9 @@ GEM erb psych (>= 4.0.0) tsort + reactionview (0.3.0) + actionview (>= 7.0) + herb (>= 0.9.0, < 1.0.0) regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) @@ -195,13 +280,19 @@ GEM strscan (3.1.7) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) + thor (1.5.0) tsort (0.2.0) + turbo-rails (2.0.23) + actionpack (>= 7.1.0) + railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) uri (1.1.1) + useragent (0.16.11) + zeitwerk (2.7.5) PLATFORMS aarch64-linux @@ -216,6 +307,8 @@ PLATFORMS DEPENDENCIES actionview (~> 8.0) + benchmark + charm digest (~> 3.2) erubi herb! @@ -229,10 +322,12 @@ DEPENDENCIES rake-compiler-dock (~> 1.11) rbs! rbs-inline (~> 0.12) + reactionview (~> 0.3.0) reline (~> 0.6) rubocop (~> 1.71) sorbet steep! + turbo-rails (~> 2.0) BUNDLED WITH 2.7.2 diff --git a/bench/action_view/benchmark_helper.rb b/bench/action_view/benchmark_helper.rb new file mode 100644 index 000000000..35500ae82 --- /dev/null +++ b/bench/action_view/benchmark_helper.rb @@ -0,0 +1,446 @@ +# frozen_string_literal: true + +require "benchmark" +require "difftastic" +require "lipgloss" +require "yaml" +require "reactionview/template/handlers/herb/herb" + +require_relative "../../test/engine/action_view/action_view_test_helper" + +module Bench + module BenchmarkHelper + include Engine::ActionViewTestHelper + + LABEL_WIDTH = 50 + RENDER_ITERATIONS = 5000 + COMPILE_ITERATIONS = 500 + WARMUP_ITERATIONS = 100 + + def bold(str) = "\e[1m#{str}\e[0m" + def dimmed(str) = "\e[2m#{str}\e[0m" + def green(str) = "\e[32m#{str}\e[0m" + def red(str) = "\e[31m#{str}\e[0m" + + class ActionViewHelperCounter < Herb::Visitor + attr_reader :count + + def initialize + super + + @count = 0 + end + + def visit_html_element_node(node) + @count += 1 if node.element_source && node.element_source != "HTML" + super + end + end + + def count_action_view_helpers(ast) + counter = ActionViewHelperCounter.new + counter.visit(ast) + counter.count + end + + def fresh_action_view_context + lookup_context = ::ActionView::LookupContext.new([]) + view = ::ActionView::Base.with_empty_template_cache.new(lookup_context, {}, nil) + view.class.include(Turbo::FramesHelper) if defined?(Turbo::FramesHelper) + view.class.include(Engine::ActionViewTestHelper::SimpleUrlFor) + view + end + + def bar(ratio, width: 40) + filled = [(ratio * width).round, width].min + "#{green("█" * filled)}#{dimmed("░" * (width - filled))}" + end + + def per_iteration_label(total_seconds, iterations, unit) + format("(%