Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance implications of using :coffescript filter inside HAML templates?

So HAML 4 includes a coffeescript filter, which allows us coffee-loving rails people to do neat things like this:

- word = "Awesome."

:coffeescript
  $ ->
    alert "No semicolons! #{word}"

My question: For the end user, is this slower than using the equivalent :javascript filter? Does using the coffeescript filter mean the coffeescript will be compiled to javascript on every page load (which would obviously be a performance disaster), or does this only happen once when the application is started?

like image 832
Adrian Macneil Avatar asked Jul 10 '13 01:07

Adrian Macneil


People also ask

Should you use CoffeeScript?

CoffeeScript is something that makes even good JavaScript code better. CoffeeScript compiled code can do everything that natively written JavaScript code can, only the code produced by using CoffeeScript is way shorter, and much easier to read.

What is the point of CoffeeScript?

Advantages of CoffeeScript Using CoffeeScript, we can write clean, clear, and easily understandable codes. Write less do more − For a huge code in JavaScript, we need comparatively very less number of lines of CoffeeScript. Reliable − CoffeeScript is a safe and reliable programming language to write dynamic programs.

How do I use CoffeeScript in HTML?

You simple need to add a <script type="text/coffeescript" src="app. coffee"></script> to execute coffee script code in an HTML file. In other cases, I've seen people use the attributes of type="coffeescript" and type="coffee" , so they might work for you as well. Save this answer.


2 Answers

It depends.

When Haml compiles a filter it checks to see if the filter text contains any interpolation (#{...}). If there isn’t any then it will be the same text to transform on each request, so the conversion is done once at compile time and the result included in the template.

If there is interpolation in the filter text, then the actual text to transform will vary on each request, so the Coffeescript will need to be compiled each time.

Here’s an example. First with no interpolation:

:coffeescript
  $ ->
    alert "No semicolons! Awesome"

This generates the code (use haml -d to see the generated Ruby code):

_hamlout.buffer << "<script>\n  (function() {\n    $(function() {\n      return alert(\"No semicolons! Awesome\");\n    });\n  \n  }).call(this);\n</script>\n";

This code simply adds a string to the buffer, so no Coffeescript is being recompiled.

Now with interpolation:

- word = "Awesome."

:coffeescript
  $ ->
    alert "No semicolons! #{word}"

This generates:

 word = "Awesome."
_hamlout.buffer << "#{
find_and_preserve(Haml::Filters::Coffee.render_with_options(
"$ ->
  alert \"No semicolons! #{word}\"\n", _hamlout.options))
}\n";

Here, since Haml needs to wait to see what the value of the interpolation is, the Coffeescript is recompiled each time.

You can avoid compiling the Coffeescript on each request by not having any interpolation inside your :coffeescript filters.

The :javascript filter behaves similarly, checking to see if there is any interpolation, but since the :javascript filter only outputs some text to the buffer when it runs there is much less of a performance hit using it. You could possibly combine :javascript and :coffeescript filters, putting interpolated data in :javascript and keeping your :coffeescript static:

- word = "Awesome"

:javascript
  var message = "No semicolons! #{word}";

:coffeescript
  alert message
like image 83
matt Avatar answered Oct 17 '22 07:10

matt


matt's answer is clear on what is going on. I made a helper to add locals to :coffeescript filters from a hash. This way you don't need to use global JavaScript variables. As a side note: on Linux, the slowdown is really negligible. On Windows however, the impact on performance is quite important (easily more than 100ms per block to compile).

module HamlHelper
  def coffee_with_locals locals={}, &block
    block_content = capture_haml do
      block.call
    end

    return block_content if locals.blank?

    javascript_locals = "\nvar "
    javascript_locals << locals.map{ |key, value| j(key.to_s) + ' = ' + value.to_json.gsub('</', '<\/') }.join(",\n    ")
    javascript_locals << ";\n"

    content_node = Nokogiri::HTML::DocumentFragment.parse(block_content)
    content_node.search('script').each do |script_tag|
      # This will match the '(function() {' at the start of coffeescript's compiled code
      split_coffee = script_tag.content.partition(/\(\s*function\s*\(\s*\)\s*\{/)
      script_tag.content = split_coffee[0] + split_coffee[1] +  javascript_locals + split_coffee[2]
    end

    content_node.to_s.html_safe
  end
end

It allows you to do the following:

= coffee_with_locals "test" => "hello ", :something => ["monde", "mundo", "world"], :signs => {:interogation => "?", :exclamation => "!"} do
  :coffeescript
    alert(test + something[2] + signs['exclamation'])

Since there is no interpollation, the code is actually compiled as normal.

like image 28
Maxime Lapointe Avatar answered Oct 17 '22 09:10

Maxime Lapointe