Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Template Engines in Ruby

Template Engines in Ruby

Slides for RubyConf 2014 talk "Template Engines in Ruby" http://rubyconf.org/program#prop_812

Akira Matsuda

November 19, 2014
Tweet

More Decks by Akira Matsuda

Other Decks in Programming

Transcript

  1. http://rubykaigi.org/2014 The slides are available from the "SCHEDULE" link The

    videos are available from the "SCHEDULE" link Check 'em out!
  2. eRuby Stands for "Embedded Ruby" (≠ mruby) The spec for

    embedding Ruby code into a text (mainly HTML file) Created by Matz & Shugo Maeda (@shugomaeda), who is Matz's boss at their company Inspired by ePerl spec
  3. eruby The referential implementation of eRuby, written in C This

    eruby has a small "r", just as Ruby (= language) and ruby(= implementation) Created by Shugo Maeda No more maintained :<
  4. ERB

  5. ERB A Pure Ruby implementation of eRuby A ruby stdlib

    Created by Masatoshi Seki (@m_seki), a legendary Ruby guru Well documented (particularly in Japanese)
  6. Erb? ERb? No, ERB! I see so many mistakes in

    books/ articles/documantations It used to be called ERb/ERbLight, then renamed to ERB when becoming a ruby standard library
  7. This includes documentation, comments % grep "^ *#" erb.rb |

    wc -l #=> 517 So, the code is actually less than 500 lines
  8. Embedding Ruby code puts ERB.new('1 + 1 = <%= 1

    + 1 %>').result #=> "1 + 1 = 2"
  9. ERB#initialize class ERB def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout') ... compiler

    = make_compiler(trim_mode) set_eoutvar(compiler, eoutvar) @src, @enc = *compiler.compile(str) ... ennd
  10. ERB::Compiler#compile class ERB class Compiler def compile(s) ... out =

    Buffer.new(self, enc) scanner = make_scanner(s) scanner.scan do |token| case token when '<%', '<%=', '<%#' add_put_cmd(out, content) if content.size > 0 ... return out.script, enc ennnd
  11. ERB::Compiler#compile Prepares a buffer Scans through the template, splits the

    template into tokens, then executes a command for each token
  12. add_put_cmd Pushes a Ruby code String into the buffer @put_cmd

    => method content_dump(content) => params
  13. What is @put_cmd? class ERB def set_eoutvar(compiler, eoutvar = '_erbout')

    compiler.put_cmd = "#{eoutvar}.concat" compiler.insert_cmd = "#{eoutvar}.concat" compiler.pre_cmd = ["#{eoutvar} = ''"] compiler.post_cmd = ["#{eoutvar}.force_encoding(__ENCODING__)"] ennd
  14. So, add_put_cmd(out, content) is equivalent to something like this out

    = Buffer.new(self, Encoding::UTF_8) out.push("_erbout.concat 'Hello'.dump")
  15. ERB internally executes something like this _erbout = "" buf

    = ERB::Compiler::Buffer.new(ERB::Comp iler.new(nil)) buf.push("_erbout.concat 'Hello'.dump") buf.close puts eval(buf.script)
  16. ActionView::Template#ren der module ActionView class Template def render(view, locals, buffer=nil,

    &block) ... compile!(view) view.send(method_name, locals, buffer, &block) ... ennnd
  17. ActionView::Template#co mpile! module ActionView class Template def compile!(view) return if

    @compiled ... mod = view.singleton_class ... compile(mod) ... @compiled = true ... ennnd
  18. ActionView::Template#co mpile module ActionView class Template def compile(mod) ... method_name

    = self.method_name code = @handler.call(self) source = <<-end_src def #{method_name}(local_assigns, output_buffer) _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} ensure @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer end end_src ... mod.module_eval(source, identifier, 0) ... ennnd
  19. ActionView::Template::Handlers::ERB# call module ActionView class Template module Handlers class ERB

    def call(template) ... erb = template_source.gsub(ENCODING_TAG, '') ... self.class.erb_implementation.new( erb, :escape => (self.class.escape_whitelist.include? template.type), :trim => (self.class.erb_trim_mode == "-") ).src ennnnnd
  20. What ActionView render does ActionView defines a Ruby method on

    the view object The method body is generated by ERB.new(template).src
  21. Short summary Templates are compiled to be Ruby methods Only

    for the first execution of the template So, compile overhead is not at all a big deal What really matters is performance of the generated ruby code
  22. ActionView::Template::Handlers::ERB# call module ActionView class Template module Handlers class ERB

    def call(template) ... erb = template_source.gsub(ENCODING_TAG, '') ... self.class.erb_implementation.new( erb, :escape => (self.class.escape_whitelist.include? template.type), :trim => (self.class.erb_trim_mode == "-") ).src ennnnnd
  23. ActionView::Template::Handlers::ERB.erb _implementation module ActionView class Template module Handlers class ERB

    ... class_attribute :erb_implementation self.erb_implementation = Erubis ... ennnnd
  24. Erubis Yet another Pure Ruby implementation of eRuby A ruby

    gem Created by Makoto Kuwata (@makotokuwata) https://github.com/kwatch/erubis
  25. According to its README "Very fast, almost three times faster

    than ERB" "Auto escaping support" "Ruby on Rails support" ... and so on
  26. I said Erubis is a yet another implementation of eRuby,

    but, Both ERB and Erubis are eRuby implementations #=> false Erubis is ERB compatible, plus some more original features #=> true
  27. action_view/template/ handlers/erb.rb module ActionView class Template module Handlers class ERB

    ... class_attribute :erb_implementation - self.erb_implementation = Erubis + self.erb_implementation = ::ERB ... ennnnd
  28. The ActionView tests do not pass! :bomb: % bundle ex

    rake 1530 runs, 3434 assertions, 17 failures, 29 errors, 1 skips
  29. We've got so many errors like this... SyntaxError: test template:2:

    syntax error, unexpected ')' ...ple.com', :method => :put) do ).to_s); _erbout.concat "Hello...
  30. Do you remember that it had to be like this

    in Rails 1.x and 2.x? <% form_for @article do |f| %> ... <% end %>
  31. actionpack/lib/action_view/ template/handlers/erb.rb require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/string/output_safety' +require 'erubis' module ActionView

    module TemplateHandlers + class Erubis < ::Erubis::Eruby + def add_preamble(src) + src << "@output_buffer = ActionView::SafeBuffer.new;" + end + + def add_text(src, text) + src << "@output_buffer << ('" << escape_text(text) << "'.html_safe!);" + end + ... + end + class ERB < TemplateHandler ... def compile(template) - require 'erb' - magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/ erb = "#{magic}<% __in_erb_template=true %>#{template.source}" - ::ERB.new(erb, nil, erb_trim_mode, '@output_buffer').src + Erubis.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src ennnnnd
  32. Do you remember that Rails templates were not html_safe by

    default before this change? We were required to html_escape everything manually like this <%=h @user.name %>
  33. actionpack/lib/action_view/ template/handlers/erb.rb module ActionView module TemplateHandlers class ERB < TemplateHandler

    ... + cattr_accessor :erubis_implementation + self.erubis_implementation = Erubis def compile(template) magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+) \s*-?%>)/ erb = "#{magic}<% __in_erb_template=true %>#{template.source}" - Erubis.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src ennnnd
  34. actionpack/lib/action_view/ template/handlers/erb.rb module ActionView module TemplateHandlers class ERB < TemplateHandler

    ... - cattr_accessor :erubis_implementation - self.erubis_implementation = Erubis + cattr_accessor :erb_implementation + self.erb_implementation = Erubis def compile(template) ... - result = self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + result = self.class.erb_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src ... ennnnd
  35. So, Rails' default template handler is Erubis, not ERB Because

    Erubis has some original extensions in addition to eRuby, and the Rails team liked it We cannot switch erb_implementation back to ERB
  36. I noticed this 2 years ago, And asked Seki-san if

    we could extend ERB to be "Rails compatible" before Ruby 2.0 release
  37. Firstly he tried to add some extension points to ERB

    git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/ trunk@38186 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 5 +++++ lib/erb.rb | 36 ++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-)
  38. But gave up. Because he didn't like this part in

    ActionView + Erubis BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ This code scans the template with the Regexp and detects the Ruby block, but this kind of code could be imperfect So this is not acceptable as an ERB spec, said Seki- san.
  39. So what is BLOCK_EXPR? And why is this problematic? Let's

    carefully take a look at the Regexp.
  40. action_view/template/ handlers/erb.rb module ActionView class Template module Handlers class Erubis

    < ::Erubis::Eruby ... BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ def add_expr_literal(src, code) flush_newline_if_pending(src) if code =~ BLOCK_EXPR src << '@output_buffer.append= ' << code else src << '@output_buffer.append=(' << code << ');' end ennnnnd
  41. BLOCK_EXPR The Regexp scans the given String token and detects

    if the token opens a block or not. <%= form_tag('http:// www.example.com') do %> This does match the expression.
  42. How about these blocks? BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ p BLOCK_EXPR =~

    "foo('bar') do" p BLOCK_EXPR =~ "foo('bar') {" p BLOCK_EXPR =~ "foo('bar')do" p BLOCK_EXPR =~ "foo('bar'){"
  43. OMG BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ p BLOCK_EXPR =~ "foo('bar') do" #=>

    matches p BLOCK_EXPR =~ "foo('bar') {" #=> matches p BLOCK_EXPR =~ "foo('bar')do" #=> does not match! p BLOCK_EXPR =~ "foo('bar'){" #=> does not match!
  44. I think I found a bug form_for('foo'){...} is a valid

    Ruby code that calls a method with a block But ActionView doesn't regard this as a block And causes a syntax error
  45. But this should be the fix, obviously. -BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/

    +BLOCK_EXPR = /\s*(do|\{)(\s*\|[^|]*\|)?\s*\Z/ p BLOCK_EXPR =~ "foo('bar') do" #=> matches p BLOCK_EXPR =~ "foo('bar') {" #=> matches p BLOCK_EXPR =~ "foo('bar')do" #=> matches p BLOCK_EXPR =~ "foo('bar'){" #=> matches
  46. Not really :< -BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ +BLOCK_EXPR = /\s*(do|\{)(\s*\|[^|]*\|)?\s*\Z/ p

    BLOCK_EXPR =~ "foo('bar') do" #=> matches p BLOCK_EXPR =~ "foo('bar') {" #=> matches p BLOCK_EXPR =~ "foo('bar')do" #=> matches p BLOCK_EXPR =~ "foo('bar'){" #=> matches p BLOCK_EXPR =~ "@todo" #=> matches!
  47. If I change the BLOCK_EXPR to /\s*(do|\{)(\s*\|[^|]*\|)?\s*\Z/ <%= @todo %>

    Now ActionView regards this code as a block call!
  48. So, The block detection code /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ in ActionView fails to

    detect some valid Ruby block calls I would fix this particular case Maybe like this? /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ But essentially, this approach is wrong! Having such a Ruby parsing code other than the Ruby parser would be a cause of such a weird bug
  49. That is why this syntax is not acceptable in ERB

    <%= form_for @article do |f| %> ... <% end %>
  50. Anyway, "ERB or Erubis" is not a big deal We

    don't use neither of them after all :trollface:
  51. Generated Ruby code puts Haml::Engine.new("= 'Hello!'").compiler.precompiled_with_ambles([]) #=> begin;extend Haml::Helpers;_hamlout =

    @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base", "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>false, :format=>:html5, :encoding=>"UTF-8", :escape_html=>false, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.format_script_false_false_fals e_false_false_true_false(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  52. Haml puts Haml::Engine.new("= 'Hello!'").compiler.precompiled_with_ambles([]) #=> begin;extend Haml::Helpers;_hamlout = @haml_buffer =

    Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base", "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>false, :format=>:html5, :encoding=>"UTF-8", :escape_html=>false, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.format_script_false_false_fals e_false_false_true_false(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  53. But seriously, We don't usually have to care what is

    executed behind But isn't this too much? It apparently looks slow...
  54. I said, compilation overhead doesn't matter But the generated Ruby

    code DOES matter Yes, it's time to benchmark
  55. Benchmarking the method call require 'erb' require 'haml' require 'benchmark/ips'

    obj = Object.new erb_src = ERB.new("<%= 'Hello!' %>").src haml_src = Haml::Engine.new("= 'Hello!'").compiler.precompiled_with_ambles([]) obj.singleton_class.module_eval("def erb() #{erb_src}; end") obj.singleton_class.module_eval("def haml() #{haml_src}; end") Benchmark.ips do |x| x.report('erb') do obj.erb end x.report('haml') do obj.haml end end
  56. Result Greeting "Hello!" in Haml is 26x slower than ERB

    But this is just a micro benchmark
  57. As a little bit more realistic benchmark Let's see actual

    rendering speed via ActionView I'm going to skip the template file path resolution which is supposed to be heavier than printing "Hello!"
  58. Directly `render`ing the template require 'erb' require 'haml' require 'action_view'

    require 'action_controller' require 'benchmark/ips' require 'haml/template' view = Class.new(ActionView::Base).new('.') erb_handler = ActionView::Template.handler_for_extension('erb') erb_template = ActionView::Template.new("<%= 'Hello!' %>", File.expand_path('hello.erb'), erb_handler, {}) # discarding the first execution that runs the template compilation puts erb_template.render(view, {}) haml_handler = ActionView::Template.handler_for_extension('haml') haml_template = ActionView::Template.new("= 'Hello!'", File.expand_path('hello.haml'), haml_handler, {}) puts haml_template.render(view, {}) Benchmark.ips do |x| x.report('erb') do erb_template.render(view, {}) end x.report('haml') do haml_template.render(view, {}) end end
  59. Rendering via ActionView::Base require 'erb' require 'haml' require 'action_view' require

    'action_controller' require 'benchmark/ips' module Rails def self.env ActiveSupport::StringInquirer.new('production') end end require 'haml/template' view = Class.new(ActionView::Base).new('.') puts view.render(template: 'hello', handlers: 'erb') puts view.render(template: 'hello', handlers: 'haml') Benchmark.ips do |x| x.report('erb') do view.render(template: 'hello', handlers: 'erb') end x.report('haml') do view.render(template: 'hello', handlers: 'haml') end end
  60. The code begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base",

    "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>false, :format=>:html5, :encoding=>"UTF-8", :escape_html=>false, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.format_script_false_f alse_false_false_false_true_false(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  61. The code begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base",

    "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>false, :format=>:html5, :encoding=>"UTF-8", :escape_html=>false, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;; _hamlout.push_text("#{_hamlout.format_script_false_false_false_fal se_false_true_false(( 'Hello!' ));}\n", 0, false); _erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end; 4&561 )&--0 5&"3%08/
  62. The first part begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area",

    "base", "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>false, :format=>:html5, :encoding=>"UTF-8", :escape_html=>false, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;
  63. Haml.has_many options begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base",

    "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>false, :format=>:html5, :encoding=>"UTF-8", :escape_html=>false, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;
  64. Haml has various options But who actually configures Haml? Haml

    buffer can be configured even in runtime! No. We don't need such a feature. Default must be just fine for everyone. Let's just remove them.
  65. The code begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base",

    "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>false, :format=>:html5, :encoding=>"UTF-8", :escape_html=>false, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.format_script_false_f alse_false_false_false_true_false(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  66. - options begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer);_erbout = _hamlout.buffer;@output_buffer

    = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.forma t_script(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  67. Haml.has_many Helpers % git grep "^ *def " lib/haml/helpers.rb lib/haml/helpers.rb:

    def initialize(method) lib/haml/helpers.rb: def to_s lib/haml/helpers.rb: def inspect lib/haml/helpers.rb: def self.action_view? lib/haml/helpers.rb: def init_haml_helpers lib/haml/helpers.rb: def non_haml lib/haml/helpers.rb: def find_and_preserve(input = nil, tags = haml_buffer.options[:preserve], &block) lib/haml/helpers.rb: def preserve(input = nil, &block) lib/haml/helpers.rb: def list_of(enum, opts={}, &block) lib/haml/helpers.rb: def html_attrs(lang = 'en-US') lib/haml/helpers.rb: def tab_up(i = 1) lib/haml/helpers.rb: def tab_down(i = 1) lib/haml/helpers.rb: def with_tabs(i) lib/haml/helpers.rb: def surround(front, back = front, &block) lib/haml/helpers.rb: def precede(str, &block) lib/haml/helpers.rb: def succeed(str, &block) lib/haml/helpers.rb: def capture_haml(*args, &block) lib/haml/helpers.rb: def haml_concat(text = "") lib/haml/helpers.rb: def haml_indent lib/haml/helpers.rb: def haml_tag(name, *rest, &block) lib/haml/helpers.rb: def haml_tag_if(condition, *tag) lib/haml/helpers.rb: def html_escape(text) lib/haml/helpers.rb: def escape_once(text) lib/haml/helpers.rb: def is_haml? lib/haml/helpers.rb: def block_is_haml?(block) lib/haml/helpers.rb: def merge_name_and_attributes(name, attributes_hash = {}) lib/haml/helpers.rb: def with_haml_buffer(buffer) lib/haml/helpers.rb: def haml_buffer lib/haml/helpers.rb: def haml_bind_proc(&proc) lib/haml/helpers.rb: def prettify(text) lib/haml/helpers.rb: def is_haml?
  68. Haml.has_many Helpers Haml extends the Helpers module into each buffer

    instance Wow lovely, but who uses them? Again, let's just remove them.
  69. The code begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer);_erbout = _hamlout.buffer;@output_buffer

    = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.format_sc ript(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  70. - Helpers begin;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer);_erbout = _hamlout.buffer;@output_buffer =

    output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.for mat_script(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  71. Haml.has_many buffers We can find 6 different buffer variables in

    the code _hamlout @haml_buffer haml_buffer _erbout @output_buffer output_buffer Why don't we just remove unneeded ones?
  72. The code begin;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer);_erbout = _hamlout.buffer;@output_buffer =

    output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.push_text("#{_hamlout.for mat_script(( 'Hello!' ));}\n", 0, false);_erbout;ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
  73. Why not directly use SafeBuffer? Requiring the SafeBuffer means that

    the Haml gem is going to depend on Rails Let's do so! Who uses Haml off Rails?
  74. This is the goal I want to make Haml as

    fast as possible But these changes would bring many incompatibilities So I'm going to fork Haml and create my own version Which is named "Haml X"
  75. Haml X Haml X is still under construction Not yet

    published I'm working on it, and hopefully will be able to ship it soon
  76. Current status of Haml X Calculating ------------------------------------- erb 69.021k i/100ms

    haml 6.263k i/100ms hamlx 9.026k i/100ms ------------------------------------------------- erb 1.831M (± 3.2%) i/s - 9.180M haml 68.364k (± 5.0%) i/s - 344.465k hamlx 110.208k (± 4.3%) i/s - 550.586k
  77. IMO Slim is actually slimmer than current Haml Code is

    slim Built on Temple Renders through Tilt Less options No helper methods
  78. Temple A very cleverly written "Template compilation framework" Compiles the

    template into "the core abstraction" S-expression using Ruby Array
  79. end