Files
liquid/performance/theme_runner.rb
T

147 lines
4.2 KiB
Ruby

# frozen_string_literal: true
# This profiler run simulates Shopify.
# We are looking in the tests directory for liquid files and render them within the designated layout file.
# We will also export a substantial database to liquid which the templates can render values of.
# All this is to make the benchmark as non synthetic as possible. All templates and tests are lifted from
# direct real-world usage and the profiler measures code that looks very similar to the way it looks in
# Shopify which is likely the biggest user of liquid in the world which something to the tune of several
# million Template#render calls a day.
require_relative 'shopify/liquid'
require_relative 'shopify/database'
class ThemeRunner
class FileSystem
def initialize(path)
@path = path
end
# Called by Liquid to retrieve a template file
def read_template_file(template_path)
File.read(@path + '/' + template_path + '.liquid')
end
end
# Initialize a new liquid ThemeRunner instance
# Will load all templates into memory, do this now so that we don't profile IO.
def initialize
@tests = []
Dir[__dir__ + '/tests/**/*.liquid'].each do |test|
next if File.basename(test) == 'theme.liquid'
test_name = File.basename(File.dirname(test)) + "/" + File.basename(test)
theme_name = File.basename(File.dirname(test))
template_name = File.basename(test)
layout_path = File.dirname(test) + '/theme.liquid'
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
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
def compile_all
@tests.each do |test_hash|
Liquid::Template.new.parse(test_hash[:liquid])
Liquid::Template.new.parse(test_hash[:layout])
end
end
# `tokenize` will just test the tokenizen portion of liquid without any templates
def tokenize_all
ss = StringScanner.new("")
@tests.each do |test_hash|
tokenizer = Liquid::Tokenizer.new(
source: test_hash[:liquid],
string_scanner: ss,
line_numbers: true,
)
while tokenizer.shift; end
end
end
# `run` is called to benchmark rendering and compiling at the same time
def run_all
@tests.each do |test|
compile_and_render(test)
end
end
# `render` is called to benchmark just the render portion of liquid
def render_all
@compiled_tests ||= compile_all_tests
@compiled_tests.each do |test|
render_template(test)
end
end
def run_one_test(test_name)
test = find_test(test_name)
compile_and_render(test)
end
private
def render_template(compiled_test)
tmpl, assigns, layout = compiled_test.values_at(:tmpl, :assigns, :layout)
if layout
assigns['content_for_layout'] = tmpl.render!(assigns)
layout.render!(assigns)
else
tmpl.render!(assigns)
end
end
def compile_and_render(test)
compiled_test = compile_test(test[:liquid], test[:layout], test[:template_name], test[:theme_path])
render_template(compiled_test)
end
def compile_all_tests
@compiled_tests = []
@tests.each do |test_hash|
@compiled_tests << compile_test(
test_hash[:liquid],
test_hash[:layout],
test_hash[:template_name],
test_hash[:theme_path],
)
end
@compiled_tests
end
def compile_test(template, layout, template_name, theme_path)
tmpl = Liquid::Template.new
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
parsed_layout = tmpl.parse(layout).dup
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
else
{ tmpl: parsed_template, assigns: assigns }
end
end
end