Optimize compiled templates #87

Open
wants to merge 59 commits into from

4 participants

@omarkhan

I have been experimenting with optimizing coffeekup templates using UglifyJS to analyze the template source and prerender the html. The idea is to turn this:

function() {
  //
  // Lots of boilerplate code...
  //
  html(function() {
    head(function() {
      return title(this.title);
    });
    return body(function() {
      h1(this.title);
      return p('Super fast templates');
    });
  });}).call(data);return __ck.buffer.join('');
}

into this:

function() {
  //
  // Much less boilerplate
  //
  ((function() {
    text("<!DOCTYPE html>");
    text("<html>");
    text(function() {
      text("<head>");
      text(function() {
        return function() {
          text("<title>");
          text(this.title);
          text("</title>");
        }.call(data);
      }.call(data));
      text("</head>");
      return function() {
        text("<body>");
        text(function() {
          text("<h1>");
          text(this.title);
          text("</h1>");
          return function() {
            text("<p>");
            text("Super fast templates");
            text("</p>");
          }.call(data);
        }.call(data));
        text("</body>");
      }.call(data);
    }.call(data));
    text("</html>");
  })).call(data);return __ck.buffer;
}

The advantages of this approach are shorter template functions (unless the template is very long) thanks to reduced boilerplate, and faster execution owing to much of the work being done at compile time. A further performance improvement is achieved by using string concatenation rather than array join to build the output. Here are my benchmark results, running a compiled template 5000 times on node:

CoffeeKup (precompiled): 263 ms
CoffeeKup (precompiled, optimized): 24 ms
Jade (precompiled): 530 ms
haml-js (precompiled): 89 ms
Eco: 92 ms

See here for browser rendering performance: http://jsperf.com/coffeekup-optimized/2

As far as I can tell, these optimizations give a 10x performance boost on node, a 30x boost in Chrome and a more modest 3x improvement in Firefox.

Usage

template = coffeekup.compile 'h1 @title', optimize: yes
template(title: 'Super fast template')
# '<h1>Super fast template</h1>'

Caveats

As this compiler uses static analysis to optimize your templates, too much complex logic may cause it to fall over. But templates should be logic-free so that shouldn't be a problem, right?

I have aimed for API compatibility with regular coffeekup, but there are a few differences to be aware of:

  • The coffeescript helper function does not add "text/coffeescript" to the <script> tag. I could implement this, but it doesn't strike me as very useful.
  • Output formatting is not implemented (yet). You get a single line of html.
  • Arrays are not rendered directly in the template output. Join them into a string before passing them to the compiled template function.
  • You can't set up a hash of options and then pass it to the tag function, e.g.
attrs =
  name: 'email'
  type: 'text'
input attrs

Do this instead:

input
  name: 'email'
  type: 'text'

I have made some minor modifications to the test suite to account for the above.

Omar Khan added some commits Oct 18, 2011
Omar Khan Simplified executable ab4a134
Omar Khan Added basic optimised compiler c029eba
Omar Khan Get exports from main script f22d1b4
Omar Khan Removed function wrapper f4becea
Omar Khan Added hardcoded locals f8a1739
Omar Khan Compiler can handle tag arguments other than functions and objects 0d53c78
Omar Khan Wrap compiled code in bound function to preserve scope 6f2f320
Omar Khan Unwrap calls to tag functions from function wrapper when not needed 42952c0
Omar Khan Fixed function wrapper 61adb49
Omar Khan Hardcoded locals also checked for markup tags bf775ed
Omar Khan Added doctype cf5fb39
Omar Khan Allow for tags without content 53039fd
Omar Khan Removed broken jade benchmarks c87c3ee
Omar Khan Merge branch 'master' into optimize 362152a
Omar Khan Skeleton test() function takes numbers as well as strings cce76e0
Omar Khan Code cleanup, comments 5b50c62
Omar Khan Added escape function h() to skeleton 3ba8a58
Omar Khan Added tag() function 631b630
Omar Khan Added comment() function 43ee3fc
Omar Khan Don't try to put too much in a single call to text() 8ac5838
Omar Khan Refactor e01a404
Omar Khan Added support for script and coffeescript functions ecb2c59
Omar Khan Comments for clarity 571732c
Omar Khan Do not parse id class string for script tags 48700e0
Omar Khan Fixed coffeescript tag function 060acd3
Omar Khan Added locals option 83f98b1
Omar Khan Added ie conditional comment helper 064faff
Omar Khan Tag functions can handle prefixed attributes 2d9611f
Omar Khan Boolean true in tag attrs rendered as selected="selected" 04cda25
Omar Khan Functions can be passed to tag attrs object 000dc5e
Omar Khan Added yield function to skeleton 68cefd7
Omar Khan Added autoescape option 3b58ad4
Omar Khan Do not escape calls to yield 87fc65e
Omar Khan Escape tag contents by default 85d1302
Omar Khan Escape return values of functions passed as arguments to tag functions 7267231
Omar Khan Escape dynamically generated html comments c094b0d
Omar Khan ie helper allows for dynamic conditions f667ee7
Omar Khan Fixed coffeescript helper b817c05
Omar Khan Default doctype 147b194
Omar Khan Tests run from command line 83ab4a1
Omar Khan Self-closing tag tweak so that test passes 7d38b5d
Omar Khan Optimized compiler does not escape literal strings in the template, t…
…ests adjusted accordingly
39a48f0
Omar Khan text() function accepts arrays 196fb6f
Omar Khan Code object can generate if blocks e0b07c4
Omar Khan Functions passed as object attributes are prerendered as text 7d2ec29
Omar Khan Minor tweak to expected test result e384f60
Omar Khan Minor refactor 064c2c5
Omar Khan Modified ie conditional comment test: formatting not yet supported 0b507bb
Omar Khan Added optimize option 8d4f3ae
Omar Khan Created separate test suite for optimized templates edcbd37
Omar Khan Do not optimize templates when cache is off db169a2
Omar Khan Benchmark optimized templates af483dc
Omar Khan Escape hardcoded string literals cb44056
Omar Khan Fixed tests 1dacf02
Omar Khan Updated package.json d23557e
Omar Khan text() does not render arrays 41653d7
Omar Khan Use string concatenation instead of array join e7e992f
Omar Khan Added precompiled eco template benchmark 6ca8b69
Omar Khan Bugfix: missing 'var' before skeleton variables 2285f55
@flosse

Great work!

@wmertens

I wish github had a +1 button, this is awesome! I know it's in https://github.com/gradus/coffeecup now but I just wanted to express my awe :-)

@gradus

@wmertens

It does. Just type.

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment