File: theme_runner.rb

package info (click to toggle)
ruby-liquid 5.4.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,176 kB
  • sloc: ruby: 10,561; makefile: 6
file content (126 lines) | stat: -rw-r--r-- 4,363 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# 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'].collect do |test|
      next if File.basename(test) == 'theme.liquid'

      theme_path = File.dirname(test) + '/theme.liquid'
      {
        liquid: File.read(test),
        layout: (File.file?(theme_path) ? File.read(theme_path) : nil),
        template_name: test,
      }
    end.compact

    compile_all_tests
  end

  # `compile` will test just the compilation portion of liquid without any templates
  def compile
    @tests.each do |test_hash|
      Liquid::Template.new.parse(test_hash[:liquid])
      Liquid::Template.new.parse(test_hash[:layout])
    end
  end

  # `run` is called to benchmark rendering and compiling at the same time
  def run
    each_test do |liquid, layout, assigns, page_template, template_name|
      compile_and_render(liquid, layout, assigns, page_template, template_name)
    end
  end

  # `render` is called to benchmark just the render portion of liquid
  def render
    @compiled_tests.each do |test|
      tmpl    = test[:tmpl]
      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

  private

  def render_layout(template, layout, assigns)
    assigns['content_for_layout'] = template.render!(assigns)
    layout&.render!(assigns)
  end

  def compile_and_render(template, layout, assigns, page_template, template_file)
    compiled_test = compile_test(template, layout, assigns, page_template, template_file)
    render_layout(compiled_test[:tmpl], compiled_test[:layout], compiled_test[:assigns])
  end

  def compile_all_tests
    @compiled_tests = []
    each_test do |liquid, layout, assigns, page_template, template_name|
      @compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
    end
    @compiled_tests
  end

  def compile_test(template, layout, assigns, page_template, template_file)
    tmpl            = init_template(page_template, template_file)
    parsed_template = tmpl.parse(template).dup

    if layout
      parsed_layout = tmpl.parse(layout)
      { tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
    else
      { tmpl: parsed_template, assigns: assigns }
    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