diff --git a/benchmarks.yml b/benchmarks.yml index 78ba26bf..18d7133b 100644 --- a/benchmarks.yml +++ b/benchmarks.yml @@ -19,6 +19,9 @@ liquid-c: liquid-compile: desc: compiles a chosen-for-profiling Liquid theme repeatedly. category: headline +liquid-il: + desc: renders LiquidIL-generated Ruby for a set of Liquid templates repeatedly (Liquid compiled to standalone Ruby). + category: headline liquid-render: desc: renders a chosen-for-profiling Liquid theme repeatedly. category: headline diff --git a/benchmarks/liquid-il/Gemfile b/benchmarks/liquid-il/Gemfile new file mode 100644 index 00000000..ae519d38 --- /dev/null +++ b/benchmarks/liquid-il/Gemfile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +git_source(:github) do |repo_name| + "https://github.com/#{repo_name}.git" +end + +# LiquidIL compiles Liquid to Ruby; this benchmark renders its generated Ruby. +# See extract.rb for how generated/ is produced from templates.yml. +gem "liquid-il", github: "tobi/liquid-il", ref: "1b07d162068f1ed2becec1bafd29f36f52dbbd5b" + +gem "base64" +gem "bigdecimal" diff --git a/benchmarks/liquid-il/Gemfile.lock b/benchmarks/liquid-il/Gemfile.lock new file mode 100644 index 00000000..41f742ce --- /dev/null +++ b/benchmarks/liquid-il/Gemfile.lock @@ -0,0 +1,30 @@ +GIT + remote: https://github.com/tobi/liquid-il.git + revision: 1b07d162068f1ed2becec1bafd29f36f52dbbd5b + ref: 1b07d162068f1ed2becec1bafd29f36f52dbbd5b + specs: + liquid-il (0.1.0) + +GEM + remote: https://rubygems.org/ + specs: + base64 (0.3.0) + bigdecimal (4.1.2) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + base64 + bigdecimal + liquid-il! + +CHECKSUMS + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd + bundler (4.0.16) sha256=d6ca5dd440c24f9abce9844cf44cc8e18c6a553de65a47efb4544137af92c47d + liquid-il (0.1.0) + +BUNDLED WITH + 4.0.16 diff --git a/benchmarks/liquid-il/benchmark.rb b/benchmarks/liquid-il/benchmark.rb new file mode 100644 index 00000000..144894ca --- /dev/null +++ b/benchmarks/liquid-il/benchmark.rb @@ -0,0 +1,31 @@ +require_relative '../../harness/loader' + +Dir.chdir __dir__ +use_gemfile + +require 'liquid_il' +require 'json' + +# LiquidIL compiles Liquid templates to standalone Ruby (see extract.rb). We +# render that committed generated Ruby here, so this benchmark exercises the +# machine-generated code a JIT actually compiles: heavy runtime-helper dispatch, +# partial lambdas, string-buffer building, hash lookups, and loops. The exact +# Ruby lives in generated/ and does not drift with the LiquidIL compiler. +manifest = JSON.parse(File.read(File.join(__dir__, 'manifest.json'))) +CASES = manifest.map do |m| + require File.join(__dir__, 'generated', "#{m['spec']}.rb") + mod = Object.const_get(m['module']) + assigns = JSON.parse(File.read(File.join(__dir__, 'fixtures', "#{m['spec']}.json"))) + [mod, assigns] +end + +# Sanity: every generated module must produce output, or the timing is empty. +CASES.each { |mod, assigns| raise "empty render for #{mod}" if mod.render(assigns).to_s.empty? } + +run_benchmark(150) do + # Each render is quick; render the whole template set several times per + # iteration to reduce time-measurement noise (mirrors liquid-render). + 1000.times do + CASES.each { |mod, assigns| mod.render(assigns) } + end +end diff --git a/benchmarks/liquid-il/extract.rb b/benchmarks/liquid-il/extract.rb new file mode 100644 index 00000000..03f0e7f2 --- /dev/null +++ b/benchmarks/liquid-il/extract.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +# Regenerate the committed LiquidIL-generated Ruby sample from templates.yml. +# +# LiquidIL (https://github.com/tobi/liquid-il) compiles a Liquid template to +# standalone Ruby (Template#to_ruby), inlining static partials so the result +# renders with no file_system. This script compiles each template in +# templates.yml to a module under generated/, writes its render assigns to +# fixtures/, and validates the standalone module reproduces the template's +# expected output. benchmark.rb then renders those committed modules in a hot +# loop, so the exact Ruby the JIT compiles lives in the repo and does not drift +# with the compiler. +# +# BUNDLE_GEMFILE=$PWD/Gemfile bundle exec ruby extract.rb +# +# The generated/ and fixtures/ output is committed; you only need to re-run +# this when templates.yml or the pinned LiquidIL version changes. + +require "json" +require "yaml" +require "liquid_il" + +HERE = __dir__ +GEN_DIR = File.join(HERE, "generated") +FIX_DIR = File.join(HERE, "fixtures") + +# Compile-time file system for the templates' inline partials. Static partials +# are inlined into the caller, so the generated Ruby holds no reference to it. +class SpecFileSystem + def initialize(files) = @files = files || {} + def read_template_file(name, _context = nil) + @files.fetch(name.to_s) { raise LiquidIL::FileSystemError, "no such partial: #{name}" } + end +end + +def module_name_for(name) + "LiquidILBench" + name.split(/[^a-zA-Z0-9]/).reject(&:empty?).map(&:capitalize).join +end + +def deep_dup(obj) + case obj + when Hash then obj.each_with_object({}) { |(k, v), h| h[k] = deep_dup(v) } + when Array then obj.map { |v| deep_dup(v) } + else obj + end +end + +Dir.mkdir(GEN_DIR) unless Dir.exist?(GEN_DIR) +Dir.mkdir(FIX_DIR) unless Dir.exist?(FIX_DIR) + +specs = YAML.safe_load(File.read(File.join(HERE, "templates.yml")), aliases: true).fetch("specs") +manifest = [] + +specs.each do |spec| + name = spec.fetch("name") + mod = module_name_for(name) + + ctx = LiquidIL::Context.new(file_system: SpecFileSystem.new(spec["filesystem"])) + template = ctx.parse(spec.fetch("template")) + ruby = template.to_ruby(mod) + + # Validate the standalone module in an isolated namespace, no file_system. + probe = Module.new + probe.module_eval(ruby, "generated/#{name}.rb") + produced = probe.const_get(mod).render(deep_dup(spec["environment"] || {})) + expected = spec["expected"] + if expected && produced != expected + raise "#{name}: generated Ruby output does not match expected" + end + + File.write(File.join(GEN_DIR, "#{name}.rb"), ruby) + File.write(File.join(FIX_DIR, "#{name}.json"), JSON.pretty_generate(spec["environment"] || {}) + "\n") + manifest << { "spec" => name, "module" => mod, "ruby_bytes" => ruby.bytesize } + printf(" ok %-32s %6d B\n", name, ruby.bytesize) +end + +File.write(File.join(HERE, "manifest.json"), JSON.pretty_generate(manifest) + "\n") +puts "Extracted #{manifest.size} module(s); #{manifest.sum { |m| m["ruby_bytes"] }} B generated Ruby." diff --git a/benchmarks/liquid-il/fixtures/bench_ecommerce_product_page.json b/benchmarks/liquid-il/fixtures/bench_ecommerce_product_page.json new file mode 100644 index 00000000..c5e6a99b --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_ecommerce_product_page.json @@ -0,0 +1,59 @@ +{ + "product": { + "name": "Premium Widget", + "price": 99.99, + "description": "The finest widget money can buy", + "sku": "WDG-001", + "in_stock": true, + "features": [ + "Durable", + "Lightweight", + "Eco-friendly" + ] + }, + "breadcrumbs": [ + { + "label": "Home", + "url": "/" + }, + { + "label": "Widgets", + "url": "/widgets" + }, + { + "label": "Premium Widget", + "url": null + } + ], + "reviews": [ + { + "author": "Alice", + "rating": 5, + "text": "Amazing!" + }, + { + "author": "Bob", + "rating": 4, + "text": "Very good" + }, + { + "author": "Carol", + "rating": 5, + "text": "Love it" + } + ], + "related": [ + { + "name": "Basic Widget", + "price": 29.99 + }, + { + "name": "Super Widget", + "price": 149.99 + }, + { + "name": "Widget Pro", + "price": 199.99 + } + ] +} diff --git a/benchmarks/liquid-il/fixtures/bench_notification_center.json b/benchmarks/liquid-il/fixtures/bench_notification_center.json new file mode 100644 index 00000000..004cac25 --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_notification_center.json @@ -0,0 +1,112 @@ +{ + "notifications": [ + { + "type": "order", + "order_id": "ORD-001", + "status": "confirmed", + "total": 149.99, + "items": 3, + "date": "2024-01-15" + }, + { + "type": "shipping", + "order_id": "ORD-002", + "carrier": "FedEx", + "tracking": "1234567890", + "eta": "Jan 18" + }, + { + "type": "review", + "product": "Leather Jacket", + "customer": "Alice", + "rating": 5, + "snippet": "Amazing quality!" + }, + { + "type": "promo", + "code": "SAVE20", + "discount": "20%", + "expires": "Jan 31", + "min_purchase": 50 + }, + { + "type": "stock", + "product": "Blue Widget", + "sku": "BW-001", + "status": "back_in_stock", + "quantity": 25 + }, + { + "type": "order", + "order_id": "ORD-003", + "status": "processing", + "total": 89.5, + "items": 2, + "date": "2024-01-16" + }, + { + "type": "shipping", + "order_id": "ORD-001", + "carrier": "UPS", + "tracking": "9876543210", + "eta": "Jan 17" + }, + { + "type": "review", + "product": "Cotton T-Shirt", + "customer": "Bob", + "rating": 4, + "snippet": "Great fit" + }, + { + "type": "promo", + "code": "FREESHIP", + "discount": "Free Shipping", + "expires": "Feb 1", + "min_purchase": 75 + }, + { + "type": "stock", + "product": "Red Scarf", + "sku": "RS-002", + "status": "low_stock", + "quantity": 3 + }, + { + "type": "order", + "order_id": "ORD-004", + "status": "shipped", + "total": 299.0, + "items": 1, + "date": "2024-01-14" + }, + { + "type": "shipping", + "order_id": "ORD-004", + "carrier": "USPS", + "tracking": "5555555555", + "eta": "Jan 19" + }, + { + "type": "review", + "product": "Wool Sweater", + "customer": "Carol", + "rating": 5, + "snippet": "Perfect for winter" + }, + { + "type": "promo", + "code": "VIP25", + "discount": "25%", + "expires": "Jan 20", + "min_purchase": 100 + }, + { + "type": "stock", + "product": "Green Hat", + "sku": "GH-003", + "status": "back_in_stock", + "quantity": 50 + } + ] +} diff --git a/benchmarks/liquid-il/fixtures/bench_render_for_loop.json b/benchmarks/liquid-il/fixtures/bench_render_for_loop.json new file mode 100644 index 00000000..948ebd8f --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_render_for_loop.json @@ -0,0 +1,24 @@ +{ + "items": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20 + ] +} diff --git a/benchmarks/liquid-il/fixtures/bench_render_with_params.json b/benchmarks/liquid-il/fixtures/bench_render_with_params.json new file mode 100644 index 00000000..4a75909f --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_render_with_params.json @@ -0,0 +1,29 @@ +{ + "products": [ + { + "name": "Widget", + "price": 9.99, + "description": "A useful widget" + }, + { + "name": "Gadget", + "price": 19.99, + "description": "An amazing gadget" + }, + { + "name": "Gizmo", + "price": 29.99, + "description": "A fantastic gizmo" + }, + { + "name": "Doohickey", + "price": 39.99, + "description": "A wonderful doohickey" + }, + { + "name": "Thingamajig", + "price": 49.99, + "description": "A marvelous thingamajig" + } + ] +} diff --git a/benchmarks/liquid-il/fixtures/bench_simple_product_listing.json b/benchmarks/liquid-il/fixtures/bench_simple_product_listing.json new file mode 100644 index 00000000..ad4443d4 --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_simple_product_listing.json @@ -0,0 +1,16 @@ +{ + "products": [ + { + "name": "Widget", + "price": 9.99 + }, + { + "name": "Gadget", + "price": 19.99 + }, + { + "name": "Gizmo", + "price": 29.99 + } + ] +} diff --git a/benchmarks/liquid-il/fixtures/bench_theme_cart_page.json b/benchmarks/liquid-il/fixtures/bench_theme_cart_page.json new file mode 100644 index 00000000..a985c80b --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_theme_cart_page.json @@ -0,0 +1,140 @@ +{ + "shop": { + "name": "Acme Store", + "currency": "USD", + "email": "support@acme.test" + }, + "cart": { + "item_count": 5, + "total_price": 549.95, + "items": [ + { + "id": 1, + "title": "Classic Leather Jacket", + "variant_title": "M / Black", + "price": 299.99, + "quantity": 1, + "line_price": 299.99, + "image": "/images/jacket.jpg", + "url": "/products/jacket", + "sku": "LJ-BLK-M", + "properties": { + "gift_wrap": "Yes", + "gift_message": "Happy Birthday!" + } + }, + { + "id": 2, + "title": "Cotton T-Shirt", + "variant_title": "L / White", + "price": 29.99, + "quantity": 3, + "line_price": 89.97, + "image": "/images/tshirt.jpg", + "url": "/products/tshirt", + "sku": "TS-WHT-L", + "properties": {} + }, + { + "id": 3, + "title": "Wool Beanie", + "variant_title": "One Size / Navy", + "price": 24.99, + "quantity": 2, + "line_price": 49.98, + "image": "/images/beanie.jpg", + "url": "/products/beanie", + "sku": "BN-NVY-OS", + "properties": {} + }, + { + "id": 4, + "title": "Leather Belt", + "variant_title": "34 / Brown", + "price": 59.99, + "quantity": 1, + "line_price": 59.99, + "image": "/images/belt.jpg", + "url": "/products/belt", + "sku": "BT-BRN-34", + "properties": {} + }, + { + "id": 5, + "title": "Silk Scarf", + "variant_title": "One Size / Burgundy", + "price": 49.99, + "quantity": 1, + "line_price": 49.99, + "image": "/images/scarf.jpg", + "url": "/products/scarf", + "sku": "SC-BUR-OS", + "properties": { + "monogram": "ABC" + } + } + ], + "discounts": [ + { + "code": "SAVE10", + "amount": 54.99, + "title": "10% Off" + } + ] + }, + "shipping_rates": [ + { + "name": "Standard Shipping", + "price": 5.99, + "delivery": "5-7 business days" + }, + { + "name": "Express Shipping", + "price": 15.99, + "delivery": "2-3 business days" + }, + { + "name": "Overnight", + "price": 29.99, + "delivery": "Next business day" + } + ], + "recommendations": [ + { + "title": "Leather Gloves", + "price": 49.99, + "image": "/images/gloves.jpg", + "url": "/products/gloves" + }, + { + "title": "Wool Socks", + "price": 19.99, + "image": "/images/socks.jpg", + "url": "/products/socks" + }, + { + "title": "Sunglasses", + "price": 79.99, + "image": "/images/sunglasses.jpg", + "url": "/products/sunglasses" + } + ], + "footer_links": [ + { + "title": "About", + "url": "/about" + }, + { + "title": "Contact", + "url": "/contact" + }, + { + "title": "Shipping", + "url": "/shipping" + }, + { + "title": "Returns", + "url": "/returns" + } + ] +} diff --git a/benchmarks/liquid-il/fixtures/bench_theme_collection_page.json b/benchmarks/liquid-il/fixtures/bench_theme_collection_page.json new file mode 100644 index 00000000..61cf88fe --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_theme_collection_page.json @@ -0,0 +1,329 @@ +{ + "shop": { + "name": "Acme Store", + "currency": "USD", + "email": "support@acme.test" + }, + "collection": { + "title": "Men's Outerwear", + "description": "Premium jackets and coats for every season", + "image": "/images/outerwear-hero.jpg", + "products_count": 48, + "products": [ + { + "id": 1, + "title": "Classic Leather Jacket", + "price": 299.99, + "compare_at_price": 399.99, + "image": "/images/leather.jpg", + "url": "/products/leather-jacket", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "leather", + "classic" + ] + }, + { + "id": 2, + "title": "Bomber Jacket", + "price": 249.99, + "compare_at_price": null, + "image": "/images/bomber.jpg", + "url": "/products/bomber", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "bomber", + "casual" + ] + }, + { + "id": 3, + "title": "Denim Jacket", + "price": 149.99, + "compare_at_price": 179.99, + "image": "/images/denim.jpg", + "url": "/products/denim-jacket", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "denim", + "casual" + ] + }, + { + "id": 4, + "title": "Wool Peacoat", + "price": 349.99, + "compare_at_price": null, + "image": "/images/peacoat.jpg", + "url": "/products/peacoat", + "vendor": "Acme Apparel", + "available": false, + "tags": [ + "wool", + "formal" + ] + }, + { + "id": 5, + "title": "Quilted Vest", + "price": 129.99, + "compare_at_price": null, + "image": "/images/vest.jpg", + "url": "/products/vest", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "vest", + "layering" + ] + }, + { + "id": 6, + "title": "Windbreaker", + "price": 99.99, + "compare_at_price": 129.99, + "image": "/images/windbreaker.jpg", + "url": "/products/windbreaker", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "light", + "athletic" + ] + }, + { + "id": 7, + "title": "Parka", + "price": 399.99, + "compare_at_price": null, + "image": "/images/parka.jpg", + "url": "/products/parka", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "winter", + "heavy" + ] + }, + { + "id": 8, + "title": "Suede Jacket", + "price": 279.99, + "compare_at_price": 329.99, + "image": "/images/suede.jpg", + "url": "/products/suede-jacket", + "vendor": "Premium Line", + "available": true, + "tags": [ + "suede", + "premium" + ] + }, + { + "id": 9, + "title": "Rain Jacket", + "price": 149.99, + "compare_at_price": null, + "image": "/images/rain.jpg", + "url": "/products/rain-jacket", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "waterproof", + "light" + ] + }, + { + "id": 10, + "title": "Down Jacket", + "price": 279.99, + "compare_at_price": null, + "image": "/images/down.jpg", + "url": "/products/down-jacket", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "winter", + "warm" + ] + }, + { + "id": 11, + "title": "Fleece Jacket", + "price": 89.99, + "compare_at_price": null, + "image": "/images/fleece.jpg", + "url": "/products/fleece", + "vendor": "Acme Apparel", + "available": true, + "tags": [ + "fleece", + "casual" + ] + }, + { + "id": 12, + "title": "Blazer", + "price": 199.99, + "compare_at_price": 249.99, + "image": "/images/blazer.jpg", + "url": "/products/blazer", + "vendor": "Premium Line", + "available": true, + "tags": [ + "formal", + "business" + ] + } + ] + }, + "filters": [ + { + "name": "Price", + "values": [ + { + "label": "Under $100", + "value": "0-100", + "count": 2 + }, + { + "label": "$100-$200", + "value": "100-200", + "count": 4 + }, + { + "label": "$200-$300", + "value": "200-300", + "count": 4 + }, + { + "label": "Over $300", + "value": "300-", + "count": 2 + } + ] + }, + { + "name": "Size", + "values": [ + { + "label": "S", + "value": "s", + "count": 10 + }, + { + "label": "M", + "value": "m", + "count": 12 + }, + { + "label": "L", + "value": "l", + "count": 11 + }, + { + "label": "XL", + "value": "xl", + "count": 9 + } + ] + }, + { + "name": "Color", + "values": [ + { + "label": "Black", + "value": "black", + "count": 6 + }, + { + "label": "Brown", + "value": "brown", + "count": 4 + }, + { + "label": "Navy", + "value": "navy", + "count": 3 + }, + { + "label": "Gray", + "value": "gray", + "count": 2 + } + ] + }, + { + "name": "Availability", + "values": [ + { + "label": "In Stock", + "value": "true", + "count": 11 + }, + { + "label": "Out of Stock", + "value": "false", + "count": 1 + } + ] + } + ], + "active_filters": [ + "100-200" + ], + "sort_options": [ + { + "label": "Featured", + "value": "featured" + }, + { + "label": "Price: Low to High", + "value": "price-asc" + }, + { + "label": "Price: High to Low", + "value": "price-desc" + }, + { + "label": "Newest", + "value": "created-desc" + }, + { + "label": "Best Selling", + "value": "best-selling" + } + ], + "current_sort": "featured", + "pagination": { + "current": 1, + "total": 4, + "pages": [ + 1, + 2, + 3, + 4 + ] + }, + "footer_links": [ + { + "title": "About", + "url": "/about" + }, + { + "title": "Contact", + "url": "/contact" + }, + { + "title": "Shipping", + "url": "/shipping" + }, + { + "title": "Returns", + "url": "/returns" + } + ] +} diff --git a/benchmarks/liquid-il/fixtures/bench_theme_product_page.json b/benchmarks/liquid-il/fixtures/bench_theme_product_page.json new file mode 100644 index 00000000..37ce5df5 --- /dev/null +++ b/benchmarks/liquid-il/fixtures/bench_theme_product_page.json @@ -0,0 +1,207 @@ +{ + "shop": { + "name": "Acme Store", + "currency": "USD", + "email": "support@acme.test" + }, + "product": { + "id": 12345, + "title": "Classic Leather Jacket", + "vendor": "Acme Apparel", + "type": "Outerwear", + "price": 299.99, + "compare_at_price": 399.99, + "description": "Premium quality genuine leather jacket with satin lining. Features two front pockets and an interior pocket.", + "sku": "LJ-BLK-M", + "available": true, + "tags": [ + "leather", + "jacket", + "outerwear", + "premium" + ], + "images": [ + { + "src": "/images/jacket-front.jpg", + "alt": "Front view" + }, + { + "src": "/images/jacket-back.jpg", + "alt": "Back view" + }, + { + "src": "/images/jacket-detail.jpg", + "alt": "Detail view" + }, + { + "src": "/images/jacket-pocket.jpg", + "alt": "Pocket detail" + } + ], + "options": [ + { + "name": "Size", + "values": [ + "S", + "M", + "L", + "XL" + ] + }, + { + "name": "Color", + "values": [ + "Black", + "Brown", + "Navy" + ] + } + ], + "features": [ + "100% genuine leather", + "Satin lining", + "YKK zippers", + "Two exterior pockets", + "One interior pocket" + ], + "care": [ + "Dry clean only", + "Store on padded hanger", + "Keep away from heat" + ] + }, + "variants": [ + { + "id": 1, + "title": "S / Black", + "price": 299.99, + "available": true, + "sku": "LJ-BLK-S" + }, + { + "id": 2, + "title": "M / Black", + "price": 299.99, + "available": true, + "sku": "LJ-BLK-M" + }, + { + "id": 3, + "title": "L / Black", + "price": 299.99, + "available": false, + "sku": "LJ-BLK-L" + }, + { + "id": 4, + "title": "XL / Black", + "price": 299.99, + "available": true, + "sku": "LJ-BLK-XL" + }, + { + "id": 5, + "title": "S / Brown", + "price": 299.99, + "available": true, + "sku": "LJ-BRN-S" + }, + { + "id": 6, + "title": "M / Brown", + "price": 299.99, + "available": true, + "sku": "LJ-BRN-M" + }, + { + "id": 7, + "title": "L / Brown", + "price": 299.99, + "available": true, + "sku": "LJ-BRN-L" + }, + { + "id": 8, + "title": "XL / Brown", + "price": 299.99, + "available": false, + "sku": "LJ-BRN-XL" + } + ], + "breadcrumbs": [ + { + "title": "Home", + "url": "/" + }, + { + "title": "Apparel", + "url": "/collections/apparel" + }, + { + "title": "Outerwear", + "url": "/collections/outerwear" + }, + { + "title": "Classic Leather Jacket", + "url": null + } + ], + "related_products": [ + { + "title": "Bomber Jacket", + "price": 249.99, + "image": "/images/bomber.jpg", + "url": "/products/bomber" + }, + { + "title": "Denim Jacket", + "price": 149.99, + "image": "/images/denim.jpg", + "url": "/products/denim" + }, + { + "title": "Suede Jacket", + "price": 279.99, + "image": "/images/suede.jpg", + "url": "/products/suede" + }, + { + "title": "Windbreaker", + "price": 99.99, + "image": "/images/wind.jpg", + "url": "/products/windbreaker" + } + ], + "recent_products": [ + { + "title": "Wool Sweater", + "price": 89.99, + "image": "/images/sweater.jpg", + "url": "/products/sweater" + }, + { + "title": "Cotton T-Shirt", + "price": 29.99, + "image": "/images/tshirt.jpg", + "url": "/products/tshirt" + } + ], + "footer_links": [ + { + "title": "About", + "url": "/about" + }, + { + "title": "Contact", + "url": "/contact" + }, + { + "title": "Shipping", + "url": "/shipping" + }, + { + "title": "Returns", + "url": "/returns" + } + ] +} diff --git a/benchmarks/liquid-il/generated/bench_ecommerce_product_page.rb b/benchmarks/liquid-il/generated/bench_ecommerce_product_page.rb new file mode 100644 index 00000000..a55795b3 --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_ecommerce_product_page.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +# {% render 'page_header', page_title: product.name %} +# {% render 'breadcrumbs', items: breadcrumbs %} +# {% render 'product_detail', product: product %} +# {% render 'product_reviews', reviews: reviews %} +# {% render 'related_products', products: related %} +# {% render 'page_footer' %} +# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchEcommerceProductPage.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchEcommerceProductPage + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + __p706167655f686561646572__ = nil + __p62726561646372756d6273__ = nil + __p70726f647563745f64657461696c__ = nil + __p7265766965775f63617264__ = nil + __p70726f647563745f72657669657773__ = nil + __p72656c617465645f70726f6475637473__ = nil + __p706167655f666f6f746572__ = nil + + __p70726f647563745f72657669657773__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _cs = isolated ? {} : (parent_cycle_state || {}) + _O << "
\n

