Hacker News new | past | comments | ask | show | jobs | submit login
T.js - A tiny templating framework in ~400 bytes (github.com/jasonmoo)
91 points by jasonmoo on Aug 13, 2012 | hide | past | favorite | 51 comments



The one thing that sucks about projects with one-char names is finding them weeks after you first discover them, when you really want to experiment with using them in a real project.


Browser bookmarks are a way unrated feature. A descriptive title and you're good to go. Who cares what the name is when your bookmark title has "t.js A tiny javascript templating framework".

I already can't remember the name of half the libraries I see even when they are clever and appropriate.


Browser bookmarks are a way unrated feature.

Or Catch.com or pinboard.in or similar.

I also tend to print Web pages to PDF and stash them in a Dropbox folder for later review.


There's always the "Star" button on GitHub.

Or, there is now, anyway.


Not to mention googling them.


pinboard or equivalent.


I use this little helper in my underground lab:

    function template(str,data){
        for(var i in data){
            str=str.replace(new RegExp("{("+i+")}","g"),data[i])
        }
        return str
    }
It can be used with arrays and object literals as well:

    txt = template('hello {name}!',{name:'Jen'})

    txt = template('days: {0}, {1} and {2}',['mon','tue','wed'])
GPL and WTFPL just in case.

* Looks coincidentally similar to Fuchs'


Watch out for

    for (var prop in obj) {}
It may iterate over attributes defined in the objects prototype. The right way to iterate over an object is

    for(var prop in obj) {
        if(obj.hasOwnProperty(prop))
            doSomethingWith(obj[prop]);
    }
