juicer - a fast template engine using javascript

60
一个前端 Javascript 模板引擎的实现与优化 流火 @ TaobaoUED Blog: http://benben.cc 12528日星期

Upload: paulguo

Post on 10-May-2015

1.048 views

Category:

Technology


10 download

DESCRIPTION

A fast template engine using javascript

TRANSCRIPT

Page 1: Juicer - A fast template engine using javascript

一个前端 Javascript 模板引擎的实现与优化

流火 @ TaobaoUEDBlog: http://benben.cc

12年5月28日星期⼀一

Page 2: Juicer - A fast template engine using javascript

Who am i ?【诗经·国风·豳风】 七月流火,九月授衣。

Blog: http://benben.cc

Github: http://github.com/PaulGuo

Weibo: http://weibo.com/janbing

Twitter: http://twitter.com/guokai

【一些开源项目】

【在哪】

12年5月28日星期⼀一

Page 3: Juicer - A fast template engine using javascript

Mustache

Kissy template

jQuery tmpl

nTenjin

...micro template

ejsdoT yayaTemplate

artTemplatehandlebar

12年5月28日星期⼀一

Page 4: Juicer - A fast template engine using javascript

is it nessary ? are u sure ?

如无必要,勿增实体

12年5月28日星期⼀一

Page 5: Juicer - A fast template engine using javascript

Juicer 的特点

1 循环 {@each}…{@/each}

2 判断 {@if}…{@else if}…{@else}…{@/if}

3 变量(支持自定义函数)${varname|function}

4 注释 {# comment here}

5 辅助循环 {@each i in range(0,9)}

6 自定义函数,扩展灵活 register/unregister

support node.js only 5kb

12年5月28日星期⼀一

Page 6: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 7: Juicer - A fast template engine using javascript

模板引擎的基本机理就是替换(转换),将指定的标签转换为

需要的业务数据;将指定的伪语句按照某种流程来变换输出。

模板引擎基本原理

The gist of JavaScript templates is that you can take an HTML fragment interpolated with template variables and combine it with a JavaScript object, replacing those tem- plate variables with values from the object. Overall, JavaScript templating works in much the same way as templating libraries in other languages, such as PHP’s Smarty, Ruby’s ERB, and Python’s string formatting.

- Javascript Web Application

12年5月28日星期⼀一

Page 8: Juicer - A fast template engine using javascript

JSON TemplateTemplateEngine

HTMLFragment

water juicer fruits

12年5月28日星期⼀一

Page 9: Juicer - A fast template engine using javascript

模板引擎的分类

·置换型模板引擎。

·解释型模板引擎。

·编译型模板引擎。

12年5月28日星期⼀一

Page 10: Juicer - A fast template engine using javascript

静态页面 含有丰富交互的应用 服务端后端提供纯数据(接口化)前端负责将数据生成页面

12年5月28日星期⼀一

Page 11: Juicer - A fast template engine using javascript

MVC 需要模板

MVC最早是在SmallTalk语言的开发过程中总结出的一种设

计模式,MVC分别代表了"模型"、"视图"和"控制",目的就

是让不同的开发角色在大中型项目中各司其职。在网络应

用程序的开发中,可以用下图来表示各概念之间的关系。

12年5月28日星期⼀一

Page 12: Juicer - A fast template engine using javascript

现实中的使用场景

12年5月28日星期⼀一

Page 13: Juicer - A fast template engine using javascript

var html = '<span class="name">' + json.name + ' (blog: ' + json.blog + ')</span>';

<span class="name">流火 (blog: ued.taobao.com)</span>

var json = { name: "liuhuo", blog: "ued.taobao.com"};

拼字符串

12年5月28日星期⼀一

Page 14: Juicer - A fast template engine using javascript

function sub(str,data) { return str .replace(/{(.*?)}/igm,function($,$1) { return data[$1]?data[$1]:$; });}

var tpl = '<span class="name">{name} (blog: {blog})</span>';

var html = sub(tpl, json);

YUI.Lang.sub

12年5月28日星期⼀一

Page 15: Juicer - A fast template engine using javascript

Syntax Performance

SecurityXSS escape

Error Handling

easy to write & read as faster as you can

robustness

12年5月28日星期⼀一

Page 16: Juicer - A fast template engine using javascript

主张原生语法的传统模板引擎 主张Logic-less的新兴模板引擎

ejs、nTenjin、doT mustache、handlebars

jQuery Tmpl、kissy Template

the templating syntax for most libraries is very similar, if not identical. - Javascript Web Application

追求纯粹,希望通过原生的

Javascript 语法来写模板,这类模

板引擎的实现一定是编译型的,

但是它们的写法往往让人抓狂。

追求 KISS / Logic less, 这注定了这

类模板引擎的实现是解释型的,

也就注定了它们性能是有瓶颈

的。

语法

{{#list}}

{{#a}} {{value}} {{/a}}

12年5月28日星期⼀一

Page 17: Juicer - A fast template engine using javascript

JSON HTML

var data={    list:[        {name:'liuhuo',show:true},        {name:'benben',show:false},        {name:'taobao',show:true}    ],    blah:[        {num:1},        {num:2},        {num:3,inner:[            {'time':'15:00'},            {'time':'16:00'},            {'time':'17:00'},            {'time':'18:00'}        ]},        {num:4}    ]};

<ul>    <li>liuhuo (index: 0)</li>    <li>taobao (index: 2)</li>    <li>num: 1</li>    <li>num: 2</li>    <li>num: 3</li>    <li>15:00</li>    <li>16:00</li>    <li>17:00</li>    <li>18:00</li>    <li>num: 4</li></ul>

12年5月28日星期⼀一

Page 18: Juicer - A fast template engine using javascript

语法 | doT、nTenjin、ejs、tmpl

<ul>    <?js for(var i=0;i<it.list.length;i++) { ?>        <?js if(it.list[i].show) { ?>            <li>${it.list[i].name} (index: ${i})</li>        <?js } ?>    <?js } ?>    <?js for(var i=0;i<it.blah.length;i++) { ?>        <li>            num: ${it.blah[i].num}            <?js if(it.blah[i].num==3) { ?>                           <?js for(var j=0;j<it.blah[i].inner.length;j++) { ?>                    <li>${it.blah[i].inner[j].time}</li>                <?js } ?>            <?js } ?>        </li>    <?js } ?></ul>

12年5月28日星期⼀一

Page 19: Juicer - A fast template engine using javascript

语法 | doT、nTenjin、ejs、tmpl

<ul>    <% for(var i=0;i<list.length;i++) { %>        <% if(list[i].show) { %>            <li><%= list[i].name %> (index: <%= i %>)</li>        <% } %>    <% } %>    <% for(var i=0;i<blah.length;i++) { %>        <li>            num: <%= blah[i].num %>            <% if(blah[i].num==3) { %>                           <% for(var j=0;j<blah[i].inner.length;j++) { %>                    <li><%= blah[i].inner[j].time %></li>                <% } %>            <% } %>        </li>    <% } %></ul>

12年5月28日星期⼀一

Page 20: Juicer - A fast template engine using javascript

语法 | mustache、handlebars

<ul>    {{#list}}        {{#show}}            <li>{{name}} (index: {{index}})</li>        {{/show}}    {{/list}}    {{#blah}}        <li>            num: {{num}}            ...            {{#inner}}                <li>{{time}}</li>            {{/inner}}            ...        </li>    {{/blah}}</ul>

12年5月28日星期⼀一

Page 21: Juicer - A fast template engine using javascript

语法 | kissy、juicer

<ul>    {{#each list as it,k}}        {{#if it.show}}            <li>{{it.name}} (index: {{k}})</li>        {{/if}}    {{/each}}    {{#each blah as it}}        <li>            num: {{it.num}}            {{#if it.num==3}}                {{#each it.inner as it2}}                    <li>{{it2.time}}</li>                {{/each}}            {{/if}}        </li>    {{/each}}</ul>

12年5月28日星期⼀一

Page 22: Juicer - A fast template engine using javascript

语法 | kissy、juicer

<ul>    {@each data.list as it,k}        {@if it.show}            <li>${it.name} (index: ${k})</li>        {@/if}    {@/each}    {@each data.blah as it}        <li>            num: ${it.num}            {@if it.num==3}                {@each it.inner as it2}                    <li>${it2.time}</li>                {@/each}            {@/if}        </li>    {@/each}</ul>

12年5月28日星期⼀一

Page 23: Juicer - A fast template engine using javascript

语法上:不赞同完全采用原生语法,也不认可过于 Logic-less 从而在性能上遇到瓶颈。性能上:认为模板引擎的性能应该是第⼀一位的。

立场

The basic advice regarding response times has been about the same for thirty years [Miller

1968; Card et al. 1991]:

• 0.1 second is about the limit for having the user feel that the system is reacting

instantaneously, meaning that no special feedback is necessary except to display the result.

• 1.0 second is about the limit for the user's flow of thought to stay uninterrupted, even

though the user will notice the delay. Normally, no special feedback is necessary during

delays of more than 0.1 but less than 1.0 second, but the user does lose the feeling of

operating directly on the data.

100ms * 100w = 27.777hours

12年5月28日星期⼀一

Page 24: Juicer - A fast template engine using javascript

性能

12年5月28日星期⼀一

Page 25: Juicer - A fast template engine using javascript

性能 - 最大化渲染测试

12年5月28日星期⼀一

Page 26: Juicer - A fast template engine using javascript

性能优化点

using += instead of array.push

12年5月28日星期⼀一

Page 27: Juicer - A fast template engine using javascript

When evaluating this code, four steps are taken:

1. A temporary string is created in memory.2. The concatenated value "onetwo" is assigned to the temporary string. 3. The temporary string is concatenated with the current value of str. 4. The result is assigned to str.

- Hign Performance Javascript

str += "one" + "two";

12年5月28日星期⼀一

Page 28: Juicer - A fast template engine using javascript

This dramatic improvement results from avoiding repeatedly allocating memory for and copying progressively larger and larger strings. When joining an array, the browser allocates enough memory to hold the complete string, and never copies the same part of the final string more than once.

- Hign Performance Javascript

str = ["one", "two"].join(‘’);

12年5月28日星期⼀一

Page 29: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 30: Juicer - A fast template engine using javascript

Browser string optimizations have changed the string concatenation picture.

Firefox was the first browser to optimize string concatenation. Beginning with version 1.0, the array technique is actually slower than using the plus operator in all cases. Other browsers have also optimized string concatenation, so Safari, Opera, Chrome, and Internet Explorer 8 also show better performance using the plus operator. Internet Explorer prior to version 8 didn’t have such an optimization, and so the array technique is always faster than the plus operator.

— Writing Efficient JavaScript: Chapter 7 – Even Faster Websites

12年5月28日星期⼀一

Page 31: Juicer - A fast template engine using javascript

// ECMA-262, section 15.5.4.6function StringConcat() {  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {    throw MakeTypeError("called_on_null_or_undefined", ["String.prototype.concat"]);  }  var len = %_ArgumentsLength();  var this_as_string = TO_STRING_INLINE(this);  if (len === 1) {    return this_as_string + %_Arguments(0);  }  var parts = new InternalArray(len + 1);  parts[0] = this_as_string;  for (var i = 0; i < len; i++) {    var part = %_Arguments(i);    parts[i + 1] = TO_STRING_INLINE(part);  }  return %StringBuilderConcat(parts, len + 1, "");}

The V8 engine (used in Google Chrome) uses this code to do string concatenation:

12年5月28日星期⼀一

Page 32: Juicer - A fast template engine using javascript

性能优化点

avoid using with block

12年5月28日星期⼀一

Page 33: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 34: Juicer - A fast template engine using javascript

var person = { name: "Nicholas", age: 30};

function displayInfo(){ var count = 5; with(person){ alert(name + " is " + age); alert("Count is " + count); }}

displayInfo();

The with construct introduces an extra scope for the script engine to search through whenever a variable is referenced. This alone produces a minor performance decrease. However, the contents of that scope are not known at compile time, meaning that the compiler cannot optimize for it, in the same way as it can with normal scopes (such as those created by functions).

12年5月28日星期⼀一

Page 35: Juicer - A fast template engine using javascript

性能优化点

cache the compiled template.

12年5月28日星期⼀一

Page 36: Juicer - A fast template engine using javascript

JavaScript, like many scripting languages, allows you to take a string containing code and execute it from within running code. There are four standard ways to accomplish this: eval(), the Function() constructor, setTimeout(), and setInterval(). Each of these functions allows you to pass in a string of JavaScript code and have it executed.

- Hign Performance Javascript

Cache The Compiled Template

12年5月28日星期⼀一

Page 37: Juicer - A fast template engine using javascript

安全性

var json={

! output:'<script>alert("XSS");</script>'

};

juicer('${output}',json);//输出:&lt;script&gt;alert("XSS");&lt;/script&gt;

juicer('$${output}',json);//输出:<script>alert("XSS");</script>

document.write $(node).html()

12年5月28日星期⼀一

Page 38: Juicer - A fast template engine using javascript

Juicer 的使用方法.

12年5月28日星期⼀一

Page 39: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 40: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 41: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 42: Juicer - A fast template engine using javascript

{@each list as it, k}

<span class=”{@if k+2>list.length}notdot{@/if}”>...</span>

{@/each}

12年5月28日星期⼀一

Page 43: Juicer - A fast template engine using javascript

http://taobao.com/s?q=%E6%B7%98%E5%AE%9D

json={query:’淘宝’,...}

http://taobao.com/s?q=${query|encodeURIComponent}

12年5月28日星期⼀一

Page 44: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 45: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 46: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 47: Juicer - A fast template engine using javascript

template reusable function html codecompiled template

compile render

12年5月28日星期⼀一

Page 48: Juicer - A fast template engine using javascript

(function() {

var juicer = function() {

var args = [].slice.call(arguments);

args.push(juicer.options);

if(arguments.length == 1) {

return juicer.compile.apply(juicer, args);

}

if(arguments.length >= 2) {

return juicer.to_html.apply(juicer, args);

}

};

...

...

单一接口

12年5月28日星期⼀一

Page 49: Juicer - A fast template engine using javascript

juicer.compile = function(tpl, options) { if(!options || options !== this.options) { options = __creator(options, this.options); }

try { var engine = this.__cache[tpl] ? this.__cache[tpl] : new this.template(this.options).parse(tpl, options); if(!options || options.cache !== false) { this.__cache[tpl] = engine; } return engine;

} catch(e) { __throw('Juicer Compile Exception: ' + e.message); return { render: function() {} //noop }; } };

编译

12年5月28日星期⼀一

Page 50: Juicer - A fast template engine using javascript

this.parse = function(tpl, options) { var _that = this;

if(!options || options.loose !== false) { tpl = this.__lexicalAnalyze(tpl) + tpl; } tpl = this.__removeShell(tpl, options); tpl = this.__toNative(tpl, options);

this._render = new Function('_, _method', tpl);

this.render = function(_, _method) { if(!_method || _method !== that.options._method) { _method = __creator(_method, that.options._method); }

return _that._render.call(this, _, _method); };

return this; };

编译

12年5月28日星期⼀一

Page 51: Juicer - A fast template engine using javascript

before __removeShell

<ul>

    {@each list as it,k}

        {@if it.show}

            <li>${it.name} (index: ${k})</li>

        {@/if}

    {@/each}

</ul>

12年5月28日星期⼀一

Page 52: Juicer - A fast template engine using javascript

after __removeShell

<ul>    <% for var i0 = 0; i0 < list; i0++ { %> <% var it = list[i0]; %> <% var k = i0; %>        <% if(it.show) { %>            <li><%= it.name %> (index: <%= k %>)</li>        <% } %>    <% } %></ul>

12年5月28日星期⼀一

Page 53: Juicer - A fast template engine using javascript

__toNative

function anonymous(_, _method) { 'use strict'; var _ = _ || {}; var _out = ''; _out += ''; try { _out += ''; var list = _.list; var it = _.it; var k = _.k; _out += '<ul>'; for (var i0 = 0, li0 = list.length; i0 < li0; i0++) { var it = list[i0]; var k = i0; _out += ''; if (it.show) { _out += '<li>'; _out += _method.__escapehtml.escaping(_method.__escapehtml.detection((it.name))); _out += ' (index: '; _out += _method.__escapehtml.escaping(_method.__escapehtml.detection((k))); _out += ')</li> '; } _out += ''; } _out += '</ul>'; } catch (e) { _method.__throw("Juicer Render Exception: " + e.message); } _out += ''; return _out;}

__lexicalAnalyze

12年5月28日星期⼀一

Page 54: Juicer - A fast template engine using javascript

渲染函数

this.parse = function(tpl, options) { var _that = this;

if(!options || options.loose !== false) { tpl = this.__lexicalAnalyze(tpl) + tpl; } tpl = this.__removeShell(tpl, options); tpl = this.__toNative(tpl, options);

this._render = new Function('_, _method', tpl);

this.render = function(_, _method) { if(!_method || _method !== that.options._method) { _method = __creator(_method, that.options._method); }

return _that._render.call(this, _, _method); };

return this; };

12年5月28日星期⼀一

Page 55: Juicer - A fast template engine using javascript

参数/方法继承

var __creator = function(o, proto) { o = o !== Object(o) ? {} : o;

if(o.__proto__) { o.__proto__ = proto; return o; }

var _Empty = function() {}; var n = new((_Empty).prototype = proto, _Empty);

for(var i in o) { if(o.hasOwnProperty(i)) { n[i] = o[i]; } }

return n; };

12年5月28日星期⼀一

Page 56: Juicer - A fast template engine using javascript

juicer.to_html = function(tpl, data, options) {

if(!options || options !== this.options) {

options = __creator(options, this.options);

}

return this.compile(tpl, options).render(data, options._method);

};

编译并渲染

12年5月28日星期⼀一

Page 57: Juicer - A fast template engine using javascript

12年5月28日星期⼀一

Page 58: Juicer - A fast template engine using javascript

this.parse = function(tpl, options) { var _that = this;

if(!options || options.loose !== false) { tpl = this.__lexicalAnalyze(tpl) + tpl; } tpl = this.__removeShell(tpl, options); tpl = this.__toNative(tpl, options);

this._render = new Function('_, _method', tpl);

this.render = function(_, _method) { var fn = function() {};

fn.toString = function() { if(!_method || _method !== that.options._method) { _method = __creator(_method, that.options._method); }

return _that._render.call(this, _, _method); };

return fn; };

return this; };

12年5月28日星期⼀一

Page 59: Juicer - A fast template engine using javascript

展望

·继续优化 Juicer (性能、源代码)

·完善更多的文档和实利

·提供其它模板语法到 Juicer 模板的自动化工具

·更多的语言支持(诸如PHP、Python、Ruby ⋯)

·精确的模板调试模式,精确到行号和模板错误位置

·持续改进

12年5月28日星期⼀一

Page 60: Juicer - A fast template engine using javascript

juicer(‘${over}’, {over:‘thanks!’});

Q & A

12年5月28日星期⼀一