Customer Reviews

\n " + _H.ei(_S.lookup("reviews")) do |_i1300__| + _O << "\n " + _pa0__ = _i1300__ + _H.rolf(_O, "
\n ", _pa0__, "author") + _H.rolf(_O, "\n ", _pa0__, "rating") + _H.rolf(_O, "/5\n

", _pa0__, "text") + _O << "

\n
\n" + _O << "\n " + end + _O << "\n
\n" + } + + _O = +"" + _cs = {} + + _pa0__ = _H.lookup(_S.lookup("product"), "name") + _H.roa(_O, "
\n

", _pa0__) + _O << "

\n
\n" + _O << "\n" + _pa1__ = _S.lookup("breadcrumbs") + _O << "\n" + _O << "\n" + _pa2__ = _S.lookup("product") + _H.rolf(_O, "
\n

", _pa2__, "name") + _H.rolf(_O, "

\n

$", _pa2__, "price") + _H.rolf(_O, "

\n

", _pa2__, "description") + _H.rolf(_O, "

\n

SKU: ", _pa2__, "sku") + _O << "

\n " + if _H.t(_H.lf(_pa2__, "in_stock")) + _O << "\n

In Stock

\n " + else + _O << "\n

Out of Stock

\n " + end + _O << "\n \n
\n" + _O << "\n" + __partial_args__ = {} + __partial_args__["reviews"] = _S.lookup("reviews") + _H.ipc(__p70726f647563745f72657669657773__, "product_reviews", __partial_args__, _O, _S, true, 4, _cs) + _O << "\n" + _pa3__ = _S.lookup("related") + _O << "
\n