(see http://stackoverflow.com/a/588276)


While true, 'data' should always just be an object-literal. If template() is ever passed an instance of an object, there's something else seriously wrong elsewhere in your script.



And a few less-featureful templating-frameworks in 140 bytes or less: http://www.140byt.es/keywords/template

Notably, the examples found at https://gist.github.com/964762#file_test.js has the closest parity.

Edit: On second thought, I think the previous gist can reach parity with just a helper function for iterating and escaping:

  function engine(a,b){return function(c,d){return a.replace(/#{([^}]*)}/g,function(a,e){return Function("x","with(x)return "+e).call(c,d||b||{})})}}

  function iterator(o, k, f){
    var i, v = [];
    for(i in o[k]){
      v = v.concat(f(o, k, i));
    }
    return v.join('');
  }

  function print_kv(o, k, i){
    return [" ", i, ":", o[k][i]];
  }

  hello = engine("Hello,#{iterator(this, 'users', print_user)}!")
  // We don't have to pass iterator, on the next line, but it should demonstrate
  // how we context can be overwritten
  hello({users: {a: 1, b: 2, c:3}}, {iterator: iterator, print_user: print_kv})

  // Or don't pass print_user
  print_user = print_kv
  hello({users: {a: 1, b: 2, c:3}})
  // "Hello, a:1 b:2 c:3!"

  // Overload it!
  hello({users: {a: 1, b: 2, c:3}}, {print_user: function(o, k, i){return " "+i}})
  // "Hello, a b c!"


I had to zoom in like 3 times on my phone to vote this up. I know it may be bad form and all to comment on such, but guhdamn if as newb this doesn't wrinkle my britches.


Couldn't update my original post, but I was in a rush earlier and couldn't make the proper complete-parity examples: http://news.ycombinator.com/item?id=4379417


I'm not sure anyone's ever said "this templating framework is too big"


Maybe when talking about PHP?


Smarty PHP? :)


The 80% case:

    var render = function(src, data) {
        return src.replace(/{{(.*?)}}/g, function(w, p) {
            return data[p];
        });
    };


This is the 0% case. No escaping and therefore not safe w.r.t. XSS attacks.


I wonder if it would be possible to fit an entire webserver and web framework runtime into L2 cache? (I'm thinking Lua VM or something like that.)

EDIT Xeon 3050 has 2MB of L2 cache, so the answer is probably yes!


Out of curiosity, do you get to directly place code on the L2 cache through the Lua VM ? Is that a module ? If so, what is this voodoo and where can I find it...


No, I'm just thinking that one could minimize cache misses a whole lot.


You don't need to store the framework you are sending to clients in the CPU cache - a good network card can read the data directly from disk, or main memory and send it off, bypassing the cpu.


That's useful. I was thinking mainly of the actual server-side app server and not what would count as its payload.


Can we stop calling these things “frameworks” if they are so lightweight?


Not to play semantics, but "A software framework is a universal, reusable software platform used to develop applications, products and solutions."

http://en.wikipedia.org/wiki/Software_framework

There's nothing in there about how lightweight they have to be to not be considered a framework, so I think you're being a tad pedantic.


No, this thing isn't a software platform. You cannot build any applications with this alone. This is a library. We have these precise terms. Don't abuse them.


I am also tired of libraries being called frameworks. I wouldn't even call this a library. A library infers a collection of something. This is just a simple single function.


Stating his opinion is not being pedantic, quoting the definition to make a point is being pedantic. I think what he's saying has merit; I would call this a utility, or something more along those lines, rather than a framework.


>There's nothing in there about how lightweight they have to be to not be considered a framework, so I think you're being a tad pedantic.

That would make him wrong, not pedantic.


Thank you... and sorry for the rant but -- the word "pedantic" is incredibly overused of late on this site.

According to some quick Google site: searches:

    WORD         USE ON HACKER NEWS      
    pedantic     3340
    thoughtful   3750
    f***         9930
    idiot        6180
Which seems quite frequent, but is obviously highly anecdotal. On the other hand, it appearing 1/3 as much as f* may indicate that this is at least a fairly civil community -- even if prone to pedantry.

Edit: formatting.


  function render(tpl,data){
  	var matches = tpl.match(/{[^\}]+}/g);
  	for(var i in matches){
  		var rep =   eval("data."+matches[i].replace('{','').replace('}',''));
  		tpl = tpl.replace(matches[i],rep);
  	}                                     
  	return tpl;
  }
  alert(render("{a} is all that i've {b.a} {b.b} {b.c}", {a:"this", b:{a:"ever",b:"really",c:"needed"} }))
http://pastebin.com/txM6NZBS here, to copy and paste


You can achieve the same effect without using eval, which his many known problems in terms of security and performance.

        function render(tpl,data){
            function lookup (obj, keys) {
                return (keys.length === 0) ? obj
                :  lookup(obj[keys[0]], keys.slice(1));
            }

            var matches = tpl.match(/\{[^\}]+\}/g),
                max = matches.length,
                i, rep, keys;

            for (i = 0; i < max; i++) {
                keys = matches[i].slice(1, -1).split('.');
                rep = lookup(data, keys);
                tpl = tpl.replace(matches[i],rep);
            }

            return tpl;
        }

        alert(render("{a} is all that i've {b.a} {b.b} {b.c}", {
            a: "this",
            b: {
                a: "ever",
                b: "really",
                c: "needed"
            } 
        }));


I like it. Here's a slightly more stable version. But still not crazy about using eval on arbitrary code. O.O

     function render(tpl,data){
          return tpl.replace(/{([^\}]+)}/g,function(_, key) {
              try { return eval("data."+key); }
              catch (e) { return ""; }
          });
     }


the code i use in production doesn't use eval, but a recursive function. i like the anonymous function passed to replace, but think the catch destroys code beauty :)


  render("{a} is all that I've {a}", {'a' : 'b'});
  "b is all that I've b"
  render("{a} is all that I've {{a}}", {'a' : 'b'});
  SyntaxError: Unexpected token {
Seems Legit


  function render(tpl,data){
  	var matches = tpl.match(/{[^\}]+}/g);
  	for(var i in matches){
  		var rep =   eval("data."+matches[i].replace(/[{}]/g,''));
  		tpl = tpl.replace(matches[i],rep);
  	}                                     
  	return tpl;
  }
Fixed


looks like something broke in copying and pasting. try: http://pastebin.com/txM6NZBS


I promised myself I'd show parity with https://gist.github.com/3346253#file_t_js_parity.js at http://news.ycombinator.com/item?id=4378847, but comments eventually go non-editable.

I haven't properly updated the gist fork yet, but as follows are the semi-minified additions [of `safe` and `map` (iter)] and parity tests.

  function t(a,b){
    return function(c,d){
      return a.replace(/#{([^}]*)}/g, function(a,e){
          function safe(v){return new Option(v).innerHTML;}
          function map(o, f){
            var k, v = [];
            for(k in o) v.push(f(k, o[k]));
            return v;
          }
          return Function("x",safe+map+"with(x)return "+e).call(c,d||b||{})
      })
    }
  }
  function l(v){
    console.log(v);
  }

  // Simple interpolation: {{=value}}
  l(t("Hello, #{this.name}!")({name: "Mike"}));

  // Scrubbed interpolation: {{%unsafe_value}}
  l(t("Saved from #{safe(this.xss)}!")({xss: "<script>alert('xss')<\/script>"}));

  // Name-spaced variables: {{=User.address.city}}
  l(t("Hello1, #{this.user.name}!")({user: {name: "Mike"}}));

  // If/else blocks: {{value}} <<markup>> {{:value}} <<alternate markup>> {{/value}}
  l(t("Hello2, #{this.name || 'World'}!")({name: "Mike"}));
  l(t("Hello2, #{this.name || 'World'}!")());
  l(t("Hello2, #{this.u ? this.u.name : 'World'}!")({u: {name: "Mike"}}));

  // If not blocks: {{!value}} <<markup>> {{/value}}
  l(t("Hello3, #{!this.u ? 'World' : ''}!")({user: {name: "Mike"}}));

  // Object/Array iteration: {{@object_value}} {{=_key}}:{{=_val}} {{/@object_value}}
  l(t("Hello4, #{map(this.users, print_user).join(', ')}!", {
        print_user: function(k, user){return user;}
  })({users: ["Mike", "John", "Bill"]}));

  // Maybe you have one of those new-fangled browsers w/ builtin map
  l(t("Hello4, #{this.users.map(print_user).join(', ')}!", {
        print_user: function(user){return user;}
  })({users: ["Mike", "John", "Bill"]}));

  // Let's go deeper...
  l(t("Hello4, #{map(this.users, user).join(', ')}!", {
        user: function(_, user){
          return t("#{safe(this.name)}")(user);
        }
  })({users:[{name: "Mike"}, {name: "John<3"}, {name: "Bill"}]}));

  // Render the same template multiple times with different data
  hello = t("Hello5, and #{this.goodbye}!");
  l(hello({goodbye: "goodbye"}));
  l(hello({goodbye: "good night"}));


