make theme_runner actually useful outside of the performance benchmarks

This commit is contained in:
Tobi Lutke
2025-07-29 17:32:31 -04:00
parent 9bb7fbf123
commit 1b1a40c606
2 changed files with 76 additions and 63 deletions
+11 -5
View File
@@ -3,7 +3,13 @@
require 'benchmark/ips' require 'benchmark/ips'
require_relative 'theme_runner' require_relative 'theme_runner'
RubyVM::YJIT.enable if defined?(RubyVM::YJIT) if defined?(RubyVM::YJIT)
RubyVM::YJIT.enable
puts "* YJIT enabled"
else
puts "* YJIT not enabled"
end
Liquid::Environment.default.error_mode = ARGV.first.to_sym if ARGV.first Liquid::Environment.default.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new profiler = ThemeRunner.new
@@ -18,8 +24,8 @@ Benchmark.ips do |x|
phase = ENV["PHASE"] || "all" phase = ENV["PHASE"] || "all"
x.report("tokenize:") { profiler.tokenize } if phase == "all" || phase == "tokenize" x.report("tokenize:") { profiler.tokenize_all } if phase == "all" || phase == "tokenize"
x.report("parse:") { profiler.compile } if phase == "all" || phase == "parse" x.report("parse:") { profiler.compile_all } if phase == "all" || phase == "parse"
x.report("render:") { profiler.render } if phase == "all" || phase == "render" x.report("render:") { profiler.render_all } if phase == "all" || phase == "render"
x.report("parse & render:") { profiler.run } if phase == "all" || phase == "run" x.report("parse & render:") { profiler.run_all } if phase == "all" || phase == "run"
end end
+65 -58
View File
@@ -26,22 +26,38 @@ class ThemeRunner
# Initialize a new liquid ThemeRunner instance # Initialize a new liquid ThemeRunner instance
# Will load all templates into memory, do this now so that we don't profile IO. # Will load all templates into memory, do this now so that we don't profile IO.
def initialize def initialize
@tests = Dir[__dir__ + '/tests/**/*.liquid'].collect do |test| @tests = []
Dir[__dir__ + '/tests/**/*.liquid'].each do |test|
next if File.basename(test) == 'theme.liquid' next if File.basename(test) == 'theme.liquid'
theme_path = File.dirname(test) + '/theme.liquid' test_name = File.basename(File.dirname(test)) + "/" + File.basename(test)
{ theme_name = File.basename(File.dirname(test))
liquid: File.read(test), template_name = File.basename(test)
layout: (File.file?(theme_path) ? File.read(theme_path) : nil), layout_path = File.dirname(test) + '/theme.liquid'
template_name: test,
}
end.compact
compile_all_tests test = {
test_name: test_name,
liquid: File.read(test),
layout: File.file?(layout_path) ? File.read(layout_path) : nil,
template_name: template_name,
theme_name: theme_name,
theme_path: File.realpath(File.dirname(test)),
}
@tests << test
end
end end
def find_test(test_name)
@tests.find do |test_hash|
test_hash[:test_name] == test_name
end
end
attr_accessor :tests
# `compile` will test just the compilation portion of liquid without any templates # `compile` will test just the compilation portion of liquid without any templates
def compile def compile_all
@tests.each do |test_hash| @tests.each do |test_hash|
Liquid::Template.new.parse(test_hash[:liquid]) Liquid::Template.new.parse(test_hash[:liquid])
Liquid::Template.new.parse(test_hash[:layout]) Liquid::Template.new.parse(test_hash[:layout])
@@ -49,7 +65,7 @@ class ThemeRunner
end end
# `tokenize` will just test the tokenizen portion of liquid without any templates # `tokenize` will just test the tokenizen portion of liquid without any templates
def tokenize def tokenize_all
ss = StringScanner.new("") ss = StringScanner.new("")
@tests.each do |test_hash| @tests.each do |test_hash|
tokenizer = Liquid::Tokenizer.new( tokenizer = Liquid::Tokenizer.new(
@@ -62,78 +78,69 @@ class ThemeRunner
end end
# `run` is called to benchmark rendering and compiling at the same time # `run` is called to benchmark rendering and compiling at the same time
def run def run_all
each_test do |liquid, layout, assigns, page_template, template_name| @tests.each do |test|
compile_and_render(liquid, layout, assigns, page_template, template_name) compile_and_render(test)
end end
end end
# `render` is called to benchmark just the render portion of liquid # `render` is called to benchmark just the render portion of liquid
def render def render_all
@compiled_tests ||= compile_all_tests
@compiled_tests.each do |test| @compiled_tests.each do |test|
tmpl = test[:tmpl] render_template(test)
assigns = test[:assigns]
layout = test[:layout]
if layout
assigns['content_for_layout'] = tmpl.render!(assigns)
layout.render!(assigns)
else
tmpl.render!(assigns)
end
end end
end end
def run_one_test(test_name)
test = find_test(test_name)
compile_and_render(test)
end
private private
def render_layout(template, layout, assigns) def render_template(compiled_test)
assigns['content_for_layout'] = template.render!(assigns) tmpl, assigns, layout = compiled_test.values_at(:tmpl, :assigns, :layout)
layout&.render!(assigns) if layout
assigns['content_for_layout'] = tmpl.render!(assigns)
layout.render!(assigns)
else
tmpl.render!(assigns)
end
end end
def compile_and_render(template, layout, assigns, page_template, template_file) def compile_and_render(test)
compiled_test = compile_test(template, layout, assigns, page_template, template_file) compiled_test = compile_test(test[:liquid], test[:layout], test[:template_name], test[:theme_path])
render_layout(compiled_test[:tmpl], compiled_test[:layout], compiled_test[:assigns]) render_template(compiled_test)
end end
def compile_all_tests def compile_all_tests
@compiled_tests = [] @compiled_tests = []
each_test do |liquid, layout, assigns, page_template, template_name| @tests.each do |test_hash|
@compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name) @compiled_tests << compile_test(
test_hash[:liquid],
test_hash[:layout],
test_hash[:template_name],
test_hash[:theme_path],
)
end end
@compiled_tests @compiled_tests
end end
def compile_test(template, layout, assigns, page_template, template_file) def compile_test(template, layout, template_name, theme_path)
tmpl = init_template(page_template, template_file) tmpl = Liquid::Template.new
parsed_template = tmpl.parse(template).dup tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = template_name
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(theme_path)
parsed_template = tmpl.parse(template)
assigns = Database.tables.dup
if layout if layout
parsed_layout = tmpl.parse(layout) parsed_layout = tmpl.parse(layout).dup
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout } { tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
else else
{ tmpl: parsed_template, assigns: assigns } { tmpl: parsed_template, assigns: assigns }
end end
end end
# utility method with similar functionality needed in `compile_all_tests` and `run`
def each_test
# Dup assigns because will make some changes to them
assigns = Database.tables.dup
@tests.each do |test_hash|
# Compute page_template outside of profiler run, uninteresting to profiler
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name])
end
end
# set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`
def init_template(page_template, template_file)
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
tmpl
end
end end