Related Products

\n \n
\n" + _O << "\n" + _O << "\n" + _O << "\n" + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/generated/bench_notification_center.rb b/benchmarks/liquid-il/generated/bench_notification_center.rb new file mode 100644 index 00000000..cb8da5bc --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_notification_center.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +#
+# {% for notification in notifications %} +# {% case notification.type %} +# {% when 'order' %} +# {% render 'notif_order', notif: notification %} +# {% when 'shipping' %} +# {% render 'notif_shipping', notif: notification %} +# {% when 'review' %} +# {% render 'notif_review', notif: notification %} +# {% when 'promo' %} +# {% render 'notif_promo', notif: notification %} +# {% when 'stock' %} +# {% render 'notif_stock', notif: notification %} +# {% endcase %} +# {% endfor %} +#
+# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchNotificationCenter.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchNotificationCenter + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + _U = LiquidIL::Utils + __p6e6f7469665f6f72646572__ = nil + __p6e6f7469665f7368697070696e67__ = nil + __p6e6f7469665f726576696577__ = nil + __p6e6f7469665f70726f6d6f__ = nil + __p6e6f7469665f73746f636b__ = nil + + _O = +"" + _cst = [] + + _O << "
\n " + _H.ei(_S.lookup("notifications")) do |_i0__| + _O << "\n " + __temp_0__ = _i0__["type"] + __temp_1__ = false + _cst << _O; _O = String.new + _O << "\n " + _O = _cst.pop + if (_U.ce?("order", __temp_0__)) + __temp_1__ = true + _O << "\n " + _pa0__ = _i0__ + _H.rolf(_O, "
\n
📦
\n
\n

Order ", _pa0__, "order_id") + _H.rolf(_O, " ", _pa0__, "status") + _H.rolf(_O, "

\n

", _pa0__, "items") + _H.rolf(_O, " items • $", _pa0__, "total") + _H.rolf(_O, "

\n \n " + if (_H.cmp(_H.lf(_pa0__, "status"), "confirmed", :eq, _O, "notif_order")) + _O << "\n Confirmed\n " + else + if (_H.cmp(_H.lf(_pa0__, "status"), "processing", :eq, _O, "notif_order")) + _O << "\n Processing\n " + else + if (_H.cmp(_H.lf(_pa0__, "status"), "shipped", :eq, _O, "notif_order")) + _O << "\n Shipped\n " + end + end + end + _O << "\n
\n
\n" + _O << "\n " + end + if (_U.ce?("shipping", __temp_0__)) + __temp_1__ = true + _O << "\n " + _pa1__ = _i0__ + _H.rolf(_O, "
\n
🚚
\n
\n

Shipment Update

\n

Order ", _pa1__, "order_id") + _H.rolf(_O, " via ", _pa1__, "carrier") + _H.rolf(_O, "

\n

Tracking: ", _pa1__, "tracking") + _H.rolf(_O, "

\n

Expected: ", _pa1__, "eta") + _H.rolf(_O, "

\n Track Package\n
\n
\n" + _O << "\n " + end + if (_U.ce?("review", __temp_0__)) + __temp_1__ = true + _O << "\n " + _pa2__ = _i0__ + _H.rolf(_O, "
\n
\n
\n

New Review for ", _pa2__, "product") + _H.rolf(_O, "

\n

", _pa2__, "customer") + _H.rolf(_O, " rated ", _pa2__, "rating") + _H.rolf(_O, "/5

\n
\"", _pa2__, "snippet") + _O << "\"
\n View All Reviews\n
\n
\n" + _O << "\n " + end + if (_U.ce?("promo", __temp_0__)) + __temp_1__ = true + _O << "\n " + _pa3__ = _i0__ + _H.rolf(_O, "
\n
🎁
\n
\n

", _pa3__, "discount") + _H.rolf(_O, " Off!

\n

Use code ", _pa3__, "code") + _H.rolf(_O, "

\n

Min. purchase $", _pa3__, "min_purchase") + _H.rolf(_O, " • Expires ", _pa3__, "expires") + _H.rolf(_O, "

\n \n
\n
\n" + _O << "\n " + end + if (_U.ce?("stock", __temp_0__)) + __temp_1__ = true + _O << "\n " + _pa4__ = _i0__ + _O << "
\n
" + if (_H.cmp(_H.lf(_pa4__, "status"), "back_in_stock", :eq, _O, "notif_stock")) + _O << "✅" + else + _O << "⚠️" + end + _H.rolf(_O, "
\n
\n

", _pa4__, "product") + _H.rolf(_O, "

\n

SKU: ", _pa4__, "sku") + _O << "

\n " + if (_H.cmp(_H.lf(_pa4__, "status"), "back_in_stock", :eq, _O, "notif_stock")) + _H.rolf(_O, "\n

Back in stock! ", _pa4__, "quantity") + _O << " available