Also see Tweet-Templ (0.1 kB): http://mir.aculo.us/2011/03/09/little-helpers-a-tweet-sized-...

More micro-frameworks at http://microjs.com


looking at the replace chains on s(val) reminded me of something a while back, is there a faster/better way to do this king of stuff? something like:

return s(val).replace(re, function(t){

    if( t == '>' ) return '&gt';
    if( t == '<' ) return '&lt;';
    if( t == '&' ) return '&amp;';
    if( t == '"' ) return '&quot;';
    return t;
});


This isn't for the whole thing, but a common pattern to clean up that inner part could be something like:

    return { '>': '&gt;', '<': '&lt;', '&': '&amp', '"': '&quot;' }[t] || t
All formatted nicer than that, of course ;-)


    new Option("<div>in the browser, yes.</div>").innerHTML


Oh I like this. I was messing with a similar tactic appending a textNode to an HTML element but this is much more elegant.


The text node approach is cross-browser, whereas I don't think this works on oldIE.


I just tried this and it doesn't seem to escape " to &quot;.


yes! awesome snippit :)


You can make it 50% faster by observing that replacing > is unnecessary, and only one of < or " needs replacing in any particular context.


Awesome work. Starting to test it right now :)


cool but what was wrong w mustache or handlebars?


The main advantage that I see at first glance is that this library, unlike lots of others (including those two), doesn't use eval or "new Function" to build compiled templates, which meets it meets Chrome's new security requirements for extensions.


i did not know that, kewl!




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: