nTPL vs Mustache

Benchmark created by Fedor Indutny on


Preparation HTML

<script>
  nTPL = function(n, t) {
   function G(l) {
    return eval(l)
   }(function(l, H, x, y, u, I, J, K, Q, L, v) {
    function m(a) {
     return function(b) {
      return a.replace(L, b, a)
     }
    }
  
    function o(a) {
     return function() {
      return a
     }
    }
  
    function z(a, b) {
     if (b.w) {
      var c = b.n,
          d;
      c && p.watchFile(a, {
       persistent: true,
       interval: 100
      }, function() {
       p.readFile(a, function(h, f) {
        if (!h) if (d = d || y[c]) d(b.call(A, f.toString()))
       })
      })
     }
    }
  
    function M(a, b, c) {
     c(a)
    }
  
    function N(a, b) {
     return a;
     var c = p.readFileSync(a).toString();
     z(a, b);
     return c
    }
  
    function w(a, b, c, d, h) {
     function f(q, C) {
      function O(e, g, j) {
       g = [i, e, function(P, D) {
        return D[D.length] = P}];
       for (j in b) g[g.length] = e[b[j]];
       return g
      }
      var k, i = {
       $r: [],
       $gid: H++
      },
          r, E = 0;
      b instanceof Array ? b[b.length] = "$_" : b = ["$_"];k = q ? q.replace(K, " ").replace(I, l).split(l).map(function(e, g, j) {
       if (g % 2) {
        (g = e.match(J)) && (g.f = v[g[1]]) && (e = g.f(e.substr(g[0].length), i));
        return e
       }
       if (!e) return "";
       j = "$" + E;
       b.push(j);
       i[j] = e;
       return "$p($" + E+++",$_);"
      }).join("") : "";r = G("(function($scope,$args,$p," + b.join(",") + "){$_=[];" + k + ";return $_.join('')})");
      if (this === A) return {
       i: r,
       namespace: i
      };y[c] = function(e) {
       r = e.i;
       i = e.namespace
      };k = x[F] = function(e) {
       e = e || {};
       for (c in i) e[c] = i[c];
       return r.apply(t, O(e))
      };c && (u[c] = k);
      if (C) C(k);
      else return k
     }
     var F, s;
     if (arguments.length == 1 && (s = u[a])) return s;
     if (s = x[F = a + l + b]) return u[c] = s;
     f.n = c;
     f.w = h;
     if (d) M(a, f, function(q) {
      f(q, d)
     });
     else return f(N(a, f))
    }
    var p, B, A = {};
    v = {
     "=": m("$p(%1,$_);"),
     "if": m("if(%1){"),
     "else": o("}else{"),
     elseif: m("}else if(%1){"),
     "/if": o("}"),
     each: m("$.each(%1,function($i){"),
     "/each": o("});"),
     "catch": m("%1=(function(){var $_=[];"),
     "/catch": o("return $_.join('')})();")
    };
    n = function(a) {
     if (typeof a !== "object") return w.apply(this, arguments);
     var b = a.template,
         c = a.args,
         d = a.name;
     if (d && !b && !c) return w(d);
     if (b) return w(b, c || [], d, a.callback, a.watch)
    };
    n.each = function(a, b, c) {
     var d, h = 0,
         f = a.length;
     if (f === t) for (d in a) {
      if (!(!d || a[d] === t || !a.hasOwnProperty(d))) if (b.call(c || a[d], d, a[d]) === false) break
     } else for (d =
     a[0]; h < f && b.call(c || d, h, d) !== false; d = a[++h]);
     return a
    };
    n.modificators = v
   })("\t", 0, {}, {}, {}, /{%|%}/g, /^([^\s]+)(?:\s|$)/, /\t/g, /\s+/g, /%1/);
   return n
  }();
  
  /*
    mustache.js — Logic-less templates in JavaScript
  
    See http://mustache.github.com/ for more info.
  */
  
  var Mustache = function() {
   var Renderer = function() {};
  
   Renderer.prototype = {
    otag: "{{",
    ctag: "}}",
    pragmas: {},
    buffer: [],
    pragmas_implemented: {
     "IMPLICIT-ITERATOR": true
    },
    context: {},
  
    render: function(template, context, partials, in_recursion) {
     // reset buffer & set context
     if (!in_recursion) {
      this.context = context;
      this.buffer = []; // TODO: make this non-lazy
     }
  
     // fail fast
     if (!this.includes("", template)) {
      if (in_recursion) {
       return template;
      } else {
       this.send(template);
       return;
      }
     }
  
     template = this.render_pragmas(template);
     var html = this.render_section(template, context, partials);
     if (in_recursion) {
      return this.render_tags(html, context, partials, in_recursion);
     }
  
     this.render_tags(html, context, partials, in_recursion);
    },
  
  /*
        Sends parsed lines
      */
    send: function(line) {
     if (line != "") {
      this.buffer.push(line);
     }
    },
  
  /*
        Looks for %PRAGMAS
      */
    render_pragmas: function(template) {
     // no pragmas
     if (!this.includes("%", template)) {
      return template;
     }
  
     var that = this;
     var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + this.ctag);
     return template.replace(regex, function(match, pragma, options) {
      if (!that.pragmas_implemented[pragma]) {
       throw ({
        message: "This implementation of mustache doesn't understand the '" + pragma + "' pragma"
       });
      }
      that.pragmas[pragma] = {};
      if (options) {
       var opts = options.split("=");
       that.pragmas[pragma][opts[0]] = opts[1];
      }
      return "";
      // ignore unknown pragmas silently
     });
    },
  
  /*
        Tries to find a partial in the curent scope and render it
      */
    render_partial: function(name, context, partials) {
     name = this.trim(name);
     if (!partials || partials[name] === undefined) {
      throw ({
       message: "unknown_partial '" + name + "'"
      });
     }
     if (typeof(context[name]) != "object") {
      return this.render(partials[name], context, partials, true);
     }
     return this.render(partials[name], context[name], partials, true);
    },
  
  /*
        Renders inverted (^) and normal (#) sections
      */
    render_section: function(template, context, partials) {
     if (!this.includes("#", template) && !this.includes("^", template)) {
      return template;
     }
  
     var that = this;
     // CSW - Added "+?" so it finds the tighest bound, not the widest
     var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + "\\s*", "mg");
  
     // for each {{#foo}}{{/foo}} section do...
     return template.replace(regex, function(match, type, name, content) {
      var value = that.find(name, context);
      if (type == "^") { // inverted section
       if (!value || that.is_array(value) && value.length === 0) {
        // false or empty list, render it
        return that.render(content, context, partials, true);
       } else {
        return "";
       }
      } else if (type == "#") { // normal section
       if (that.is_array(value)) { // Enumerable, Let's loop!
        return that.map(value, function(row) {
         return that.render(content, that.create_context(row), partials, true);
        }).join("");
       } else if (that.is_object(value)) { // Object, Use it as subcontext!
        return that.render(content, that.create_context(value), partials, true);
       } else if (typeof value === "function") {
        // higher order section
        return value.call(context, content, function(text) {
         return that.render(text, context, partials, true);
        });
       } else if (value) { // boolean section
        return that.render(content, context, partials, true);
       } else {
        return "";
       }
      }
     });
    },
  
  /*
        Replace {{foo}} and friends with values from our view
      */
    render_tags: function(template, context, partials, in_recursion) {
     // tit for tat
     var that = this;
  
     var new_regex = function() {
      return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + that.ctag + "+", "g");
     };
  
     var regex = new_regex();
     var tag_replace_callback = function(match, operator, name) {
      switch (operator) {
      case "!":
       // ignore comments
       return "";
      case "=":
       // set new delimiters, rebuild the replace regexp
       that.set_delimiters(name);
       regex = new_regex();
       return "";
      case ">":
       // render partial
       return that.render_partial(name, context, partials);
      case "{":
       // the triple mustache is unescaped
       return that.find(name, context);
      default:
       // escape the value
       return that.escape(that.find(name, context));
      }
     };
     var lines = template.split("\n");
     for (var i = 0; i < lines.length; i++) {
      lines[i] = lines[i].replace(regex, tag_replace_callback, this);
      if (!in_recursion) {
       this.send(lines[i]);
      }
     }
  
     if (in_recursion) {
      return lines.join("\n");
     }
    },
  
    set_delimiters: function(delimiters) {
     var dels = delimiters.split(" ");
     this.otag = this.escape_regex(dels[0]);
     this.ctag = this.escape_regex(dels[1]);
    },
  
    escape_regex: function(text) {
     // thank you Simon Willison
     if (!arguments.callee.sRE) {
      var specials = [
       '/', '.', '*', '+', '?', '|',
       '(', ')', '[', ']', '{', '}', '\\'
       ];
      arguments.callee.sRE = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
     }
     return text.replace(arguments.callee.sRE, '\\$1');
    },
  
  /*
        find `name` in current `context`. That is find me a value
        from the view object
      */
    find: function(name, context) {
     name = this.trim(name);
  
     // Checks whether a value is thruthy or false or 0
  
  
     function is_kinda_truthy(bool) {
      return bool === false || bool === 0 || bool;
     }
  
     var value;
     if (is_kinda_truthy(context[name])) {
      value = context[name];
     } else if (is_kinda_truthy(this.context[name])) {
      value = this.context[name];
     }
  
     if (typeof value === "function") {
      return value.apply(context);
     }
     if (value !== undefined) {
      return value;
     }
     // silently ignore unkown variables
     return "";
    },
  
    // Utility methods
    /* includes tag */
    includes: function(needle, haystack) {
     return haystack.indexOf(this.otag + needle) != -1;
    },
  
  /*
        Does away with nasty characters
      */
    escape: function(s) {
     s = String(s === null ? "" : s);
     return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
      switch (s) {
      case "&":
       return "&amp;";
      case "\\":
       return "\\\\";
      case '"':
       return '\"';
      case "<":
       return "&lt;";
      case ">":
       return "&gt;";
      default:
       return s;
      }
     });
    },
  
    // by @langalex, support for arrays of strings
    create_context: function(_context) {
     if (this.is_object(_context)) {
      return _context;
     } else {
      var iterator = ".";
      if (this.pragmas["IMPLICIT-ITERATOR"]) {
       iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
      }
      var ctx = {};
      ctx[iterator] = _context;
      return ctx;
     }
    },
  
    is_object: function(a) {
     return a && typeof a == "object";
    },
  
    is_array: function(a) {
     return Object.prototype.toString.call(a) === '[object Array]';
    },
  
  /*
        Gets rid of leading and trailing whitespace
      */
    trim: function(s) {
     return s.replace(/^\s*|\s*$/g, "");
    },
  
  /*
        Why, why, why? Because IE. Cry, cry cry.
      */
    map: function(array, fn) {
     if (typeof array.map == "function") {
      return array.map(fn);
     } else {
      var r = [];
      var l = array.length;
      for (var i = 0; i < l; i++) {
       r.push(fn(array[i]));
      }
      return r;
     }
    }
   };
  
   return ({
    name: "mustache.js",
    version: "0.3.0",
  
  /*
        Turns a template and view into HTML
      */
    to_html: function(template, view, partials, send_fun) {
     var renderer = new Renderer();
     if (send_fun) {
      renderer.send = send_fun;
     }
     renderer.render(template, view, partials);
     if (!send_fun) {
      return renderer.buffer.join("\n");
     }
    }
   });
  }();
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
nTPL simple
nTPL("abc")();
ready
Mustache simple
Mustache.to_html("abc");
ready
nTPL advanced
nTPL("{%= a %}-{%= b %}-{%= c %}", ["a", "b", "c"])({
 a: 1,
 b: 2,
 c: 3
});
ready
Mustache advanced
Mustache.to_html("{{a}}-{{b}}-{{c}}", {
 a: "a",
 b: "b",
 c: "c"
})
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by Fedor Indutny on