\n " + else + if (_H.cmp(_H.lf(_pa4__, "status"), "low_stock", :eq, _O, "notif_stock")) + _H.rolf(_O, "\n

Low stock: only ", _pa4__, "quantity") + _O << " left

\n " + end + end + _H.rolf(_O, "\n View Product\n
\n
\n" + _O << "\n " + end + _O << "\n " + end + _O << "\n
\n" + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/generated/bench_render_for_loop.rb b/benchmarks/liquid-il/generated/bench_render_for_loop.rb new file mode 100644 index 00000000..c234020e --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_render_for_loop.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +# {% render 'item' for items as item %} +# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchRenderForLoop.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchRenderForLoop + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + __p6974656d__ = nil + + __p6974656d__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _H.oa(_O, _S.lookup("item")) + unless _H.t(_H.lp(_S.lookup("forloop"), "last")) + _O << ", " + end + } + + _O = +"" + + __partial_args__ = {} + _H.rpf(__p6974656d__, "item", "item", _S.lookup("items"), __partial_args__, _O, _S, 1) + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/generated/bench_render_with_params.rb b/benchmarks/liquid-il/generated/bench_render_with_params.rb new file mode 100644 index 00000000..58cba7f8 --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_render_with_params.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +# {% for product in products %} +# {% render 'product_card', product: product, show_price: true %} +# {% endfor %} +# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchRenderWithParams.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchRenderWithParams + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + __p70726f647563745f63617264__ = nil + + _O = +"" + + _H.ei(_S.lookup("products")) do |_i0__| + _O << "\n" + _pa0__ = _i0__ + _pa1__ = true + _H.rolf(_O, "
\n

", _pa0__, "name") + _H.rolf(_O, "

\n

", _pa0__, "description") + _O << "

\n " + if _H.t(_pa1__) + _H.rolf(_O, "$", _pa0__, "price") + _O << "" + end + _O << "\n
\n" + _O << "\n" + end + _O << "\n" + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/generated/bench_simple_product_listing.rb b/benchmarks/liquid-il/generated/bench_simple_product_listing.rb new file mode 100644 index 00000000..3c209cc1 --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_simple_product_listing.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +# {% render 'header' with 'Products' as title %} +# +# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchSimpleProductListing.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchSimpleProductListing + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + __p686561646572__ = nil + __p6974656d__ = nil + + __p686561646572__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _H.roa(_O, "
", _S.lookup("title")) + _O << "
" + } + + __p6974656d__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _H.rolf(_O, "
  • ", _S.lookup("item"), "name") + _H.rolf(_O, ": $", _S.lookup("item"), "price") + _O << "
  • " + } + + _O = +"" + + __partial_args__ = {} + __with_val__ = "Products" + __partial_args__["title"] = __with_val__ unless __with_val__.nil? + _H.ipc(__p686561646572__, "header", __partial_args__, _O, _S, true, 1) + _O << "\n\n" + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/generated/bench_theme_cart_page.rb b/benchmarks/liquid-il/generated/bench_theme_cart_page.rb new file mode 100644 index 00000000..7bb719b3 --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_theme_cart_page.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +# {% render 'theme_header', shop: shop %} +#
    +#

    Shopping Cart

    +# {% if cart.items.size > 0 %} +# {% render 'cart_table', cart: cart %} +# {% render 'cart_totals', cart: cart %} +# {% render 'cart_actions' %} +# {% render 'shipping_calculator', rates: shipping_rates %} +# {% else %} +# {% render 'empty_cart' %} +# {% endif %} +# {% render 'cart_recommendations', products: recommendations %} +#
    +# {% render 'theme_footer', shop: shop, links: footer_links %} +# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchThemeCartPage.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchThemeCartPage + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + _F = LiquidIL::Filters + __p7468656d655f686561646572__ = nil + __p636172745f6c696e655f6974656d__ = nil + __p636172745f7461626c65__ = nil + __p636172745f746f74616c73__ = nil + __p636172745f616374696f6e73__ = nil + __p7368697070696e675f63616c63756c61746f72__ = nil + __p656d7074795f63617274__ = nil + __p70726f647563745f63617264__ = nil + __p636172745f7265636f6d6d656e646174696f6e73__ = nil + __p7468656d655f666f6f746572__ = nil + + __p636172745f7461626c65__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _cs = isolated ? {} : (parent_cycle_state || {}) + _O << "\n \n \n \n \n \n \n \n \n \n \n " + _H.ei(_H.lf(_S.lookup("cart"), "items")) do |_i3100__| + _O << "\n " + _pa0__ = _i3100__ + _H.rolf(_O, "\n \n \n \n \n \n\n" + _O << "\n " + end + _O << "\n \n
    ProductPriceQuantityTotalActions
    \n \"",\n
    \n ", _pa0__, "title") + _H.rolf(_O, "\n

    ", _pa0__, "variant_title") + _H.rolf(_O, "

    \n

    SKU: ", _pa0__, "sku") + _O << "

    \n " + if (_H.cmp(_H.lp(_H.lf(_pa0__, "properties"), "size"), 0, :gt, _O, "cart_line_item")) + _O << "\n
    \n " + _H.ei(_H.lf(_pa0__, "properties")) do |_i3200__| + _O << "\n

    " + _H.oa(_O, _H.bl(_i3200__, 0)) + _O << ": " + _H.oa(_O, _H.bl(_i3200__, 1)) + _O << "

    \n " + end + _O << "\n
    \n " + end + _H.rolf(_O, "\n
    \n
    $", _pa0__, "price") + _H.rolf(_O, "\n \n $", _pa0__, "line_price") + _H.rolf(_O, "\n \n
    \n" + } + + __p636172745f7265636f6d6d656e646174696f6e73__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _cs = isolated ? {} : (parent_cycle_state || {}) + _O << "
    \n

    You might also like

    \n
    \n " + _H.ei(_S.lookup("products")) do |_i3700__| + _O << "\n " + _pa0__ = _i3700__ + _H.rolf(_O, "\n" + _O << "\n " + end + _O << "\n
    \n
    \n" + } + + _O = +"" + _cs = {} + + _pa0__ = _S.lookup("shop") + _H.rolf(_O, "\n\n\n ", _pa0__, "name") + _H.rolf(_O, " - Cart\n\n\n
    \n
    ", _pa0__, "name") + _O << "
    \n
    \n" + _O << "\n
    \n

    Shopping Cart

    \n " + if (_H.cmp(_H.lp(_H.lf(_S.lookup("cart"), "items"), "size"), 0, :gt, _O, nil)) + _O << "\n " + __partial_args__ = {} + __partial_args__["cart"] = _S.lookup("cart") + _H.ipc(__p636172745f7461626c65__, "cart_table", __partial_args__, _O, _S, true, 5, _cs) + _O << "\n " + _pa1__ = _S.lookup("cart") + __partial_args__ = {} + __partial_args__["cart"] = _pa1__ + __caller_scope_2__ = _S; _S = _S.isolated_with(__partial_args__) + _H.rolf(_O, "
    \n
    \n Subtotal (", _pa1__, "item_count") + _H.rolf(_O, " items):\n $", _pa1__, "total_price") + _O << "\n
    \n " + if (_H.cmp(_H.lp(_H.lf(_pa1__, "discounts"), "size"), 0, :gt, _O, "cart_totals")) + _O << "\n
    \n " + _H.ei(_H.lf(_pa1__, "discounts")) do |_i3300__| + _O << "\n
    \n " + _O << (_i3300__["title"].to_s) + _O << " (" + _O << (_i3300__["code"].to_s) + _O << "):\n -$" + _O << (_i3300__["amount"].to_s) + _O << "\n
    \n " + end + _O << "\n
    \n " + end + _O << "\n " + _H.af(_S, "final_total", _H.lf(_pa1__, "total_price")) + _O << "\n " + _H.ei(_H.lf(_pa1__, "discounts")) do |_i3300__| + _H.af(_S, "final_total", _F.minus(_S.lookup("final_total"), _i3300__["amount"])) + end + _H.roa(_O, "\n
    \n Total:\n $", _S.lookup("final_total")) + _O << "\n
    \n
    \n" + _S = __caller_scope_2__ + _O << "\n " + _O << "
    \n Continue Shopping\n \n Proceed to Checkout\n
    \n" + _O << "\n " + _pa2__ = _S.lookup("shipping_rates") + _O << "
    \n

    Shipping Estimates

    \n \n
    \n" + _O << "\n " + else + _O << "\n " + _O << "
    \n

    Your cart is empty.

    \n Start Shopping\n
    \n" + _O << "\n " + end + _O << "\n " + __partial_args__ = {} + __partial_args__["products"] = _S.lookup("recommendations") + _H.ipc(__p636172745f7265636f6d6d656e646174696f6e73__, "cart_recommendations", __partial_args__, _O, _S, true, 12, _cs) + _O << "\n
    \n" + _pa3__ = _S.lookup("shop") + _pa4__ = _S.lookup("footer_links") + _O << "\n\n\n" + _O << "\n" + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/generated/bench_theme_collection_page.rb b/benchmarks/liquid-il/generated/bench_theme_collection_page.rb new file mode 100644 index 00000000..f4caecdd --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_theme_collection_page.rb @@ -0,0 +1,223 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +# {% render 'theme_header', shop: shop %} +# {% render 'collection_hero', collection: collection %} +#
    +# {% render 'filter_sidebar', filters: filters, active_filters: active_filters %} +#
    +# {% render 'sort_bar', sort_options: sort_options, current_sort: current_sort, product_count: collection.products.size %} +# {% render 'product_grid', products: collection.products %} +# {% render 'pagination', pages: pagination %} +#
    +#
    +# {% render 'theme_footer', shop: shop, links: footer_links %} +# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchThemeCollectionPage.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchThemeCollectionPage + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + _F = LiquidIL::Filters + __p7468656d655f686561646572__ = nil + __p636f6c6c656374696f6e5f6865726f__ = nil + __p66696c7465725f73696465626172__ = nil + __p736f72745f626172__ = nil + __p636f6c6c656374696f6e5f70726f647563745f63617264__ = nil + __p70726f647563745f67726964__ = nil + __p706167696e6174696f6e__ = nil + __p7468656d655f666f6f746572__ = nil + + __p70726f647563745f67726964__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _cs = isolated ? {} : (parent_cycle_state || {}) + _O << "
    \n " + _H.ei(_S.lookup("products")) do |_i4300__| + _O << "\n " + _pa0__ = _i4300__ + _O << "
    \n \n
    \n \"",\n " + if _H.t(_H.lf(_pa0__, "compare_at_price")) + _O << "\n Sale\n " + end + _O << "\n " + unless _H.t(_H.lf(_pa0__, "available")) + _O << "\n Sold Out\n " + end + _H.rolf(_O, "\n
    \n
    \n

    ", _pa0__, "vendor") + _H.rolf(_O, "

    \n

    ", _pa0__, "title") + _O << "

    \n
    \n " + if _H.t(_H.lf(_pa0__, "compare_at_price")) + _H.rolf(_O, "\n $", _pa0__, "compare_at_price") + _O << "\n " + end + _H.rolf(_O, "\n $", _pa0__, "price") + _O << "\n
    \n
    \n " + _H.ei(_H.lf(_pa0__, "tags")) do |_i4400__| + _O << "\n " + _H.oa(_O, _i4400__) + _O << "\n " + end + _O << "\n
    \n
    \n
    \n " + if _H.t(_H.lf(_pa0__, "available")) + _H.rolf(_O, "\n \n " + end + _O << "\n
    \n" + _O << "\n " + end + _O << "\n
    \n" + } + + _O = +"" + _cs = {} + + _pa0__ = _S.lookup("shop") + _H.rolf(_O, "\n\n\n ", _pa0__, "name") + _H.rolf(_O, "\n\n\n
    \n
    ", _pa0__, "name") + _O << "
    \n
    \n" + _O << "\n" + _pa1__ = _S.lookup("collection") + _H.rolf(_O, "
    \n
    \n

    ", _pa1__, "title") + _H.rolf(_O, "

    \n

    ", _pa1__, "description") + _H.rolf(_O, "

    \n ", _pa1__, "products_count") + _O << " products\n
    \n
    \n" + _O << "\n
    \n " + _pa2__ = _S.lookup("filters") + _pa3__ = _S.lookup("active_filters") + _O << "\n" + _O << "\n
    \n " + _pa4__ = _S.lookup("sort_options") + _pa5__ = _S.lookup("current_sort") + _pa6__ = _H.lookup(_H.lookup(_S.lookup("collection"), "products"), "size") + _H.roa(_O, "
    \n ", _pa6__) + _O << " products\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n" + _O << "\n " + __partial_args__ = {} + __partial_args__["products"] = _H.lookup(_S.lookup("collection"), "products") + _H.ipc(__p70726f647563745f67726964__, "product_grid", __partial_args__, _O, _S, true, 7, _cs) + _O << "\n " + _pa7__ = _S.lookup("pagination") + __partial_args__ = {} + __partial_args__["pages"] = _pa7__ + __caller_scope_5__ = _S; _S = _S.isolated_with(__partial_args__) + _O << "\n" + _S = __caller_scope_5__ + _O << "\n
    \n
    \n" + _pa8__ = _S.lookup("shop") + _pa9__ = _S.lookup("footer_links") + _O << "\n\n\n" + _O << "\n" + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/generated/bench_theme_product_page.rb b/benchmarks/liquid-il/generated/bench_theme_product_page.rb new file mode 100644 index 00000000..7d6473aa --- /dev/null +++ b/benchmarks/liquid-il/generated/bench_theme_product_page.rb @@ -0,0 +1,272 @@ +# frozen_string_literal: true +# +# Auto-generated by LiquidIL (ruby compiler) +# Source template: +# {% render 'theme_header', shop: shop %} +# {% render 'breadcrumb_nav', crumbs: breadcrumbs %} +#
    +# {% render 'product_gallery', images: product.images %} +# {% render 'product_info', product: product, variants: variants %} +# {% render 'variant_selector', variants: variants %} +# {% render 'add_to_cart_form', product: product %} +# {% render 'product_tabs', product: product %} +# {% render 'related_grid', products: related_products, title: 'You may also like' %} +# {% render 'recently_viewed', products: recent_products %} +#
    +# {% render 'theme_footer', shop: shop, links: footer_links %} +# +# Usage: +# require_relative "this_file" +# output = LiquidILBenchBenchThemeProductPage.render({"name" => "World"}) +# + +autoload :LiquidIL, "liquid_il" unless defined?(LiquidIL) + +module LiquidILBenchBenchThemeProductPage + + PARTIAL_CONSTANTS = nil.freeze + + def self.render(assigns = {}, render_errors: true) + __scope__ = LiquidIL::Scope.new(assigns) + __scope__.render_errors = render_errors + __partial_constants__ = PARTIAL_CONSTANTS + + # Match compiler-generated local names used in proc source. + _S = __scope__ + _pc = __partial_constants__ + + _H = LiquidIL::RuntimeHelpers + _F = LiquidIL::Filters + __p6d61696e5f6e6176__ = nil + __p636172745f69636f6e__ = nil + __p7468656d655f686561646572__ = nil + __p62726561646372756d625f6e6176__ = nil + __p70726f647563745f67616c6c657279__ = nil + __p70726f647563745f696e666f__ = nil + __p76617269616e745f73656c6563746f72__ = nil + __p6164645f746f5f636172745f666f726d__ = nil + __p70726f647563745f74616273__ = nil + __p70726f647563745f63617264__ = nil + __p72656c617465645f67726964__ = nil + __p726563656e746c795f766965776564__ = nil + __p7468656d655f666f6f746572__ = nil + + __p7468656d655f686561646572__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _cs = isolated ? {} : (parent_cycle_state || {}) + _H.rolf(_O, "\n\n\n ", _S.lookup("shop"), "name") + _H.rolf(_O, "\n \n\n\n
    \n
    ", _S.lookup("shop"), "name") + _O << "
    \n " + _O << "\n" + _O << "\n " + _O << "
    \n Cart\n
    \n" + _O << "\n
    \n" + } + + __p72656c617465645f67726964__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _cs = isolated ? {} : (parent_cycle_state || {}) + _H.roa(_O, "
    \n

    ", _S.lookup("title")) + _O << "

    \n
    \n " + _H.ei(_S.lookup("products")) do |_i2600__| + _O << "\n " + _pa0__ = _i2600__ + _H.rolf(_O, "\n" + _O << "\n " + end + _O << "\n
    \n
    \n" + } + + __p726563656e746c795f766965776564__ = ->(_S, _O, isolated, caller_line: 1, parent_cycle_state: nil) { + _cs = isolated ? {} : (parent_cycle_state || {}) + _O << "
    \n

    Recently Viewed

    \n
    \n " + _H.ei(_S.lookup("products")) do |_i2800__| + _O << "\n " + _pa0__ = _i2800__ + _H.rolf(_O, "\n" + _O << "\n " + end + _O << "\n
    \n
    \n" + } + + _O = +"" + _cs = {} + + __partial_args__ = {} + __partial_args__["shop"] = _S.lookup("shop") + _H.ipc(__p7468656d655f686561646572__, "theme_header", __partial_args__, _O, _S, true, 1, _cs) + _O << "\n" + _pa0__ = _S.lookup("breadcrumbs") + _O << "\n" + _O << "\n
    \n " + _pa1__ = _H.lookup(_S.lookup("product"), "images") + _O << "
    \n " + _H.eif(_pa1__, "image-images", nil) do |_i2100__, _fl2100__| + _O << "\n
    \n \""\n
    \n " + end + _O << "\n
    \n " + _H.eif(_pa1__, "image-images", nil) do |_i2100__, _fl2100__| + _O << "\n \n " + end + _O << "\n
    \n
    \n" + _O << "\n " + _pa2__ = _S.lookup("product") + __partial_args__ = {} + __partial_args__["product"] = _pa2__ + __caller_scope_3__ = _S; _S = _S.isolated_with(__partial_args__) + _H.rolf(_O, "
    \n

    ", _pa2__, "vendor") + _H.rolf(_O, "

    \n

    ", _pa2__, "title") + _O << "

    \n
    \n " + if (_H.cmp(_H.lf(_pa2__, "compare_at_price"), _H.lf(_pa2__, "price"), :gt, _O, "product_info")) + _H.rolf(_O, "\n $", _pa2__, "compare_at_price") + _O << "\n " + end + _H.rolf(_O, "\n $", _pa2__, "price") + _O << "\n " + if (_H.cmp(_H.lf(_pa2__, "compare_at_price"), _H.lf(_pa2__, "price"), :gt, _O, "product_info")) + _O << "\n " + _H.af(_S, "savings", _F.minus(_H.lf(_pa2__, "compare_at_price"), _H.lf(_pa2__, "price"))) + _H.roa(_O, "\n Save $", _S.lookup("savings")) + _O << "\n " + end + _H.rolf(_O, "\n
    \n
    ", _pa2__, "description") + _O << "
    \n
    \n " + _H.ei(_H.lf(_pa2__, "tags")) do |_i2200__| + _O << "\n " + _H.oa(_O, _i2200__) + _O << "\n " + end + _O << "\n
    \n
    \n" + _S = __caller_scope_3__ + _O << "\n " + _pa3__ = _S.lookup("variants") + _O << "
    \n \n \n
    \n" + _O << "\n " + _pa4__ = _S.lookup("product") + _H.rolf(_O, "
    \n \n
    \n \n \n
    \n " + if _H.t(_H.lf(_pa4__, "available")) + _O << "\n \n " + else + _O << "\n \n " + end + _O << "\n
    \n" + _O << "\n " + _pa5__ = _S.lookup("product") + _O << "
    \n
    \n

    Features

    \n
      \n " + _H.ei(_H.lf(_pa5__, "features")) do |_i2500__| + _O << "\n
    • " + _H.oa(_O, _i2500__) + _O << "
    • \n " + end + _O << "\n
    \n
    \n
    \n

    Care Instructions

    \n
      \n " + _H.ei(_H.lf(_pa5__, "care")) do |_i2500__| + _O << "\n
    • " + _H.oa(_O, _i2500__) + _O << "
    • \n " + end + _O << "\n
    \n
    \n
    \n" + _O << "\n " + __partial_args__ = {} + __partial_args__["products"] = _S.lookup("related_products") + __partial_args__["title"] = "You may also like" + _H.ipc(__p72656c617465645f67726964__, "related_grid", __partial_args__, _O, _S, true, 9, _cs) + _O << "\n " + __partial_args__ = {} + __partial_args__["products"] = _S.lookup("recent_products") + _H.ipc(__p726563656e746c795f766965776564__, "recently_viewed", __partial_args__, _O, _S, true, 10, _cs) + _O << "\n
    \n" + _pa6__ = _S.lookup("shop") + _pa7__ = _S.lookup("footer_links") + _O << "\n\n\n" + _O << "\n" + + _O + + + _O + rescue LiquidIL::RuntimeError => e + raise unless render_errors + output = e.partial_output || "" + location = e.file ? "#{e.file} line #{e.line}" : "line #{e.line}" + output + "Liquid error (#{location}): #{e.message}" + rescue StandardError => e + raise unless render_errors + "Liquid error (line 1): #{LiquidIL.clean_error_message(e.message)}" + end +end diff --git a/benchmarks/liquid-il/manifest.json b/benchmarks/liquid-il/manifest.json new file mode 100644 index 00000000..2e825a12 --- /dev/null +++ b/benchmarks/liquid-il/manifest.json @@ -0,0 +1,42 @@ +[ + { + "spec": "bench_simple_product_listing", + "module": "LiquidILBenchBenchSimpleProductListing", + "ruby_bytes": 2249 + }, + { + "spec": "bench_render_with_params", + "module": "LiquidILBenchBenchRenderWithParams", + "ruby_bytes": 1684 + }, + { + "spec": "bench_render_for_loop", + "module": "LiquidILBenchBenchRenderForLoop", + "ruby_bytes": 1486 + }, + { + "spec": "bench_notification_center", + "module": "LiquidILBenchBenchNotificationCenter", + "ruby_bytes": 6449 + }, + { + "spec": "bench_ecommerce_product_page", + "module": "LiquidILBenchBenchEcommerceProductPage", + "ruby_bytes": 4510 + }, + { + "spec": "bench_theme_product_page", + "module": "LiquidILBenchBenchThemeProductPage", + "ruby_bytes": 11008 + }, + { + "spec": "bench_theme_cart_page", + "module": "LiquidILBenchBenchThemeCartPage", + "ruby_bytes": 8886 + }, + { + "spec": "bench_theme_collection_page", + "module": "LiquidILBenchBenchThemeCollectionPage", + "ruby_bytes": 9026 + } +] diff --git a/benchmarks/liquid-il/templates.yml b/benchmarks/liquid-il/templates.yml new file mode 100644 index 00000000..2e654f31 --- /dev/null +++ b/benchmarks/liquid-il/templates.yml @@ -0,0 +1,1724 @@ +# Extracted and edited from upstream tobi/liquid-il +--- +specs: +- name: bench_simple_product_listing + template: | + {% render 'header' with 'Products' as title %} + + filesystem: + header: "
    {{ title }}
    " + item: "
  • {{ item.name }}: ${{ item.price }}
  • " + environment: + products: + - name: Widget + price: 9.99 + - name: Gadget + price: 19.99 + - name: Gizmo + price: 29.99 + expected: | +
    Products
    + +- name: bench_render_with_params + template: | + {% for product in products %} + {% render 'product_card', product: product, show_price: true %} + {% endfor %} + filesystem: + product_card: | +
    +

    {{ product.name }}

    +

    {{ product.description }}

    + {% if show_price %}${{ product.price }}{% endif %} +
    + environment: + products: + - name: Widget + price: 9.99 + description: A useful widget + - name: Gadget + price: 19.99 + description: An amazing gadget + - name: Gizmo + price: 29.99 + description: A fantastic gizmo + - name: Doohickey + price: 39.99 + description: A wonderful doohickey + - name: Thingamajig + price: 49.99 + description: A marvelous thingamajig + expected: |2+ + +
    +

    Widget

    +

    A useful widget

    + $9.99 +
    + + +
    +

    Gadget

    +

    An amazing gadget

    + $19.99 +
    + + +
    +

    Gizmo

    +

    A fantastic gizmo

    + $29.99 +
    + + +
    +

    Doohickey

    +

    A wonderful doohickey

    + $39.99 +
    + + +
    +

    Thingamajig

    +

    A marvelous thingamajig

    + $49.99 +
    + + +- name: bench_render_for_loop + template: "{% render 'item' for items as item %}" + filesystem: + item: "{{ item }}{% unless forloop.last %}, {% endunless %}" + environment: + items: + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 + - 16 + - 17 + - 18 + - 19 + - 20 + expected: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 +- name: bench_notification_center + template: | +
    + {% for notification in notifications %} + {% case notification.type %} + {% when 'order' %} + {% render 'notif_order', notif: notification %} + {% when 'shipping' %} + {% render 'notif_shipping', notif: notification %} + {% when 'review' %} + {% render 'notif_review', notif: notification %} + {% when 'promo' %} + {% render 'notif_promo', notif: notification %} + {% when 'stock' %} + {% render 'notif_stock', notif: notification %} + {% endcase %} + {% endfor %} +
    + filesystem: + notif_order: "
    \n
    \U0001F4E6
    \n + \
    \n

    Order {{ notif.order_id }} {{ notif.status + }}

    \n

    {{ notif.items }} items • ${{ notif.total }}

    \n \n {% if notif.status == 'confirmed' %}\n Confirmed\n {% elsif notif.status == + 'processing' %}\n Processing\n + \ {% elsif notif.status == 'shipped' %}\n Shipped\n + \ {% endif %}\n
    \n
    \n" + notif_shipping: "
    \n
    \U0001F69A
    \n + \
    \n

    Shipment Update

    \n

    Order + {{ notif.order_id }} via {{ notif.carrier }}

    \n

    Tracking: + {{ notif.tracking }}

    \n

    Expected: {{ notif.eta }}

    \n + \ Track Package\n + \
    \n
    \n" + notif_review: | +
    +
    +
    +

    New Review for {{ notif.product }}

    +

    {{ notif.customer }} rated {{ notif.rating }}/5

    +
    "{{ notif.snippet }}"
    + View All Reviews +
    +
    + notif_promo: "
    \n
    \U0001F381
    \n + \
    \n

    {{ notif.discount }} Off!

    \n

    Use + code {{ notif.code }}

    \n

    Min. purchase + ${{ notif.min_purchase }} • Expires {{ notif.expires }}

    \n \n
    \n
    \n" + notif_stock: | +
    +
    {% if notif.status == 'back_in_stock' %}✅{% else %}⚠️{% endif %}
    +
    +

    {{ notif.product }}

    +

    SKU: {{ notif.sku }}

    + {% if notif.status == 'back_in_stock' %} +

    Back in stock! {{ notif.quantity }} available

    + {% elsif notif.status == 'low_stock' %} +

    Low stock: only {{ notif.quantity }} left

    + {% endif %} + View Product +
    +
    + environment: + notifications: + - type: order + order_id: ORD-001 + status: confirmed + total: 149.99 + items: 3 + date: '2024-01-15' + - type: shipping + order_id: ORD-002 + carrier: FedEx + tracking: '1234567890' + eta: Jan 18 + - type: review + product: Leather Jacket + customer: Alice + rating: 5 + snippet: Amazing quality! + - type: promo + code: SAVE20 + discount: 20% + expires: Jan 31 + min_purchase: 50 + - type: stock + product: Blue Widget + sku: BW-001 + status: back_in_stock + quantity: 25 + - type: order + order_id: ORD-003 + status: processing + total: 89.5 + items: 2 + date: '2024-01-16' + - type: shipping + order_id: ORD-001 + carrier: UPS + tracking: '9876543210' + eta: Jan 17 + - type: review + product: Cotton T-Shirt + customer: Bob + rating: 4 + snippet: Great fit + - type: promo + code: FREESHIP + discount: Free Shipping + expires: Feb 1 + min_purchase: 75 + - type: stock + product: Red Scarf + sku: RS-002 + status: low_stock + quantity: 3 + - type: order + order_id: ORD-004 + status: shipped + total: 299.0 + items: 1 + date: '2024-01-14' + - type: shipping + order_id: ORD-004 + carrier: USPS + tracking: '5555555555' + eta: Jan 19 + - type: review + product: Wool Sweater + customer: Carol + rating: 5 + snippet: Perfect for winter + - type: promo + code: VIP25 + discount: 25% + expires: Jan 20 + min_purchase: 100 + - type: stock + product: Green Hat + sku: GH-003 + status: back_in_stock + quantity: 50 + expected: "
    \n \n \n
    \n
    \U0001F4E6
    \n
    \n + \

    Order ORD-001 confirmed

    \n

    3 items • $149.99

    \n \n + \ \n Confirmed\n \n
    \n
    \n\n + \ \n \n \n
    \n + \
    \U0001F69A
    \n
    \n + \

    Shipment Update

    \n

    Order ORD-002 via FedEx

    \n

    Tracking: + 1234567890

    \n

    Expected: Jan 18

    \n Track Package\n
    \n
    \n\n \n \n \n + \
    \n
    \n + \
    \n

    New Review for Leather Jacket

    \n + \

    Alice rated 5/5

    \n
    \"Amazing quality!\"
    \n + \ View All Reviews\n
    \n
    \n\n + \ \n \n \n
    \n
    \U0001F381
    \n
    \n

    20% + Off!

    \n

    Use code SAVE20

    \n

    Min. + purchase $50 • Expires Jan 31

    \n \n
    \n
    \n\n \n \n \n
    \n
    \n
    \n + \

    Blue Widget

    \n

    SKU: BW-001

    \n \n

    Back in stock! 25 available

    \n \n View Product\n
    \n
    \n\n \n \n \n
    \n
    \U0001F4E6
    \n + \
    \n

    Order ORD-003 processing

    \n

    2 + items • $89.5

    \n \n \n Processing\n \n
    \n
    \n\n \n \n \n + \
    \n
    \U0001F69A
    \n + \
    \n

    Shipment Update

    \n

    Order ORD-001 + via UPS

    \n

    Tracking: 9876543210

    \n

    Expected: + Jan 17

    \n Track Package\n + \
    \n
    \n\n \n \n \n
    \n + \
    \n
    \n

    New + Review for Cotton T-Shirt

    \n

    Bob rated 4/5

    \n + \
    \"Great fit\"
    \n View + All Reviews\n
    \n
    \n\n \n \n \n
    \n
    \U0001F381
    \n
    \n + \

    Free Shipping Off!

    \n

    Use code FREESHIP

    \n + \

    Min. purchase $75 • Expires Feb 1

    \n \n
    \n
    \n\n \n \n \n + \
    \n
    ⚠️
    \n + \
    \n

    Red Scarf

    \n

    SKU: + RS-002

    \n \n

    Low stock: only 3 left

    \n + \ \n View Product\n
    \n
    \n\n + \ \n \n \n
    \n
    \U0001F4E6
    \n
    \n

    Order + ORD-004 shipped

    \n

    1 items • $299.0

    \n \n + \ \n Shipped\n \n
    \n
    \n\n + \ \n \n \n
    \n + \
    \U0001F69A
    \n
    \n + \

    Shipment Update

    \n

    Order ORD-004 via USPS

    \n

    Tracking: + 5555555555

    \n

    Expected: Jan 19

    \n Track Package\n
    \n
    \n\n \n \n \n + \
    \n
    \n + \
    \n

    New Review for Wool Sweater

    \n

    Carol rated 5/5

    \n
    \"Perfect for winter\"
    \n + \ View All Reviews\n
    \n
    \n\n + \ \n \n \n
    \n
    \U0001F381
    \n
    \n

    25% + Off!

    \n

    Use code VIP25

    \n

    Min. + purchase $100 • Expires Jan 20

    \n \n
    \n
    \n\n \n \n \n
    \n
    \n
    \n + \

    Green Hat

    \n

    SKU: GH-003

    \n \n

    Back in stock! 50 available

    \n \n View Product\n
    \n
    \n\n \n \n
    \n" +- name: bench_ecommerce_product_page + template: | + {% render 'page_header', page_title: product.name %} + {% render 'breadcrumbs', items: breadcrumbs %} + {% render 'product_detail', product: product %} + {% render 'product_reviews', reviews: reviews %} + {% render 'related_products', products: related %} + {% render 'page_footer' %} + filesystem: + page_header: | +
    +

    {{ page_title }}

    +
    + breadcrumbs: | + + product_detail: | +
    +

    {{ product.name }}

    +

    ${{ product.price }}

    +

    {{ product.description }}

    +

    SKU: {{ product.sku }}

    + {% if product.in_stock %} +

    In Stock

    + {% else %} +

    Out of Stock

    + {% endif %} + +
    + product_reviews: | +
    +

    Customer Reviews

    + {% for review in reviews %} + {% render 'review_card', review: review %} + {% endfor %} +
    + review_card: | +
    + {{ review.author }} + {{ review.rating }}/5 +

    {{ review.text }}

    +
    + related_products: | + + page_footer: | + + environment: + product: + name: Premium Widget + price: 99.99 + description: The finest widget money can buy + sku: WDG-001 + in_stock: true + features: + - Durable + - Lightweight + - Eco-friendly + breadcrumbs: + - label: Home + url: "/" + - label: Widgets + url: "/widgets" + - label: Premium Widget + url: + reviews: + - author: Alice + rating: 5 + text: Amazing! + - author: Bob + rating: 4 + text: Very good + - author: Carol + rating: 5 + text: Love it + related: + - name: Basic Widget + price: 29.99 + - name: Super Widget + price: 149.99 + - name: Widget Pro + price: 199.99 + expected: "
    \n

    Premium Widget

    \n
    \n\n\n\n
    \n

    Premium Widget

    \n

    $99.99

    \n + \

    The finest widget money can buy

    \n

    SKU: + WDG-001

    \n \n

    In Stock

    \n \n \n
    \n\n
    \n

    Customer Reviews

    \n + \ \n
    \n Alice\n 5/5\n + \

    Amazing!

    \n
    \n\n \n
    \n Bob\n + \ 4/5\n

    Very good

    \n
    \n\n \n
    \n Carol\n 5/5\n + \

    Love it

    \n
    \n\n \n
    \n\n
    \n

    Related + Products

    \n \n
    \n\n\n\n" +- name: bench_theme_product_page + template: | + {% render 'theme_header', shop: shop %} + {% render 'breadcrumb_nav', crumbs: breadcrumbs %} +
    + {% render 'product_gallery', images: product.images %} + {% render 'product_info', product: product, variants: variants %} + {% render 'variant_selector', variants: variants %} + {% render 'add_to_cart_form', product: product %} + {% render 'product_tabs', product: product %} + {% render 'related_grid', products: related_products, title: 'You may also like' %} + {% render 'recently_viewed', products: recent_products %} +
    + {% render 'theme_footer', shop: shop, links: footer_links %} + filesystem: + theme_header: | + + + + {{ shop.name }} + + + + + main_nav: | + + cart_icon: | +
    + Cart +
    + breadcrumb_nav: | + + product_gallery: | + + product_info: | +
    +

    {{ product.vendor }}

    +

    {{ product.title }}

    +
    + {% if product.compare_at_price > product.price %} + ${{ product.compare_at_price }} + {% endif %} + ${{ product.price }} + {% if product.compare_at_price > product.price %} + {% assign savings = product.compare_at_price | minus: product.price %} + Save ${{ savings }} + {% endif %} +
    +
    {{ product.description }}
    +
    + {% for tag in product.tags %} + {{ tag }} + {% endfor %} +
    +
    + variant_selector: | +
    + + +
    + add_to_cart_form: | +
    + +
    + + +
    + {% if product.available %} + + {% else %} + + {% endif %} +
    + product_tabs: | +
    +
    +

    Features

    + +
    +
    +

    Care Instructions

    + +
    +
    + related_grid: | + + product_card: | +
    + + {{ product.title }} +

    {{ product.title }}

    +

    ${{ product.price }}

    +
    +
    + recently_viewed: | +
    +

    Recently Viewed

    +
    + {% for product in products %} + {% render 'product_card', product: product %} + {% endfor %} +
    +
    + theme_footer: | + + + + environment: + shop: + name: Acme Store + currency: USD + email: support@acme.test + product: + id: 12345 + title: Classic Leather Jacket + vendor: Acme Apparel + type: Outerwear + price: 299.99 + compare_at_price: 399.99 + description: Premium quality genuine leather jacket with satin lining. Features + two front pockets and an interior pocket. + sku: LJ-BLK-M + available: true + tags: + - leather + - jacket + - outerwear + - premium + images: + - src: "/images/jacket-front.jpg" + alt: Front view + - src: "/images/jacket-back.jpg" + alt: Back view + - src: "/images/jacket-detail.jpg" + alt: Detail view + - src: "/images/jacket-pocket.jpg" + alt: Pocket detail + options: + - name: Size + values: + - S + - M + - L + - XL + - name: Color + values: + - Black + - Brown + - Navy + features: + - 100% genuine leather + - Satin lining + - YKK zippers + - Two exterior pockets + - One interior pocket + care: + - Dry clean only + - Store on padded hanger + - Keep away from heat + variants: + - id: 1 + title: S / Black + price: 299.99 + available: true + sku: LJ-BLK-S + - id: 2 + title: M / Black + price: 299.99 + available: true + sku: LJ-BLK-M + - id: 3 + title: L / Black + price: 299.99 + available: false + sku: LJ-BLK-L + - id: 4 + title: XL / Black + price: 299.99 + available: true + sku: LJ-BLK-XL + - id: 5 + title: S / Brown + price: 299.99 + available: true + sku: LJ-BRN-S + - id: 6 + title: M / Brown + price: 299.99 + available: true + sku: LJ-BRN-M + - id: 7 + title: L / Brown + price: 299.99 + available: true + sku: LJ-BRN-L + - id: 8 + title: XL / Brown + price: 299.99 + available: false + sku: LJ-BRN-XL + breadcrumbs: + - title: Home + url: "/" + - title: Apparel + url: "/collections/apparel" + - title: Outerwear + url: "/collections/outerwear" + - title: Classic Leather Jacket + url: + related_products: + - title: Bomber Jacket + price: 249.99 + image: "/images/bomber.jpg" + url: "/products/bomber" + - title: Denim Jacket + price: 149.99 + image: "/images/denim.jpg" + url: "/products/denim" + - title: Suede Jacket + price: 279.99 + image: "/images/suede.jpg" + url: "/products/suede" + - title: Windbreaker + price: 99.99 + image: "/images/wind.jpg" + url: "/products/windbreaker" + recent_products: + - title: Wool Sweater + price: 89.99 + image: "/images/sweater.jpg" + url: "/products/sweater" + - title: Cotton T-Shirt + price: 29.99 + image: "/images/tshirt.jpg" + url: "/products/tshirt" + footer_links: + - title: About + url: "/about" + - title: Contact + url: "/contact" + - title: Shipping + url: "/shipping" + - title: Returns + url: "/returns" + expected: "\n\n\n Acme Store\n \n\n\n
    \n
    Acme Store
    \n \n\n + \
    \n Cart\n
    \n\n
    \n\n\n\n
    \n
    \n \n
    \n \"Front\n
    \n + \ \n
    \n \"Back\n
    \n \n
    \n + \ \"Detail\n + \
    \n \n
    \n \"Pocket\n
    \n \n
    \n + \ \n \n \n \n + \ \n \n \n \n + \ \n
    \n
    \n\n
    \n

    Acme + Apparel

    \n

    Classic Leather Jacket

    \n
    \n + \ \n $399.99\n \n $299.99\n + \ \n \n Save $100.0\n \n
    \n + \
    Premium quality genuine leather jacket with satin + lining. Features two front pockets and an interior pocket.
    \n
    \n + \ \n leather\n \n jacket\n + \ \n outerwear\n \n premium\n + \ \n
    \n
    \n\n
    \n \n \n
    \n\n
    \n \n + \
    \n \n \n
    \n + \ \n \n \n
    \n\n + \
    \n
    \n + \

    Features

    \n
      \n \n
    • 100% genuine leather
    • \n + \ \n
    • Satin lining
    • \n \n
    • YKK zippers
    • \n + \ \n
    • Two exterior pockets
    • \n \n
    • One interior + pocket
    • \n \n
    \n
    \n
    \n + \

    Care Instructions

    \n
      \n \n
    • Dry clean only
    • \n + \ \n
    • Store on padded hanger
    • \n \n
    • Keep away + from heat
    • \n \n
    \n
    \n
    \n\n
    \n + \

    You may also like

    \n \n
    \n\n + \
    \n

    Recently Viewed

    \n
    \n + \ \n \n\n \n \n\n + \ \n
    \n
    \n\n
    \n\n\n\n\n" +- name: bench_theme_cart_page + template: | + {% render 'theme_header', shop: shop %} +
    +

    Shopping Cart

    + {% if cart.items.size > 0 %} + {% render 'cart_table', cart: cart %} + {% render 'cart_totals', cart: cart %} + {% render 'cart_actions' %} + {% render 'shipping_calculator', rates: shipping_rates %} + {% else %} + {% render 'empty_cart' %} + {% endif %} + {% render 'cart_recommendations', products: recommendations %} +
    + {% render 'theme_footer', shop: shop, links: footer_links %} + filesystem: + theme_header: | + + + + {{ shop.name }} - Cart + + + + cart_table: | + + + + + + + + + + + + {% for item in cart.items %} + {% render 'cart_line_item', item: item %} + {% endfor %} + +
    ProductPriceQuantityTotalActions
    + cart_line_item: | + + + {{ item.title }} +
    + {{ item.title }} +

    {{ item.variant_title }}

    +

    SKU: {{ item.sku }}

    + {% if item.properties.size > 0 %} +
    + {% for prop in item.properties %} +

    {{ prop[0] }}: {{ prop[1] }}

    + {% endfor %} +
    + {% endif %} +
    + + ${{ item.price }} + + + + ${{ item.line_price }} + + + + + cart_totals: | +
    +
    + Subtotal ({{ cart.item_count }} items): + ${{ cart.total_price }} +
    + {% if cart.discounts.size > 0 %} +
    + {% for discount in cart.discounts %} +
    + {{ discount.title }} ({{ discount.code }}): + -${{ discount.amount }} +
    + {% endfor %} +
    + {% endif %} + {% assign final_total = cart.total_price %} + {% for discount in cart.discounts %} + {% assign final_total = final_total | minus: discount.amount %} + {% endfor %} +
    + Total: + ${{ final_total }} +
    +
    + cart_actions: | +
    + Continue Shopping + + Proceed to Checkout +
    + shipping_calculator: | +
    +

    Shipping Estimates

    + +
    + empty_cart: | +
    +

    Your cart is empty.

    + Start Shopping +
    + cart_recommendations: | +
    +

    You might also like

    +
    + {% for product in products %} + {% render 'product_card', product: product %} + {% endfor %} +
    +
    + product_card: | +
    + + {{ product.title }} +

    {{ product.title }}

    +

    ${{ product.price }}

    +
    +
    + theme_footer: | + + + + environment: + shop: + name: Acme Store + currency: USD + email: support@acme.test + cart: + item_count: 5 + total_price: 549.95 + items: + - id: 1 + title: Classic Leather Jacket + variant_title: M / Black + price: 299.99 + quantity: 1 + line_price: 299.99 + image: "/images/jacket.jpg" + url: "/products/jacket" + sku: LJ-BLK-M + properties: + gift_wrap: 'Yes' + gift_message: Happy Birthday! + - id: 2 + title: Cotton T-Shirt + variant_title: L / White + price: 29.99 + quantity: 3 + line_price: 89.97 + image: "/images/tshirt.jpg" + url: "/products/tshirt" + sku: TS-WHT-L + properties: {} + - id: 3 + title: Wool Beanie + variant_title: One Size / Navy + price: 24.99 + quantity: 2 + line_price: 49.98 + image: "/images/beanie.jpg" + url: "/products/beanie" + sku: BN-NVY-OS + properties: {} + - id: 4 + title: Leather Belt + variant_title: 34 / Brown + price: 59.99 + quantity: 1 + line_price: 59.99 + image: "/images/belt.jpg" + url: "/products/belt" + sku: BT-BRN-34 + properties: {} + - id: 5 + title: Silk Scarf + variant_title: One Size / Burgundy + price: 49.99 + quantity: 1 + line_price: 49.99 + image: "/images/scarf.jpg" + url: "/products/scarf" + sku: SC-BUR-OS + properties: + monogram: ABC + discounts: + - code: SAVE10 + amount: 54.99 + title: 10% Off + shipping_rates: + - name: Standard Shipping + price: 5.99 + delivery: 5-7 business days + - name: Express Shipping + price: 15.99 + delivery: 2-3 business days + - name: Overnight + price: 29.99 + delivery: Next business day + recommendations: + - title: Leather Gloves + price: 49.99 + image: "/images/gloves.jpg" + url: "/products/gloves" + - title: Wool Socks + price: 19.99 + image: "/images/socks.jpg" + url: "/products/socks" + - title: Sunglasses + price: 79.99 + image: "/images/sunglasses.jpg" + url: "/products/sunglasses" + footer_links: + - title: About + url: "/about" + - title: Contact + url: "/contact" + - title: Shipping + url: "/shipping" + - title: Returns + url: "/returns" + expected: "\n\n\n Acme Store - Cart\n\n\n + \
    \n
    Acme Store
    \n
    \n\n
    \n

    Shopping Cart

    \n \n \n + \ \n \n \n \n \n + \ \n \n \n \n \n + \ \n \n \n \n + \ \n \n \n\n\n \n \n \n \n + \ \n \n \n\n\n \n \n \n + \ \n \n \n + \ \n\n\n \n \n \n \n \n + \ \n \n\n\n \n \n \n \n + \ \n \n \n\n\n \n \n
    ProductPriceQuantityTotalActions
    \n + \ \"Classic\n + \
    \n Classic Leather + Jacket\n

    M / Black

    \n

    SKU: + LJ-BLK-M

    \n \n
    \n \n

    gift_wrap: Yes

    \n \n

    gift_message: Happy Birthday!

    \n \n + \
    \n \n
    \n
    $299.99\n \n + \ $299.99\n \n
    \n \"Cotton\n
    \n Cotton T-Shirt\n

    L / White

    \n + \

    SKU: TS-WHT-L

    \n \n
    \n
    $29.99\n \n + \ $89.97\n \n
    \n \"Wool\n
    \n Wool Beanie\n

    One Size + / Navy

    \n

    SKU: BN-NVY-OS

    \n \n
    \n
    $24.99\n \n $49.98\n \n + \
    \n + \ \"Leather\n + \
    \n Leather Belt\n + \

    34 / Brown

    \n

    SKU: BT-BRN-34

    \n + \ \n
    \n
    $59.99\n + \ \n $59.99\n \n
    \n \"Silk\n
    \n Silk Scarf\n

    One Size / + Burgundy

    \n

    SKU: SC-BUR-OS

    \n \n
    \n \n

    monogram: + ABC

    \n \n
    \n \n
    \n
    $49.99\n \n + \ $49.99\n \n
    \n\n + \
    \n
    \n Subtotal + (5 items):\n $549.95\n
    \n \n
    \n + \ \n
    \n 10% Off (SAVE10):\n + \ -$54.99\n
    \n \n
    \n \n \n \n + \
    \n Total:\n $494.96\n
    \n
    \n\n + \
    \n Continue + Shopping\n \n Proceed to Checkout\n
    \n\n
    \n + \

    Shipping Estimates

    \n \n
    \n\n \n
    \n + \

    You might also like

    \n \n
    \n\n
    \n\n\n\n\n" +- name: bench_theme_collection_page + template: | + {% render 'theme_header', shop: shop %} + {% render 'collection_hero', collection: collection %} +
    + {% render 'filter_sidebar', filters: filters, active_filters: active_filters %} +
    + {% render 'sort_bar', sort_options: sort_options, current_sort: current_sort, product_count: collection.products.size %} + {% render 'product_grid', products: collection.products %} + {% render 'pagination', pages: pagination %} +
    +
    + {% render 'theme_footer', shop: shop, links: footer_links %} + filesystem: + theme_header: | + + + + {{ shop.name }} + + + + collection_hero: | +
    +
    +

    {{ collection.title }}

    +

    {{ collection.description }}

    + {{ collection.products_count }} products +
    +
    + filter_sidebar: | + + sort_bar: | +
    + {{ product_count }} products +
    + + +
    +
    + + +
    +
    + product_grid: | +
    + {% for product in products %} + {% render 'collection_product_card', product: product %} + {% endfor %} +
    + collection_product_card: | +
    + +
    + {{ product.title }} + {% if product.compare_at_price %} + Sale + {% endif %} + {% unless product.available %} + Sold Out + {% endunless %} +
    +
    +

    {{ product.vendor }}

    +

    {{ product.title }}

    +
    + {% if product.compare_at_price %} + ${{ product.compare_at_price }} + {% endif %} + ${{ product.price }} +
    +
    + {% for tag in product.tags %} + {{ tag }} + {% endfor %} +
    +
    +
    + {% if product.available %} + + {% endif %} +
    + pagination: | + + theme_footer: | + + + + environment: + shop: + name: Acme Store + currency: USD + email: support@acme.test + collection: + title: Men's Outerwear + description: Premium jackets and coats for every season + image: "/images/outerwear-hero.jpg" + products_count: 48 + products: + - id: 1 + title: Classic Leather Jacket + price: 299.99 + compare_at_price: 399.99 + image: "/images/leather.jpg" + url: "/products/leather-jacket" + vendor: Acme Apparel + available: true + tags: + - leather + - classic + - id: 2 + title: Bomber Jacket + price: 249.99 + compare_at_price: + image: "/images/bomber.jpg" + url: "/products/bomber" + vendor: Acme Apparel + available: true + tags: + - bomber + - casual + - id: 3 + title: Denim Jacket + price: 149.99 + compare_at_price: 179.99 + image: "/images/denim.jpg" + url: "/products/denim-jacket" + vendor: Acme Apparel + available: true + tags: + - denim + - casual + - id: 4 + title: Wool Peacoat + price: 349.99 + compare_at_price: + image: "/images/peacoat.jpg" + url: "/products/peacoat" + vendor: Acme Apparel + available: false + tags: + - wool + - formal + - id: 5 + title: Quilted Vest + price: 129.99 + compare_at_price: + image: "/images/vest.jpg" + url: "/products/vest" + vendor: Acme Apparel + available: true + tags: + - vest + - layering + - id: 6 + title: Windbreaker + price: 99.99 + compare_at_price: 129.99 + image: "/images/windbreaker.jpg" + url: "/products/windbreaker" + vendor: Acme Apparel + available: true + tags: + - light + - athletic + - id: 7 + title: Parka + price: 399.99 + compare_at_price: + image: "/images/parka.jpg" + url: "/products/parka" + vendor: Acme Apparel + available: true + tags: + - winter + - heavy + - id: 8 + title: Suede Jacket + price: 279.99 + compare_at_price: 329.99 + image: "/images/suede.jpg" + url: "/products/suede-jacket" + vendor: Premium Line + available: true + tags: + - suede + - premium + - id: 9 + title: Rain Jacket + price: 149.99 + compare_at_price: + image: "/images/rain.jpg" + url: "/products/rain-jacket" + vendor: Acme Apparel + available: true + tags: + - waterproof + - light + - id: 10 + title: Down Jacket + price: 279.99 + compare_at_price: + image: "/images/down.jpg" + url: "/products/down-jacket" + vendor: Acme Apparel + available: true + tags: + - winter + - warm + - id: 11 + title: Fleece Jacket + price: 89.99 + compare_at_price: + image: "/images/fleece.jpg" + url: "/products/fleece" + vendor: Acme Apparel + available: true + tags: + - fleece + - casual + - id: 12 + title: Blazer + price: 199.99 + compare_at_price: 249.99 + image: "/images/blazer.jpg" + url: "/products/blazer" + vendor: Premium Line + available: true + tags: + - formal + - business + filters: + - name: Price + values: + - label: Under $100 + value: 0-100 + count: 2 + - label: "$100-$200" + value: 100-200 + count: 4 + - label: "$200-$300" + value: 200-300 + count: 4 + - label: Over $300 + value: 300- + count: 2 + - name: Size + values: + - label: S + value: s + count: 10 + - label: M + value: m + count: 12 + - label: L + value: l + count: 11 + - label: XL + value: xl + count: 9 + - name: Color + values: + - label: Black + value: black + count: 6 + - label: Brown + value: brown + count: 4 + - label: Navy + value: navy + count: 3 + - label: Gray + value: gray + count: 2 + - name: Availability + values: + - label: In Stock + value: 'true' + count: 11 + - label: Out of Stock + value: 'false' + count: 1 + active_filters: + - 100-200 + sort_options: + - label: Featured + value: featured + - label: 'Price: Low to High' + value: price-asc + - label: 'Price: High to Low' + value: price-desc + - label: Newest + value: created-desc + - label: Best Selling + value: best-selling + current_sort: featured + pagination: + current: 1 + total: 4 + pages: + - 1 + - 2 + - 3 + - 4 + footer_links: + - title: About + url: "/about" + - title: Contact + url: "/contact" + - title: Shipping + url: "/shipping" + - title: Returns + url: "/returns" + expected: "\n\n\n Acme Store\n\n\n + \
    \n
    Acme Store
    \n
    \n\n
    \n + \
    \n

    Men's Outerwear

    \n

    Premium jackets + and coats for every season

    \n 48 products\n + \
    \n
    \n\n
    \n \n\n
    \n + \
    \n 12 products\n + \
    \n \n \n
    \n
    \n \n + \ \n
    \n
    \n\n
    \n + \ \n \n\n \n \n\n \n \n\n + \ \n \n\n \n \n\n \n \n\n + \ \n \n\n \n \n\n + \ \n \n\n \n \n\n \n \n\n \n \n\n \n
    \n\n \n\n + \
    \n
    \n\n\n\n\n"