├── .editorconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── doc
├── api.md
├── config.md
└── syntax.md
├── main.js
├── package.json
├── src
├── main.js
├── node.js
└── tpl.js
├── test
├── etpl
├── lib
│ ├── esl-1.6.6.js
│ └── jasmine-1.3.0
│ │ ├── MIT.LICENSE
│ │ ├── jasmine-html.js
│ │ ├── jasmine.css
│ │ └── jasmine.js
├── performance
│ ├── artTemplate
│ │ ├── artTemplate.html
│ │ ├── artTemplate.js
│ │ └── template.html
│ ├── baiduTemplate
│ │ ├── baiduTemplate.html
│ │ ├── baiduTemplate.js
│ │ └── template.html
│ ├── compile-time-tpl.html
│ ├── compile-time.html
│ ├── dot
│ │ ├── dot.html
│ │ ├── dot.js
│ │ └── template.html
│ ├── ejs
│ │ ├── ejs.html
│ │ ├── ejs.js
│ │ └── template.html
│ ├── etpl
│ │ ├── etpl.html
│ │ └── template.html
│ ├── handlebars
│ │ ├── handlebars-1.1.2.js
│ │ ├── handlebars.html
│ │ └── template.html
│ ├── hogan
│ │ ├── hogan-2.0.0.js
│ │ ├── hogan.html
│ │ └── template.html
│ ├── juicer
│ │ ├── juicer.html
│ │ ├── juicer.js
│ │ └── template.html
│ ├── load-template.js
│ ├── loops.js
│ ├── mustache
│ │ ├── mustache.html
│ │ ├── mustache.js
│ │ └── template.html
│ ├── nunjucks
│ │ ├── nunjucks.html
│ │ ├── nunjucks.js
│ │ └── template.html
│ ├── render-data.js
│ └── render-time.html
├── run.html
├── server.js
└── spec
│ ├── amdplugin-absolute.text.html
│ ├── amdplugin-relative.text.html
│ ├── amdplugin.js
│ ├── amdplugin.text.html
│ ├── comment.spec.js
│ ├── comment.text.html
│ ├── engine.spec.js
│ ├── engine.text.html
│ ├── filter.spec.js
│ ├── filter.text.html
│ ├── for.spec.js
│ ├── for.text.html
│ ├── if.spec.js
│ ├── if.text.html
│ ├── import.spec.js
│ ├── import.text.html
│ ├── node.spec.js
│ ├── node.spec.text
│ ├── node
│ ├── footer.etpl
│ ├── header.etpl
│ ├── main.etpl
│ ├── master-main.etpl
│ ├── master-root.etpl
│ └── master-sub.etpl
│ ├── readTextSync.js
│ ├── target.spec.js
│ ├── target.text.html
│ ├── use.spec.js
│ ├── use.text.html
│ ├── var.spec.js
│ ├── var.text.html
│ ├── variableSubstitution.spec.js
│ └── variableSubstitution.text.html
└── tool
└── dist.sh
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 |
9 | [**.js]
10 | indent_style = space
11 | indent_size = 4
12 |
13 | [**.css]
14 | indent_style = space
15 | indent_size = 4
16 |
17 | [**.less]
18 | indent_style = space
19 | indent_size = 4
20 |
21 | [**.styl]
22 | indent_style = space
23 | indent_size = 4
24 |
25 | [**.html]
26 | indent_style = space
27 | indent_size = 4
28 |
29 | [**.tpl]
30 | indent_style = space
31 | indent_size = 4
32 |
33 | [**.json]
34 | indent_style = space
35 | indent_size = 4
36 |
37 | [*.md]
38 | trim_trailing_whitespace = false
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | .idea/
4 | npm-debug.log
5 | Thumbs.db
6 | .DS_Store
7 | *.swp
8 | *.gz
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /test
2 | /tool
3 | /dist
4 | npm-debug.log
5 | .DS_Store
6 | Thumbs.db
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.10
4 | branches:
5 | only:
6 | - master
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Baidu Inc.
2 | All rights reserved.
3 |
4 | Redistribution and use of this software in source and binary forms, with or
5 | without modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | Neither the name of Baidu Inc. nor the names of its contributors may be used
16 | to endorse or promote products derived from this software without specific
17 | prior written permission of Baidu Inc.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ETPL (Enterprise Template)
2 |
3 | [](https://travis-ci.org/ecomfe/etpl)
4 |
5 | ETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。
6 |
7 | [WebSite](http://ecomfe.github.io/etpl/)
8 |
9 | ## Download
10 |
11 | 除了通过github clone外,你可以通过`右键另存`的方式获得ETpl:
12 |
13 | - [压缩代码 (Compressed)](http://s1.bdstatic.com/r/www/cache/ecom/etpl/3-2-0/etpl.js)
14 | - [源码 (Source)](http://s1.bdstatic.com/r/www/cache/ecom/etpl/3-2-0/etpl.source.js)
15 |
16 | 也可以通过CDN引用:
17 |
18 | ```html
19 |
20 | ```
21 |
22 | ## Start
23 |
24 | ETpl可以在`CommonJS/AMD`的模块定义环境中使用,也能直接在页面下通过`script`标签引用。
25 |
26 |
27 | ### 浏览器环境
28 |
29 | 直接通过script标签引用,你可以获得一个全局的`etpl`变量
30 |
31 | ```html
32 |
33 | ```
34 |
35 | 在AMD环境的模块定义时,你可以通过`同步require`获得ETpl模块
36 |
37 | ```javascript
38 | define(function (require) {
39 | var etpl = require('etpl');
40 | });
41 | ```
42 |
43 | 在AMD环境,你也可以通过`异步require`获得ETpl模块
44 |
45 | ```javascript
46 | require([ 'etpl' ], function (etpl) {
47 | });
48 | ```
49 |
50 | *在AMD环境下,请确保你的require.config配置能够让Loader找到ETpl模块*
51 |
52 | ### Node.JS环境
53 |
54 | 你可以通过`npm`来安装ETpl
55 |
56 | ```
57 | $ npm install etpl
58 | ```
59 |
60 | 安装完成后,你就可以通过`require`获得一个ETpl模块,正常地使用它
61 |
62 | ```javascript
63 | var etpl = require('etpl');
64 | ```
65 |
66 | ### 使用
67 |
68 | 使用ETPL模块,对模板源代码进行编译,会能得到编译后的function
69 |
70 | ```javascript
71 | var render = etpl.compile('Hello ${name}!');
72 | ```
73 |
74 | 执行这个function,传入数据对象,就能得到模板执行的结果了
75 |
76 | ```javascript
77 | var text = render({ name: 'etpl' });
78 | ```
79 |
80 | 查看更多例子,或者对模板渲染结果有疑虑,就去ETPL的[example](http://ecomfe.github.io/etpl/example.html)看看吧。
81 |
82 |
83 | ## Documents
84 |
85 | 通过文档,你可以更详细地了解ETpl的语法格式、使用方法、API等内容。
86 |
87 | - [模板语法](doc/syntax.md)
88 | - [API](doc/api.md)
89 | - [配置参数](doc/config.md)
90 |
91 |
92 |
93 | ## Related
94 |
95 | * Sublime Text 语法高亮插件:[sublime-etpl](https://github.com/ecomfe/sublime-etpl)
96 | * vim 语法高亮插件:[vim-etpl](https://github.com/hushicai/vim-etpl)
97 | * Atom 语法高亮插件:[atom-etpl](https://github.com/ecomfe/atom-etpl)
98 |
99 |
100 |
101 | ## Compatibility
102 |
103 | ### ETpl3的新语法
104 |
105 | 我们认为,当前流行的通过`block`来表达模板继承中的变化,是更好的表达方式。所以在ETpl3中,我们优化了母版的语法,删除了`master`、`contentplacehoder`、`content`标签,引入了`block`标签。
106 |
107 | 对于ETpl2的使用者,我们提供一个[etpl2to3](https://github.com/ecomfe/etpl2to3)工具,能够帮助你平滑地将ETpl2的模板翻译成ETpl3。
108 |
109 |
110 | ### get
111 |
112 | ETpl2中,为了前向兼容,Engine的`get`方法可以根据target名称获取模板内容。
113 |
114 | ETpl3不再支持该方法,所有的模板都通过render来使用:
115 |
116 | - 直接使用engine实例的render方法
117 | - 调用renderer function
118 |
119 | 如果仍需要该功能,说明你正在维护一个遗留系统,并且没有很频繁的升级需求。请继续使用ETpl2。
120 |
121 |
122 | ### merge
123 |
124 | ETpl的前身是[ER框架](https://github.com/ecomfe/er)自带的简易模板引擎,其基本与前身保持兼容。但出于代码体积和使用频度的考虑,ETpl删除了`merge`API。如果想要该API,请在自己的应用中加入如下代码:
125 |
126 | ```javascript
127 | /**
128 | * 执行模板渲染,并将渲染后的字符串作为innerHTML填充到HTML元素中。
129 | * 兼容老版本的模板引擎api
130 | *
131 | * @param {HTMLElement} element 渲染字符串填充的HTML元素
132 | * @param {string} name target名称
133 | * @param {Object=} data 模板数据
134 | */
135 | etpl.merge = function ( element, name, data ) {
136 | if ( element ) {
137 | element.innerHTML = this.render( name, data );
138 | }
139 | };
140 | ```
141 |
142 |
--------------------------------------------------------------------------------
/doc/api.md:
--------------------------------------------------------------------------------
1 | ## API
2 |
3 | ETpl 是一个多 Engine 实例的模板引擎。初始化时其自动创建一个默认的 Engine 实例,并将其暴露。大多数应用场景可直接使用默认的引擎实例。
4 |
5 | ```javascript
6 | var etpl = require( 'etpl' );
7 | ```
8 |
9 | 你也可以通过 `new etpl.Engine` 初始化自己的引擎实例。 *不同的引擎实例可有效避免target命名冲突的问题。*
10 |
11 | ```javascript
12 | var etpl = require( 'etpl' );
13 | var etplEngine = new etpl.Engine();
14 | ```
15 |
16 | 引擎实例的初始化允许传入引擎参数。支持的引擎参数见下面的`config`方法。
17 |
18 | ```javascript
19 | var etpl = require( 'etpl' );
20 | var etplEngine = new etpl.Engine({
21 | commandOpen: '<%',
22 | commandClose: '%>'
23 | });
24 | ```
25 |
26 | 下面是 Engine 的实例方法列表。
27 |
28 | - [addCommand](#addCommand)
29 | - [addFilter](#addFilter)
30 | - [compile](#compile)
31 | - [config](#config)
32 | - [getRenderer](#getRenderer)
33 | - [load](#load)
34 | - [loadFromFile](#loadFromFile)
35 | - [parse](#parse)
36 | - [render](#render)
37 |
38 |
39 |
40 | ### addCommand
41 |
42 | `{void} addCommand( {string}name, {Object}command )`
43 |
44 | `常用` 自定义命令标签。
45 |
46 | - `{string}`name - 命令标签名称
47 | - `{Object}`command - 命令对象
48 |
49 | command 参数是一个对象,包含一些在命令标签编译时各个阶段的处理方法。
50 |
51 | - `init(context)` - 初始化
52 | - `open(context)` - 标签起始
53 | - `close(context)` - 标签闭合
54 | - `getRendererBody():string` - 生成编译代码
55 |
56 | ```javascript
57 | etpl.addCommand('dump', {
58 | init: function () {
59 | var match = this.value.match(/^\s*([a-z0-9_]+)\s*$/i);
60 | if (!match) {
61 | throw new Error('Invalid ' + this.type + ' syntax: ' + this.value);
62 | }
63 |
64 | this.name = match[1];
65 | this.cloneProps = ['name'];
66 | },
67 |
68 | open: function (context) {
69 | context.stack.top().addChild(this);
70 | },
71 |
72 | getRendererBody: function () {
73 | var util = etpl.util;
74 | var options = this.engine.options;
75 | return util.stringFormat(
76 | 'console.log({0});',
77 | util.compileVariable(options.variableOpen + this.name + options.variableClose, this.engine)
78 | );
79 | }
80 | });
81 | ```
82 |
83 |
84 | ### addFilter
85 |
86 | `{void} addFilter( {string}name, {function({string}, {...*}):string}filter )`
87 |
88 | `常用` 为默认引擎添加过滤器。过滤函数的第一个参数为过滤源字符串,其后的参数可由模板开发者传入。过滤函数必须返回string。
89 |
90 | - `{string}`name - 过滤器名称
91 | - `{Function}`filter - 过滤函数
92 |
93 | ```javascript
94 | etpl.addFilter( 'markdown', function ( source, useExtra ) {
95 | // ......
96 | } );
97 | ```
98 |
99 | ### compile
100 |
101 | `{Function} compile( {string}source )`
102 |
103 | `常用` 使用默认引擎编译模板。返回第一个target编译后的renderer函数。
104 |
105 | - `{string}`source - 模板源代码
106 |
107 | ```javascript
108 | var helloRenderer = etpl.compile( 'Hello ${name}!' );
109 | helloRenderer( {name: 'ETPL'} ); // Hello ETPL!
110 | ```
111 |
112 | ### config
113 |
114 | `{void} config( {Object}options )`
115 |
116 | `常用` 对默认引擎进行配置,配置参数将合并到引擎现有的参数中。[查看配置参数](config.md)。
117 |
118 | ```javascript
119 | etplEngine.config( {
120 | defaultFilter: ''
121 | } );
122 | ```
123 |
124 | ### getRenderer
125 |
126 | `{Function} getRenderer( {string}name )`
127 |
128 | `常用` 从默认引擎中,根据target名称获取编译后的renderer函数。
129 |
130 | - `{string}`name - target名称
131 |
132 | ```javascript
133 | etpl.compile( 'Hello ${name}!' );
134 | var helloRenderer = etpl.getRenderer( 'hello' );
135 | helloRenderer( {name: 'ETPL'} ); // Hello ETPL!
136 | ```
137 |
138 |
139 | ### load
140 |
141 | `{Function} load( {string}name )`
142 |
143 | `仅node环境` 加载并编译target文件
144 |
145 | - `{string}`name - target名称
146 |
147 | ```javascript
148 | var mainRenderer = etpl.load( 'main' );
149 | mainRenderer( {name: 'ETPL'} );
150 | ```
151 |
152 |
153 | ### loadFromFile
154 |
155 | `{Function} loadFromFile( {string}file )`
156 |
157 | `仅node环境` 加载并编译模板文件
158 |
159 | - `{string}`file - 模板文件路径
160 |
161 | ```javascript
162 | var mainRenderer = etpl.loadFromFile( path.resolve(__dirname, 'main.etpl') );
163 | mainRenderer( {name: 'ETPL'} );
164 | ```
165 |
166 |
167 | ### parse
168 |
169 | 同`compile`方法。该方法的存在是为了兼容老版本的模板引擎api,不建议使用。
170 |
171 |
172 | ### render
173 |
174 | `{string} render( {string}name, {Object}data )`
175 |
176 | 使用默认引擎执行模板渲染,返回渲染后的字符串。
177 |
178 | - `{string}`name - target名称
179 | - `{Object}`data - 模板数据。可以是plain object,也可以是带有 **{string}get({string}name)** 方法的对象
180 |
181 | ```javascript
182 | etpl.compile( 'Hello ${name}!' );
183 | etpl.render( 'hello', {name: 'ETPL'} ); // Hello ETPL!
184 | ```
185 |
186 |
187 |
--------------------------------------------------------------------------------
/doc/config.md:
--------------------------------------------------------------------------------
1 | ## Config
2 |
3 | - [commandOpen](#commandopen)
4 | - [commandClose](#commandclose)
5 | - [commandSyntax](#commandsyntax)
6 | - [variableOpen](#variableopen)
7 | - [variableClose](#variableclose)
8 | - [defaultFilter](#defaultfilter)
9 | - [strip](#strip)
10 | - [namingConflict](#namingconflict)
11 | - [missTarget](#misstarget)
12 | - [dir](#dir)
13 | - [extname](#extname)
14 | - [encoding](#encoding)
15 |
16 |
17 | 通过engine初始化时构造函数参数,或者engine实例的config方法,可以配置ETpl引擎的参数。
18 |
19 | ```javascript
20 | etplEngine.config( {
21 | strip: true
22 | } );
23 | ```
24 |
25 | 下面是ETpl支持的参数列表。
26 |
27 |
28 | ### commandOpen
29 |
30 | `string`
31 |
32 | 命令语法起始串,默认值为 **
40 |
41 |
42 | ### commandSyntax
43 |
44 | `RegExp`
45 |
46 | 命令语法格式。设置该参数时,正则需要包含3个matches:
47 |
48 | 1. 命令结束标记
49 | 2. 命令名称
50 | 3. 命令值
51 |
52 |
53 | ### variableOpen
54 |
55 | `string`,默认值为 *${*
56 |
57 | 变量语法起始串
58 |
59 |
60 | ### variableClose
61 |
62 | `string`
63 |
64 | 变量语法结束串,默认值为 *}*
65 |
66 |
67 | ### defaultFilter
68 |
69 | `string`
70 |
71 | 默认变量替换的filter,默认值为 *html*
72 |
73 |
74 | ### strip
75 |
76 | `boolean`
77 |
78 | 是否清除命令标签前后的空白字符,默认值为 *false*
79 |
80 |
81 | ### namingConflict
82 |
83 | `string`
84 |
85 | target名字冲突时的处理策略,值可以是:
86 |
87 | - `error`: 抛出错误。此项为默认值
88 | - `ignore`: 保留现有目标,忽略新目标
89 | - `override`: 覆盖现有目标
90 |
91 | ### missTarget
92 |
93 | `string`
94 |
95 | target不存在时的处理策略,值可以是:
96 |
97 | - `error`: 抛出错误
98 | - `ignore`: 静默处理,无错误。此项为默认值
99 |
100 |
101 | ### dir
102 |
103 | `string` `仅node环境有效`
104 |
105 | 模板文件目录,用于加载target文件时的路径查找,默认值为 *process.pwd()*
106 |
107 |
108 | ### extname
109 |
110 | `string` `仅node环境有效`
111 |
112 | 模板文件后缀名,默认值为 *.etpl*
113 |
114 |
115 | ### encoding
116 |
117 | `string` `仅node环境有效`
118 |
119 | 模板文件编码,默认值为 *UTF-8*
120 |
121 |
122 |
--------------------------------------------------------------------------------
/doc/syntax.md:
--------------------------------------------------------------------------------
1 | ## Syntax
2 |
3 |
4 | - [基础](#基础)
5 | - [语法形式](#语法形式)
6 | - [target](#target)
7 | - [变量替换](#变量替换)
8 | - [模板复用](#模板复用)
9 | - [import](#import)
10 | - [母版](#母版)
11 | - [引用代入](#引用代入)
12 | - [use](#use)
13 | - [分支与循环](#分支与循环)
14 | - [if](#if)
15 | - [for](#for)
16 | - [其他](其他)
17 | - [变量声明](#变量声明)
18 | - [内容块过滤](#内容块过滤)
19 |
20 |
21 | ### 基础
22 |
23 | #### 语法形式
24 |
25 | ETPL的指令标签默认为HTML注释的形式,在指令标签内允许声明 `指令起始`、`指令结束`和`注释`。
26 |
27 | `指令起始`的语法形式为: **。其中, *command-value* 的具体语法形式详情请参见各指令相关章节。
28 |
29 | ```html
30 |
31 |
32 |
33 | ```
34 |
35 | `指令结束`的语法形式为: **。
36 |
37 | ```html
38 |
39 |
40 |
41 | ```
42 |
43 | `注释`的语法形式为: **,注释指令在render时将不输出。
44 |
45 | ```html
46 |
47 | ```
48 |
49 | 如果不期望使用HTML注释形式的指令标签,可以通过config API可以配置指令标签的形式:
50 |
51 | ```javascript
52 | etpl.config({
53 | commandOpen: '<%',
54 | commandClose: '%>'
55 | });
56 |
57 | /*
58 | 配置指令标签的形式为“<% ... %>”,然后指令标签可以像下面一样声明:
59 | <% if: ${number} > 0 %>
60 | greater than zero
61 | <% /if %>
62 | */
63 | ```
64 |
65 | ##### 自动结束
66 |
67 | 为了减少开发者的工作量,部分指令标签支持`自动结束`,模板开发者无需手工编写`指令结束`。比如:当遇见target指令起始时,ETPL自动认为上一个target已经结束。
68 |
69 | 具体指令的`自动结束`支持,请参考相应指令相关章节。
70 |
71 |
72 | #### target
73 |
74 | `target`是ETPL的基本单元,其含义是 **一个模版片段** 。`target`可用于render,也可用于被其他`target`所import或use,也可以被其他`target`以母版的方式继承。
75 |
76 | 在target中可以任意编写`block`片段。`block`主要的作用是做为 **可被替换片段** ,在母版或引用代入功能中被使用。详细的使用方式请参考[模板复用](#模板复用)章节。
77 |
78 |
79 | ##### 语法
80 |
81 | target的语法形式为:
82 |
83 | target: target-name
84 | target: target-name(master=master-name)
85 |
86 |
87 | block的语法形式为:
88 |
89 | block: block-name
90 |
91 |
92 | ##### 自动结束
93 |
94 | target支持自动结束,当遇见 *target* 时自动结束。
95 |
96 | block不支持自动结束,必须手工编写`指令结束`。
97 |
98 | ##### 示例
99 |
100 | ```html
101 |
102 | Hello ETPL!
103 |
104 |
105 | Bye ETPL!
106 |
107 |
108 |
109 | Header Content
110 |
111 |
112 | ```
113 |
114 | ##### 匿名target
115 |
116 | 如果仅仅编写的是一个模板片段,可以省略`target`的声明。这样的编写方式与其他模板引擎类似,ETPL将默认生成匿名target,但模板片段将不可复用(不可被import或use,不可指定母版)。
117 |
118 | 匿名target应位于模板源码起始。下面例子中,位于其他target后的模板片段`Bye`将没有任何作用。
119 |
120 | ```
121 |
122 |
123 | Hello ${name}!
124 |
125 | Bye
126 | ```
127 |
128 | #### 变量替换
129 |
130 | 绝大多数模板引擎都支持变量替换功能。ETPL变量替换的语法为:
131 |
132 | ${variable-name}
133 | ${variable-name|filter-name}
134 | ${variable-name|filter-name(arguments)}
135 | ${variable-name|filter1|filter2(arguments)|filter3|...}
136 |
137 | variable-name支持`.`形式的property access。
138 |
139 | 编写模板时可以指定filter,默认使用html filter进行HTML转义。如果想要保留变量的原形式,需要手工指定使用名称为raw的filter,或者通过config API配置引擎的defaultFilter参数。
140 |
141 | ${myVariable|raw}
142 |
143 | ```javascript
144 | etpl.config({
145 | defaultFilter: ''
146 | });
147 | ```
148 |
149 | ETPL默认支持3种filters,可以通过引擎的`addFilter`方法自定义扩展filter。
150 |
151 | - html: html转义
152 | - url: url转义
153 | - raw: 不进行任何转义
154 |
155 |
156 | 变量替换支持多filter处理。filter之间以类似管道的方式,前一个filter的输出做为后一个filter的输入,向后传递。
157 |
158 | ```html
159 | ${myVariable|html|url}
160 | ```
161 |
162 | filter支持参数传递,参数可以使用动态数据。
163 |
164 | ```html
165 |
166 | ${myVariable|comma(3)}
167 | ${myVariable|comma(${commaLength})}
168 | ${myVariable|comma(${commaLength}+1)}
169 | ```
170 |
171 | 在变量替换中,引擎会默认将数据toString后传递给filter,以保证filter输入输出的类型一致性。如果filter期望接受的是原始数据,模板开发者需要通过前缀的`*`指定。
172 |
173 | ```html
174 |
175 | ${*myDate|dateFormat('yyyy-MM-dd')}
176 | ```
177 |
178 |
179 |
180 | ### 模板复用
181 |
182 | ETPL支持多种形式的模板复用方式,帮助模板开发者减少模板编写的重复劳动和维护成本。
183 |
184 |
185 | #### import
186 |
187 | 通过import指令,可以在当前位置插入指定target的源码。
188 |
189 | ##### 语法
190 |
191 | import的语法形式为:
192 |
193 | import: target-name
194 |
195 |
196 | ##### 示例
197 |
198 | ```html
199 |
200 | Hello ${name}!
201 |
202 |
203 |
204 | ```
205 |
206 | ##### 自动结束
207 |
208 | 如果不编写import的`指令结束`,其`指令起始`后立即自动结束。
209 |
210 | 如果需要使用import的[引用代入](#引用代入)功能,需要手工编写`指令结束`。
211 |
212 |
213 | #### 母版
214 |
215 |
216 | `target`声明时可以通过 **master=master-name** 指定一个继承的母版。并且通过自身的`block`指令,能够替换母版中同名`block`指令声明部分的内容。
217 |
218 | 模版继承(母版)功能允许你在一开始就对多个相似的模板从“结构”的角度进行抽象。继承自一个和你具有相同结构的母版,你可以无需再编写结构骨架部分,只需要编写结构内部的内容差异部分。
219 |
220 |
221 | ##### 示例
222 |
223 | ```html
224 |
225 |
226 | Hello ${name}!
227 |
228 |
229 |
230 |
231 |
232 | ```
233 |
234 | ##### 复杂示例
235 |
236 | ```html
237 |
238 |
239 | title
240 |
241 |
242 |
243 |
244 |
245 |
246 | title for has sidebar
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | Building WebKit from Xcode
255 |
256 |
257 | To build from within Xcode, you can use the WebKit workspace.
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 | To build from within Xcode, you can use the WebKit workspace.
266 |
267 | ```
268 |
269 |
270 | #### 引用代入
271 |
272 | 通过`import`指令引入一个`target`时,可以定制其`block`部分的内容。
273 |
274 | 如果有一个模板的大部分可以复用时,通过引用代入功能,你可以在引用时抹平差异,定制其中差异部分内容。当你面临结构种类数量爆炸时,引用代入提供了另一种更灵活的复用方式。
275 |
276 | 使用引用代入功能,import需要手工编写`指令结束`,并且import中只允许包含`block`,其他指令将被忽略。
277 |
278 | ##### 示例
279 |
280 | ```html
281 |
282 |
283 |
284 |
285 | list
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 | default main
296 |
297 | ```
298 |
299 |
300 |
301 | #### use
302 |
303 | 通过`use`指令,可以调用指定`target`,在当前位置插入其render后的结果。允许使用静态或动态数据指定数据项。
304 |
305 | 在面临多种不同的数据名称、相似的结构时,动态调用可以让你只编写一份代码,多次调用。
306 |
307 | ##### 语法
308 |
309 | use的语法形式为:
310 |
311 | use: target-name
312 | use: target-name(data-name=expression, data-name=expression)
313 |
314 |
315 | ##### 示例
316 |
317 | ```html
318 |
323 |
324 | ${main}[${sub}]
325 | ```
326 |
327 | ##### 自动结束
328 |
329 | use无需编写`指令结束`,其将在`指令起始`后立即自动结束。
330 |
331 |
332 |
333 | ### 分支与循环
334 |
335 | #### if
336 |
337 | ETPL提供了分支的支持,相关指令有`if`、`elif`、`else`。
338 |
339 | ##### 语法
340 |
341 | if的语法形式为:
342 |
343 | if: conditional-expression
344 |
345 | elif的语法形式为:
346 |
347 | elif: conditional-expression
348 |
349 | else的语法形式为:
350 |
351 | else
352 |
353 | conditional-expression中可以使用动态数据,通过`${variable}`的形式,可以使用模板render的data。`${variable}`支持`.`的property accessor。
354 |
355 | ##### 自动结束
356 |
357 | if指令不支持自动结束,必须手工编写指令结束``。
358 |
359 | ##### 示例
360 |
361 | ```html
362 |
363 | larger than zero
364 |
365 | zero
366 |
367 | invalid
368 |
369 | ```
370 |
371 | ##### 自动结束
372 |
373 | if指令不支持自动结束,必须手工编写`指令结束`。
374 |
375 | ```html
376 |
377 | ```
378 |
379 | #### for
380 |
381 | 通过for指令的支持,可以实现对Array和Object的遍历。Array为正序遍历,Object为不保证顺序的forin。
382 |
383 | ##### 语法
384 |
385 | for的语法形式为:
386 |
387 | for: ${variable} as ${item-variable}
388 | for: ${variable} as ${item-variable}, ${index-variable}
389 | for: ${variable} as ${item-variable}, ${key-variable}
390 |
391 | 其中,`${variable}`为想要遍历的对象,支持`.`形式的property access。在遍历过程中,声明的`${item-variable}`和`${index-variable}`,分别代表数据项和索引(遍历Object时为键名)。
392 |
393 | ##### 示例
394 |
395 | ```html
396 |
397 |
398 | - ${index}: ${person.name}
399 |
400 |
401 | ```
402 |
403 | ##### 自动结束
404 |
405 | for指令不支持自动结束,必须手工编写`指令结束`。
406 |
407 | ```html
408 |
409 | ```
410 |
411 | ### 其他
412 |
413 | #### 变量声明
414 |
415 | 通过`var`指令可以在模板内部声明一个变量。声明的变量在整个`target`内有效。
416 |
417 | ##### 语法
418 |
419 | var的语法形式为:
420 |
421 | var: var-name=expression
422 |
423 | `expression`中可以使用静态或动态数据。
424 |
425 | ##### 示例
426 |
427 | ```html
428 |
429 |
430 |
431 | ```
432 |
433 | ##### 自动结束
434 |
435 | var无需编写`指令结束`,其将在`指令起始`后立即自动结束。
436 |
437 |
438 | #### 内容块过滤
439 |
440 | 除了在变量替换中可以使用filter进行处理,ETPL还可以通过`filter`指令,使用指定的filter对一个模板内容块进行过滤处理。
441 |
442 | ##### 语法
443 |
444 | filter的语法形式为:
445 |
446 | filter: filter-name
447 | filter: filter-name(arguments)
448 |
449 | ##### 示例
450 |
451 | 下面的例子假定使用者实现了一个markdown的filter
452 |
453 | ```html
454 |
455 | ## markdown document
456 |
457 | This is the content, also I can use `${variables}`
458 |
459 | ```
460 |
461 | ##### 自动结束
462 |
463 | filter指令不支持自动结束,必须手工编写`指令结束`。
464 |
465 | ```html
466 |
467 | ```
468 |
469 | ##### 表达式的特殊语法( expression 和 condition-expression )
470 |
471 | `if`指令、`use`指令和`var`指令中的表达式( expression 或者 condition-expression )部分,可以使用动态的数据:
472 |
473 | ```html
474 | He's name is zhangsan.
475 |
476 | ${personName}
477 |
478 |
479 | ${name}
480 | ```
481 |
482 | 如果传入的数据是:
483 |
484 | ```json
485 | {
486 | "arr": ["zhangsan"],
487 | "index": 0
488 | }
489 | ```
490 |
491 | 输出的结果为:
492 |
493 | ```
494 | He's name is zhangsan.
495 |
496 | zhangsan
497 |
498 | zhangsan
499 | ```
500 |
501 | 其它语法示例:
502 |
503 | ```html
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 | ```
516 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ETPL (Enterprise Template)
3 | * Copyright 2013 Baidu Inc. All rights reserved.
4 | *
5 | * @file Node入口
6 | * @author firede(firede@firede.us)
7 | */
8 |
9 | module.exports = require('./src/node');
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "etpl",
3 | "version": "3.2.0",
4 | "contributors": [
5 | { "name": "erik", "email": "errorrik@gmail.com" },
6 | { "name": "otakustay", "email": "otakustay@gmail.com" },
7 | { "name": "firede", "email": "firede@firede.us" }
8 | ],
9 | "main": "main",
10 | "homepage": "http://ecomfe.github.io/etpl/",
11 | "repository": "git://github.com/ecomfe/etpl",
12 | "description": "ETPL是一个强复用、灵活、高性能的JavaScript模板引擎,适用于浏览器端或Node环境中视图的生成。",
13 | "scripts": {
14 | "test": "jasmine-node test/spec"
15 | },
16 | "devDependencies": {
17 | "jasmine-node": "1.14.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ETPL (Enterprise Template)
3 | * Copyright 2016 Baidu Inc. All rights reserved.
4 | *
5 | * @file node环境支持模块,主要提供import和master的依赖文件自动加载功能
6 | * @author errorrik(errorrik@gmail.com)
7 | */
8 |
9 |
10 | /* eslint-env node */
11 |
12 | var etpl = require('./main');
13 | var path = require('path');
14 | var fs = require('fs');
15 |
16 | /**
17 | * 加载模板文件
18 | *
19 | * @param {string} file 文件路径
20 | * @return {function(Object):string} renderer函数
21 | */
22 | etpl.Engine.prototype.loadFromFile = function (file) {
23 | var load = this.load.bind(this);
24 | var targets = this.targets;
25 |
26 | /* jshint -W054 */
27 | var renderer = new Function('return ""');
28 | /* jshint +W054 */
29 |
30 | var encoding = this.options.encoding || 'UTF-8';
31 | var source = fs.readFileSync(file, encoding);
32 |
33 | var parseInfo = etpl.util.parseSource(source, this);
34 | var targetNames = parseInfo.targets;
35 |
36 |
37 | parseInfo.deps.forEach(function (dep) {
38 | if (!targets[dep]) {
39 | load(dep);
40 | }
41 | });
42 |
43 | if (targetNames.length) {
44 | renderer = targets[targetNames[0]].getRenderer();
45 | }
46 |
47 | return renderer;
48 | };
49 |
50 | /**
51 | * 加载target
52 | *
53 | * @param {string} targetName target名称
54 | * @return {function(Object):string} renderer函数
55 | */
56 | etpl.Engine.prototype.load = function (targetName) {
57 | if (this.targets[targetName]) {
58 | return this.targets[targetName].getRenderer();
59 | }
60 |
61 | return this.loadFromFile(resolveTargetPath(targetName, this));
62 | };
63 |
64 |
65 | /**
66 | * 获取 target 对应的文件路径
67 | *
68 | * @inner
69 | * @param {string} targetName target名称
70 | * @param {Engine} engine etpl引擎
71 | * @return {string}
72 | */
73 | function resolveTargetPath(targetName, engine) {
74 | var dir = engine.options.dir || process.pwd();
75 | var ext = engine.options.extname || '.etpl';
76 |
77 | return path.resolve(dir, targetName) + ext;
78 | }
79 |
80 |
81 | module.exports = exports = etpl;
82 |
--------------------------------------------------------------------------------
/src/tpl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ETPL (Enterprise Template)
3 | * Copyright 2013 Baidu Inc. All rights reserved.
4 | *
5 | * @file 加载模板的amd模块
6 | * @author errorrik(errorrik@gmail.com)
7 | */
8 |
9 | /* global ActiveXObject */
10 | define(
11 | function (require, exports, module) {
12 | var etpl = require('.');
13 |
14 | return {
15 | load: function (resourceId, req, load) {
16 | var xhr = window.XMLHttpRequest
17 | ? new XMLHttpRequest()
18 | : new ActiveXObject('Microsoft.XMLHTTP');
19 |
20 | xhr.open('GET', req.toUrl(resourceId), true);
21 |
22 | xhr.onreadystatechange = function () {
23 | if (xhr.readyState === 4) {
24 | if (xhr.status >= 200 && xhr.status < 300) {
25 | var source = xhr.responseText;
26 | var moduleConfig = module.config();
27 | if (moduleConfig.autoCompile
28 | || moduleConfig.autoCompile == null
29 | ) {
30 | etpl.compile(source);
31 | }
32 |
33 | load(source);
34 | }
35 |
36 | /* jshint -W054 */
37 | xhr.onreadystatechange = new Function();
38 | /* jshint +W054 */
39 | xhr = null;
40 | }
41 | };
42 |
43 | xhr.send(null);
44 | }
45 | };
46 | }
47 | );
48 |
--------------------------------------------------------------------------------
/test/etpl:
--------------------------------------------------------------------------------
1 | ../src
--------------------------------------------------------------------------------
/test/lib/jasmine-1.3.0/MIT.LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008-2011 Pivotal Labs
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/test/lib/jasmine-1.3.0/jasmine.css:
--------------------------------------------------------------------------------
1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
2 |
3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 | #HTMLReporter a { text-decoration: none; }
5 | #HTMLReporter a:hover { text-decoration: underline; }
6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; }
9 | #HTMLReporter .version { color: #aaaaaa; }
10 | #HTMLReporter .banner { margin-top: 14px; }
11 | #HTMLReporter .duration { color: #aaaaaa; float: right; }
12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; }
15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; }
17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; }
21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
24 | #HTMLReporter .runningAlert { background-color: #666666; }
25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; }
26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; }
27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
28 | #HTMLReporter .passingAlert { background-color: #a6b779; }
29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
30 | #HTMLReporter .failingAlert { background-color: #cf867e; }
31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; }
32 | #HTMLReporter .results { margin-top: 14px; }
33 | #HTMLReporter #details { display: none; }
34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
38 | #HTMLReporter.showDetails .summary { display: none; }
39 | #HTMLReporter.showDetails #details { display: block; }
40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
41 | #HTMLReporter .summary { margin-top: 14px; }
42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; }
45 | #HTMLReporter .description + .suite { margin-top: 0; }
46 | #HTMLReporter .suite { margin-top: 14px; }
47 | #HTMLReporter .suite a { color: #333333; }
48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; }
49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
51 | #HTMLReporter .resultMessage span.result { display: block; }
52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
53 |
54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; }
56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
61 | #TrivialReporter .runner.running { background-color: yellow; }
62 | #TrivialReporter .options { text-align: right; font-size: .8em; }
63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
64 | #TrivialReporter .suite .suite { margin: 5px; }
65 | #TrivialReporter .suite.passed { background-color: #dfd; }
66 | #TrivialReporter .suite.failed { background-color: #fdd; }
67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
71 | #TrivialReporter .spec.skipped { background-color: #bbb; }
72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
73 | #TrivialReporter .passed { background-color: #cfc; display: none; }
74 | #TrivialReporter .failed { background-color: #fbb; }
75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
77 | #TrivialReporter .resultMessage .mismatch { color: black; }
78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; }
82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
83 |
--------------------------------------------------------------------------------
/test/performance/artTemplate/artTemplate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - artTemplate
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
32 |
33 |
--------------------------------------------------------------------------------
/test/performance/artTemplate/artTemplate.js:
--------------------------------------------------------------------------------
1 | /*!art-template - Template Engine | http://aui.github.com/artTemplate/*/
2 | !function(){function a(a){return a.replace(t,"").replace(u,",").replace(v,"").replace(w,"").replace(x,"").split(/^$|,+/)}function b(a){return"'"+a.replace(/('|\\)/g,"\\$1").replace(/\r/g,"\\r").replace(/\n/g,"\\n")+"'"}function c(c,d){function e(a){return m+=a.split(/\n/).length-1,k&&(a=a.replace(/\s+/g," ").replace(//g,"")),a&&(a=s[1]+b(a)+s[2]+"\n"),a}function f(b){var c=m;if(j?b=j(b,d):g&&(b=b.replace(/\n/g,function(){return m++,"$line="+m+";"})),0===b.indexOf("=")){var e=l&&!/^=[=#]/.test(b);if(b=b.replace(/^=[=#]?|[\s;]*$/g,""),e){var f=b.replace(/\s*\([^\)]+\)/,"");n[f]||/^(include|print)$/.test(f)||(b="$escape("+b+")")}else b="$string("+b+")";b=s[1]+b+s[2]}return g&&(b="$line="+c+";"+b),r(a(b),function(a){if(a&&!p[a]){var b;b="print"===a?u:"include"===a?v:n[a]?"$utils."+a:o[a]?"$helpers."+a:"$data."+a,w+=a+"="+b+",",p[a]=!0}}),b+"\n"}var g=d.debug,h=d.openTag,i=d.closeTag,j=d.parser,k=d.compress,l=d.escape,m=1,p={$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1},q="".trim,s=q?["$out='';","$out+=",";","$out"]:["$out=[];","$out.push(",");","$out.join('')"],t=q?"$out+=text;return $out;":"$out.push(text);",u="function(){var text=''.concat.apply('',arguments);"+t+"}",v="function(filename,data){data=data||$data;var text=$utils.$include(filename,data,$filename);"+t+"}",w="'use strict';var $utils=this,$helpers=$utils.$helpers,"+(g?"$line=0,":""),x=s[0],y="return new String("+s[3]+");";r(c.split(h),function(a){a=a.split(i);var b=a[0],c=a[1];1===a.length?x+=e(b):(x+=f(b),c&&(x+=e(c)))});var z=w+x+y;g&&(z="try{"+z+"}catch(e){throw {filename:$filename,name:'Render Error',message:e.message,line:$line,source:"+b(c)+".split(/\\n/)[$line-1].replace(/^\\s+/,'')};}");try{var A=new Function("$data","$filename",z);return A.prototype=n,A}catch(B){throw B.temp="function anonymous($data,$filename) {"+z+"}",B}}var d=function(a,b){return"string"==typeof b?q(b,{filename:a}):g(a,b)};d.version="3.0.0",d.config=function(a,b){e[a]=b};var e=d.defaults={openTag:"<%",closeTag:"%>",escape:!0,cache:!0,compress:!1,parser:null},f=d.cache={};d.render=function(a,b){return q(a,b)};var g=d.renderFile=function(a,b){var c=d.get(a)||p({filename:a,name:"Render Error",message:"Template not found"});return b?c(b):c};d.get=function(a){var b;if(f[a])b=f[a];else if("object"==typeof document){var c=document.getElementById(a);if(c){var d=(c.value||c.innerHTML).replace(/^\s*|\s*$/g,"");b=q(d,{filename:a})}}return b};var h=function(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?h(a.call(a)):""),a},i={"<":"<",">":">",'"':""","'":"'","&":"&"},j=function(a){return i[a]},k=function(a){return h(a).replace(/&(?![\w#]+;)|[<>"']/g,j)},l=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},m=function(a,b){var c,d;if(l(a))for(c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)},n=d.utils={$helpers:{},$include:g,$string:h,$escape:k,$each:m};d.helper=function(a,b){o[a]=b};var o=d.helpers=n.$helpers;d.onerror=function(a){var b="Template Error\n\n";for(var c in a)b+="<"+c+">\n"+a[c]+"\n\n";"object"==typeof console&&console.error(b)};var p=function(a){return d.onerror(a),function(){return"{Template Error}"}},q=d.compile=function(a,b){function d(c){try{return new i(c,h)+""}catch(d){return b.debug?p(d)():(b.debug=!0,q(a,b)(c))}}b=b||{};for(var g in e)void 0===b[g]&&(b[g]=e[g]);var h=b.filename;try{var i=c(a,b)}catch(j){return j.filename=h||"anonymous",j.name="Syntax Error",p(j)}return d.prototype=i.prototype,d.toString=function(){return i.toString()},h&&b.cache&&(f[h]=d),d},r=n.$each,s="break,case,catch,continue,debugger,default,delete,do,else,false,finally,for,function,if,in,instanceof,new,null,return,switch,this,throw,true,try,typeof,var,void,while,with,abstract,boolean,byte,char,class,const,double,enum,export,extends,final,float,goto,implements,import,int,interface,long,native,package,private,protected,public,short,static,super,synchronized,throws,transient,volatile,arguments,let,yield,undefined",t=/\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g,u=/[^\w$]+/g,v=new RegExp(["\\b"+s.replace(/,/g,"\\b|\\b")+"\\b"].join("|"),"g"),w=/^\d[^,]*|,\d[^,]*/g,x=/^,+|,+$/g;"function"==typeof define?define(function(){return d}):"undefined"!=typeof exports?module.exports=d:this.template=d}();
--------------------------------------------------------------------------------
/test/performance/artTemplate/template.html:
--------------------------------------------------------------------------------
1 |
2 | <% for(i = 0, len = persons.length; i < len; i++) {%>
3 | - <%= persons[i].name %> [<%= persons[i].email %>] is <%= persons[i].age %> ages.
4 | <%}%>
5 |
--------------------------------------------------------------------------------
/test/performance/baiduTemplate/baiduTemplate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - baiduTemplate
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/baiduTemplate/baiduTemplate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * baiduTemplate简单好用的Javascript模板引擎 1.0.6 版本
3 | * http://baidufe.github.com/BaiduTemplate
4 | * 开源协议:BSD License
5 | * 浏览器环境占用命名空间 baidu.template ,nodejs环境直接安装 npm install baidutemplate
6 | * @param str{String} dom结点ID,或者模板string
7 | * @param data{Object} 需要渲染的json对象,可以为空。当data为{}时,仍然返回html。
8 | * @return 如果无data,直接返回编译后的函数;如果有data,返回html。
9 | * @author wangxiao
10 | * @email 1988wangxiao@gmail.com
11 | */
12 |
13 | ;(function(window){
14 |
15 | //取得浏览器环境的baidu命名空间,非浏览器环境符合commonjs规范exports出去
16 | //修正在nodejs环境下,采用baidu.template变量名
17 | var baidu = typeof module === 'undefined' ? (window.baidu = window.baidu || {}) : module.exports;
18 |
19 | //模板函数(放置于baidu.template命名空间下)
20 | baidu.template = function(str, data){
21 |
22 | //检查是否有该id的元素存在,如果有元素则获取元素的innerHTML/value,否则认为字符串为模板
23 | var fn = (function(){
24 |
25 | //判断如果没有document,则为非浏览器环境
26 | if(!window.document){
27 | return bt._compile(str);
28 | };
29 |
30 | //HTML5规定ID可以由任何不包含空格字符的字符串组成
31 | var element = document.getElementById(str);
32 | if (element) {
33 |
34 | //取到对应id的dom,缓存其编译后的HTML模板函数
35 | if (bt.cache[str]) {
36 | return bt.cache[str];
37 | };
38 |
39 | //textarea或input则取value,其它情况取innerHTML
40 | var html = /^(textarea|input)$/i.test(element.nodeName) ? element.value : element.innerHTML;
41 | return bt._compile(html);
42 |
43 | }else{
44 |
45 | //是模板字符串,则生成一个函数
46 | //如果直接传入字符串作为模板,则可能变化过多,因此不考虑缓存
47 | return bt._compile(str);
48 | };
49 |
50 | })();
51 |
52 | //有数据则返回HTML字符串,没有数据则返回函数 支持data={}的情况
53 | var result = bt._isObject(data) ? fn( data ) : fn;
54 | fn = null;
55 |
56 | return result;
57 | };
58 |
59 | //取得命名空间 baidu.template
60 | var bt = baidu.template;
61 |
62 | //标记当前版本
63 | bt.versions = bt.versions || [];
64 | bt.versions.push('1.0.6');
65 |
66 | //缓存 将对应id模板生成的函数缓存下来。
67 | bt.cache = {};
68 |
69 | //自定义分隔符,可以含有正则中的字符,可以是HTML注释开头
70 | bt.LEFT_DELIMITER = bt.LEFT_DELIMITER||'<%';
71 | bt.RIGHT_DELIMITER = bt.RIGHT_DELIMITER||'%>';
72 |
73 | //自定义默认是否转义,默认为默认自动转义
74 | bt.ESCAPE = true;
75 |
76 | //HTML转义
77 | bt._encodeHTML = function (source) {
78 | return String(source)
79 | .replace(/&/g,'&')
80 | .replace(//g,'>')
82 | .replace(/\\/g,'\')
83 | .replace(/"/g,'"')
84 | .replace(/'/g,''');
85 | };
86 |
87 | //转义影响正则的字符
88 | bt._encodeReg = function (source) {
89 | return String(source).replace(/([.*+?^=!:${}()|[\]/\\])/g,'\\$1');
90 | };
91 |
92 | //转义UI UI变量使用在HTML页面标签onclick等事件函数参数中
93 | bt._encodeEventHTML = function (source) {
94 | return String(source)
95 | .replace(/&/g,'&')
96 | .replace(//g,'>')
98 | .replace(/"/g,'"')
99 | .replace(/'/g,''')
100 | .replace(/\\\\/g,'\\')
101 | .replace(/\\\//g,'\/')
102 | .replace(/\\n/g,'\n')
103 | .replace(/\\r/g,'\r');
104 | };
105 |
106 | //将字符串拼接生成函数,即编译过程(compile)
107 | bt._compile = function(str){
108 | var funBody = "var _template_fun_array=[];\nvar fn=(function(__data__){\nvar _template_varName='';\nfor(name in __data__){\n_template_varName+=('var '+name+'=__data__[\"'+name+'\"];');\n};\neval(_template_varName);\n_template_fun_array.push('"+bt._analysisStr(str)+"');\n_template_varName=null;\n})(_template_object);\nfn = null;\nreturn _template_fun_array.join('');\n";
109 | return new Function("_template_object",funBody);
110 | };
111 |
112 | //判断是否是Object类型
113 | bt._isObject = function (source) {
114 | return 'function' === typeof source || !!(source && 'object' === typeof source);
115 | };
116 |
117 | //解析模板字符串
118 | bt._analysisStr = function(str){
119 |
120 | //取得分隔符
121 | var _left_ = bt.LEFT_DELIMITER;
122 | var _right_ = bt.RIGHT_DELIMITER;
123 |
124 | //对分隔符进行转义,支持正则中的元字符,可以是HTML注释
125 | var _left = bt._encodeReg(_left_);
126 | var _right = bt._encodeReg(_right_);
127 |
128 | str = String(str)
129 |
130 | //去掉分隔符中js注释
131 | .replace(new RegExp("("+_left+"[^"+_right+"]*)//.*\n","g"), "$1")
132 |
133 | //去掉注释内容 <%* 这里可以任意的注释 *%>
134 | //默认支持HTML注释,将HTML注释匹配掉的原因是用户有可能用 来做分割符
135 | .replace(new RegExp("", "g"),"")
136 | .replace(new RegExp(_left+"\\*.*?\\*"+_right, "g"),"")
137 |
138 | //把所有换行去掉 \r回车符 \t制表符 \n换行符
139 | .replace(new RegExp("[\\r\\t\\n]","g"), "")
140 |
141 | //用来处理非分隔符内部的内容中含有 斜杠 \ 单引号 ‘ ,处理办法为HTML转义
142 | .replace(new RegExp(_left+"(?:(?!"+_right+")[\\s\\S])*"+_right+"|((?:(?!"+_left+")[\\s\\S])+)","g"),function (item, $1) {
143 | var str = '';
144 | if($1){
145 |
146 | //将 斜杠 单引 HTML转义
147 | str = $1.replace(/\\/g,"\").replace(/'/g,''');
148 | while(/<[^<]*?'[^<]*?>/g.test(str)){
149 |
150 | //将标签内的单引号转义为\r 结合最后一步,替换为\'
151 | str = str.replace(/(<[^<]*?)'([^<]*?>)/g,'$1\r$2')
152 | };
153 | }else{
154 | str = item;
155 | }
156 | return str ;
157 | });
158 |
159 |
160 | str = str
161 | //定义变量,如果没有分号,需要容错 <%var val='test'%>
162 | .replace(new RegExp("("+_left+"[\\s]*?var[\\s]*?.*?[\\s]*?[^;])[\\s]*?"+_right,"g"),"$1;"+_right_)
163 |
164 | //对变量后面的分号做容错(包括转义模式 如<%:h=value%>) <%=value;%> 排除掉函数的情况 <%fun1();%> 排除定义变量情况 <%var val='test';%>
165 | .replace(new RegExp("("+_left+":?[hvu]?[\\s]*?=[\\s]*?[^;|"+_right+"]*?);[\\s]*?"+_right,"g"),"$1"+_right_)
166 |
167 | //按照 <% 分割为一个个数组,再用 \t 和在一起,相当于将 <% 替换为 \t
168 | //将模板按照<%分为一段一段的,再在每段的结尾加入 \t,即用 \t 将每个模板片段前面分隔开
169 | .split(_left_).join("\t");
170 |
171 | //支持用户配置默认是否自动转义
172 | if(bt.ESCAPE){
173 | str = str
174 |
175 | //找到 \t=任意一个字符%> 替换为 ‘,任意字符,'
176 | //即替换简单变量 \t=data%> 替换为 ',data,'
177 | //默认HTML转义 也支持HTML转义写法<%:h=value%>
178 | .replace(new RegExp("\\t=(.*?)"+_right,"g"),"',typeof($1) === 'undefined'?'':baidu.template._encodeHTML($1),'");
179 | }else{
180 | str = str
181 |
182 | //默认不转义HTML转义
183 | .replace(new RegExp("\\t=(.*?)"+_right,"g"),"',typeof($1) === 'undefined'?'':$1,'");
184 | };
185 |
186 | str = str
187 |
188 | //支持HTML转义写法<%:h=value%>
189 | .replace(new RegExp("\\t:h=(.*?)"+_right,"g"),"',typeof($1) === 'undefined'?'':baidu.template._encodeHTML($1),'")
190 |
191 | //支持不转义写法 <%:=value%>和<%-value%>
192 | .replace(new RegExp("\\t(?::=|-)(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':$1,'")
193 |
194 | //支持url转义 <%:u=value%>
195 | .replace(new RegExp("\\t:u=(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':encodeURIComponent($1),'")
196 |
197 | //支持UI 变量使用在HTML页面标签onclick等事件函数参数中 <%:v=value%>
198 | .replace(new RegExp("\\t:v=(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':baidu.template._encodeEventHTML($1),'")
199 |
200 | //将字符串按照 \t 分成为数组,在用'); 将其合并,即替换掉结尾的 \t 为 ');
201 | //在if,for等语句前面加上 '); ,形成 ');if ');for 的形式
202 | .split("\t").join("');")
203 |
204 | //将 %> 替换为_template_fun_array.push('
205 | //即去掉结尾符,生成函数中的push方法
206 | //如:if(list.length=5){%>',list[4],'
');}
207 | //会被替换为 if(list.length=5){_template_fun_array.push('',list[4],'
');}
208 | .split(_right_).join("_template_fun_array.push('")
209 |
210 | //将 \r 替换为 \
211 | .split("\r").join("\\'");
212 |
213 | return str;
214 | };
215 |
216 | })(window);
217 |
--------------------------------------------------------------------------------
/test/performance/baiduTemplate/template.html:
--------------------------------------------------------------------------------
1 |
2 | <% for(var i = 0, len = persons.length; i < len; i++) {%>
3 | - <%= persons[i].name %> [<%= persons[i].email %>] is <%= persons[i].age %> ages.
4 | <% } %>
5 |
--------------------------------------------------------------------------------
/test/performance/compile-time.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ETPL Compile Time Test
6 |
7 |
8 |
9 |
10 |
34 |
35 |
--------------------------------------------------------------------------------
/test/performance/dot/dot.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - doT
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/dot/dot.js:
--------------------------------------------------------------------------------
1 | /* Laura Doktorova https://github.com/olado/doT */
2 | (function(){function o(){var a={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},b=/&(?!#?\w+;)|<|>|"|'|\//g;return function(){return this?this.replace(b,function(c){return a[c]||c}):this}}function p(a,b,c){return(typeof b==="string"?b:b.toString()).replace(a.define||i,function(l,e,f,g){if(e.indexOf("def.")===0)e=e.substring(4);if(!(e in c))if(f===":"){a.defineParams&&g.replace(a.defineParams,function(n,h,d){c[e]={arg:h,text:d}});e in c||(c[e]=g)}else(new Function("def","def['"+
3 | e+"']="+g))(c);return""}).replace(a.use||i,function(l,e){if(a.useParams)e=e.replace(a.useParams,function(g,n,h,d){if(c[h]&&c[h].arg&&d){g=(h+":"+d).replace(/'|\\/g,"_");c.__exp=c.__exp||{};c.__exp[g]=c[h].text.replace(RegExp("(^|[^\\w$])"+c[h].arg+"([^\\w$])","g"),"$1"+d+"$2");return n+"def.__exp['"+g+"']"}});var f=(new Function("def","return "+e))(c);return f?p(a,f,c):f})}function m(a){return a.replace(/\\('|\\)/g,"$1").replace(/[\r\t\n]/g," ")}var j={version:"1.0.1",templateSettings:{evaluate:/\{\{([\s\S]+?(\}?)+)\}\}/g,
4 | interpolate:/\{\{=([\s\S]+?)\}\}/g,encode:/\{\{!([\s\S]+?)\}\}/g,use:/\{\{#([\s\S]+?)\}\}/g,useParams:/(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,define:/\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,defineParams:/^\s*([\w$]+):([\s\S]+)/,conditional:/\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,iterate:/\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,varname:"it",strip:true,append:true,selfcontained:false},template:undefined,
5 | compile:undefined},q;if(typeof module!=="undefined"&&module.exports)module.exports=j;else if(typeof define==="function"&&define.amd)define(function(){return j});else{q=function(){return this||(0,eval)("this")}();q.doT=j}String.prototype.encodeHTML=o();var r={append:{start:"'+(",end:")+'",endencode:"||'').toString().encodeHTML()+'"},split:{start:"';out+=(",end:");out+='",endencode:"||'').toString().encodeHTML();out+='"}},i=/$^/;j.template=function(a,b,c){b=b||j.templateSettings;var l=b.append?r.append:
6 | r.split,e,f=0,g;a=b.use||b.define?p(b,a,c||{}):a;a=("var out='"+(b.strip?a.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ").replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""):a).replace(/'|\\/g,"\\$&").replace(b.interpolate||i,function(h,d){return l.start+m(d)+l.end}).replace(b.encode||i,function(h,d){e=true;return l.start+m(d)+l.endencode}).replace(b.conditional||i,function(h,d,k){return d?k?"';}else if("+m(k)+"){out+='":"';}else{out+='":k?"';if("+m(k)+"){out+='":"';}out+='"}).replace(b.iterate||i,function(h,
7 | d,k,s){if(!d)return"';} } out+='";f+=1;g=s||"i"+f;d=m(d);return"';var arr"+f+"="+d+";if(arr"+f+"){var "+k+","+g+"=-1,l"+f+"=arr"+f+".length-1;while("+g+"
2 | {{~it.persons :person}}
3 | {{!person.name}} [{{!person.email}}] is {{!person.age}} ages.
4 | {{~}}
5 |
--------------------------------------------------------------------------------
/test/performance/ejs/ejs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - EJS
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/ejs/ejs.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 |
4 | var rsplit = function(string, regex) {
5 | var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit;
6 | while (result != null)
7 | {
8 | first_idx = result.index; last_idx = regex.lastIndex;
9 | if ((first_idx) != 0)
10 | {
11 | first_bit = string.substring(0,first_idx);
12 | retArr.push(string.substring(0,first_idx));
13 | string = string.slice(first_idx);
14 | }
15 | retArr.push(result[0]);
16 | string = string.slice(result[0].length);
17 | result = regex.exec(string);
18 | }
19 | if (! string == '')
20 | {
21 | retArr.push(string);
22 | }
23 | return retArr;
24 | },
25 | chop = function(string){
26 | return string.substr(0, string.length - 1);
27 | },
28 | extend = function(d, s){
29 | for(var n in s){
30 | if(s.hasOwnProperty(n)) d[n] = s[n]
31 | }
32 | }
33 |
34 |
35 | EJS = function( options ){
36 | options = typeof options == "string" ? {view: options} : options
37 | this.set_options(options);
38 | if(options.precompiled){
39 | this.template = {};
40 | this.template.process = options.precompiled;
41 | EJS.update(this.name, this);
42 | return;
43 | }
44 | if(options.element)
45 | {
46 | if(typeof options.element == 'string'){
47 | var name = options.element
48 | options.element = document.getElementById( options.element )
49 | if(options.element == null) throw name+'does not exist!'
50 | }
51 | if(options.element.value){
52 | this.text = options.element.value
53 | }else{
54 | this.text = options.element.innerHTML
55 | }
56 | this.name = options.element.id
57 | this.type = '['
58 | }else if(options.url){
59 | options.url = EJS.endExt(options.url, this.extMatch);
60 | this.name = this.name ? this.name : options.url;
61 | var url = options.url
62 | //options.view = options.absolute_url || options.view || options.;
63 | var template = EJS.get(this.name /*url*/, this.cache);
64 | if (template) return template;
65 | if (template == EJS.INVALID_PATH) return null;
66 | try{
67 | this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() ));
68 | }catch(e){}
69 |
70 | if(this.text == null){
71 | throw( {type: 'EJS', message: 'There is no template at '+url} );
72 | }
73 | //this.name = url;
74 | }
75 | var template = new EJS.Compiler(this.text, this.type);
76 |
77 | template.compile(options, this.name);
78 |
79 |
80 | EJS.update(this.name, this);
81 | this.template = template;
82 | };
83 | /* @Prototype*/
84 | EJS.prototype = {
85 | /**
86 | * Renders an object with extra view helpers attached to the view.
87 | * @param {Object} object data to be rendered
88 | * @param {Object} extra_helpers an object with additonal view helpers
89 | * @return {String} returns the result of the string
90 | */
91 | render : function(object, extra_helpers){
92 | object = object || {};
93 | this._extra_helpers = extra_helpers;
94 | var v = new EJS.Helpers(object, extra_helpers || {});
95 | return this.template.process.call(object, object,v);
96 | },
97 | update : function(element, options){
98 | if(typeof element == 'string'){
99 | element = document.getElementById(element)
100 | }
101 | if(options == null){
102 | _template = this;
103 | return function(object){
104 | EJS.prototype.update.call(_template, element, object)
105 | }
106 | }
107 | if(typeof options == 'string'){
108 | params = {}
109 | params.url = options
110 | _template = this;
111 | params.onComplete = function(request){
112 | var object = eval( request.responseText )
113 | EJS.prototype.update.call(_template, element, object)
114 | }
115 | EJS.ajax_request(params)
116 | }else
117 | {
118 | element.innerHTML = this.render(options)
119 | }
120 | },
121 | out : function(){
122 | return this.template.out;
123 | },
124 | /**
125 | * Sets options on this view to be rendered with.
126 | * @param {Object} options
127 | */
128 | set_options : function(options){
129 | this.type = options.type || EJS.type;
130 | this.cache = options.cache != null ? options.cache : EJS.cache;
131 | this.text = options.text || null;
132 | this.name = options.name || null;
133 | this.ext = options.ext || EJS.ext;
134 | this.extMatch = new RegExp(this.ext.replace(/\./, '\.'));
135 | }
136 | };
137 | EJS.endExt = function(path, match){
138 | if(!path) return null;
139 | match.lastIndex = 0
140 | return path+ (match.test(path) ? '' : this.ext )
141 | }
142 |
143 |
144 |
145 |
146 | /* @Static*/
147 | EJS.Scanner = function(source, left, right) {
148 |
149 | extend(this,
150 | {left_delimiter: left +'%',
151 | right_delimiter: '%'+right,
152 | double_left: left+'%%',
153 | double_right: '%%'+right,
154 | left_equal: left+'%=',
155 | left_comment: left+'%#'})
156 |
157 | this.SplitRegexp = left=='[' ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)') ;
158 |
159 | this.source = source;
160 | this.stag = null;
161 | this.lines = 0;
162 | };
163 |
164 | EJS.Scanner.to_text = function(input){
165 | if(input == null || input === undefined)
166 | return '';
167 | if(input instanceof Date)
168 | return input.toDateString();
169 | if(input.toString)
170 | return input.toString();
171 | return '';
172 | };
173 |
174 | EJS.Scanner.prototype = {
175 | scan: function(block) {
176 | scanline = this.scanline;
177 | regex = this.SplitRegexp;
178 | if (! this.source == '')
179 | {
180 | var source_split = rsplit(this.source, /\n/);
181 | for(var i=0; i 0)
228 | {
229 | for (var i=0; i 0)
303 | {
304 | buff.push(put_cmd + '"' + clean(content) + '")');
305 | }
306 | content = '';
307 | break;
308 | case scanner.double_left:
309 | content = content + scanner.left_delimiter;
310 | break;
311 | default:
312 | content = content + token;
313 | break;
314 | }
315 | }
316 | else {
317 | switch(token) {
318 | case scanner.right_delimiter:
319 | switch(scanner.stag) {
320 | case scanner.left_delimiter:
321 | if (content[content.length - 1] == '\n')
322 | {
323 | content = chop(content);
324 | buff.push(content);
325 | buff.cr();
326 | }
327 | else {
328 | buff.push(content);
329 | }
330 | break;
331 | case scanner.left_equal:
332 | buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))");
333 | break;
334 | }
335 | scanner.stag = null;
336 | content = '';
337 | break;
338 | case scanner.double_right:
339 | content = content + scanner.right_delimiter;
340 | break;
341 | default:
342 | content = content + token;
343 | break;
344 | }
345 | }
346 | });
347 | if (content.length > 0)
348 | {
349 | // Chould be content.dump in Ruby
350 | buff.push(put_cmd + '"' + clean(content) + '")');
351 | }
352 | buff.close();
353 | this.out = buff.script + ";";
354 | var to_be_evaled = '/*'+name+'*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};";
355 |
356 | try{
357 | eval(to_be_evaled);
358 | }catch(e){
359 | if(typeof JSLINT != 'undefined'){
360 | JSLINT(this.out);
361 | for(var i = 0; i < JSLINT.errors.length; i++){
362 | var error = JSLINT.errors[i];
363 | if(error.reason != "Unnecessary semicolon."){
364 | error.line++;
365 | var e = new Error();
366 | e.lineNumber = error.line;
367 | e.message = error.reason;
368 | if(options.view)
369 | e.fileName = options.view;
370 | throw e;
371 | }
372 | }
373 | }else{
374 | throw e;
375 | }
376 | }
377 | }
378 | };
379 |
380 |
381 | //type, cache, folder
382 | /**
383 | * Sets default options for all views
384 | * @param {Object} options Set view with the following options
385 | *
386 | Option | Default | Description |
387 |
388 | type |
389 | '<' |
390 | type of magic tags. Options are '<' or '['
391 | |
392 |
393 |
394 | cache |
395 | true in production mode, false in other modes |
396 | true to cache template.
397 | |
398 |
399 |
400 | *
401 | */
402 | EJS.config = function(options){
403 | EJS.cache = options.cache != null ? options.cache : EJS.cache;
404 | EJS.type = options.type != null ? options.type : EJS.type;
405 | EJS.ext = options.ext != null ? options.ext : EJS.ext;
406 |
407 | var templates_directory = EJS.templates_directory || {}; //nice and private container
408 | EJS.templates_directory = templates_directory;
409 | EJS.get = function(path, cache){
410 | if(cache == false) return null;
411 | if(templates_directory[path]) return templates_directory[path];
412 | return null;
413 | };
414 |
415 | EJS.update = function(path, template) {
416 | if(path == null) return;
417 | templates_directory[path] = template ;
418 | };
419 |
420 | EJS.INVALID_PATH = -1;
421 | };
422 | EJS.config( {cache: true, type: '<', ext: '.ejs' } );
423 |
424 |
425 |
426 | /**
427 | * @constructor
428 | * By adding functions to EJS.Helpers.prototype, those functions will be available in the
429 | * views.
430 | * @init Creates a view helper. This function is called internally. You should never call it.
431 | * @param {Object} data The data passed to the view. Helpers have access to it through this._data
432 | */
433 | EJS.Helpers = function(data, extras){
434 | this._data = data;
435 | this._extras = extras;
436 | extend(this, extras );
437 | };
438 | /* @prototype*/
439 | EJS.Helpers.prototype = {
440 | /**
441 | * Renders a new view. If data is passed in, uses that to render the view.
442 | * @param {Object} options standard options passed to a new view.
443 | * @param {optional:Object} data
444 | * @return {String}
445 | */
446 | view: function(options, data, helpers){
447 | if(!helpers) helpers = this._extras
448 | if(!data) data = this._data;
449 | return new EJS(options).render(data, helpers);
450 | },
451 | /**
452 | * For a given value, tries to create a human representation.
453 | * @param {Object} input the value being converted.
454 | * @param {Object} null_text what text should be present if input == null or undefined, defaults to ''
455 | * @return {String}
456 | */
457 | to_text: function(input, null_text) {
458 | if(input == null || input === undefined) return null_text || '';
459 | if(input instanceof Date) return input.toDateString();
460 | if(input.toString) return input.toString().replace(/\n/g, '
').replace(/''/g, "'");
461 | return '';
462 | }
463 | };
464 | EJS.newRequest = function(){
465 | var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
466 | for(var i = 0; i < factories.length; i++) {
467 | try {
468 | var request = factories[i]();
469 | if (request != null) return request;
470 | }
471 | catch(e) { continue;}
472 | }
473 | }
474 |
475 | EJS.request = function(path){
476 | var request = new EJS.newRequest()
477 | request.open("GET", path, false);
478 |
479 | try{request.send(null);}
480 | catch(e){return null;}
481 |
482 | if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;
483 |
484 | return request.responseText
485 | }
486 | EJS.ajax_request = function(params){
487 | params.method = ( params.method ? params.method : 'GET')
488 |
489 | var request = new EJS.newRequest();
490 | request.onreadystatechange = function(){
491 | if(request.readyState == 4){
492 | if(request.status == 200){
493 | params.onComplete(request)
494 | }else
495 | {
496 | params.onComplete(request)
497 | }
498 | }
499 | }
500 | request.open(params.method, params.url)
501 | request.send(null)
502 | }
503 |
504 |
505 | })();
--------------------------------------------------------------------------------
/test/performance/ejs/template.html:
--------------------------------------------------------------------------------
1 |
2 | <% for(var i = 0, len = persons.length; i < len; i++) {%>
3 | - <%= persons[i].name %> [<%= persons[i].email %>] is <%= persons[i].age %> ages.
4 | <% } %>
5 |
--------------------------------------------------------------------------------
/test/performance/etpl/etpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - ETPL
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/etpl/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | - ${person.name} [${person.email}] is ${person.age} ages.
4 |
5 |
--------------------------------------------------------------------------------
/test/performance/handlebars/handlebars.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - mustache
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/handlebars/template.html:
--------------------------------------------------------------------------------
1 |
2 | {{#persons}}
3 | - {{name}} [{{email}}] is {{age}} ages.
4 | {{/persons}}
5 |
--------------------------------------------------------------------------------
/test/performance/hogan/hogan-2.0.0.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Twitter, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 |
17 |
18 | var Hogan = {};
19 |
20 | (function (Hogan, useArrayBuffer) {
21 | Hogan.Template = function (renderFunc, text, compiler, options) {
22 | this.r = renderFunc || this.r;
23 | this.c = compiler;
24 | this.options = options;
25 | this.text = text || '';
26 | this.buf = (useArrayBuffer) ? [] : '';
27 | }
28 |
29 | Hogan.Template.prototype = {
30 | // render: replaced by generated code.
31 | r: function (context, partials, indent) { return ''; },
32 |
33 | // variable escaping
34 | v: hoganEscape,
35 |
36 | // triple stache
37 | t: coerceToString,
38 |
39 | render: function render(context, partials, indent) {
40 | return this.ri([context], partials || {}, indent);
41 | },
42 |
43 | // render internal -- a hook for overrides that catches partials too
44 | ri: function (context, partials, indent) {
45 | return this.r(context, partials, indent);
46 | },
47 |
48 | // tries to find a partial in the curent scope and render it
49 | rp: function(name, context, partials, indent) {
50 | var partial = partials[name];
51 |
52 | if (!partial) {
53 | return '';
54 | }
55 |
56 | if (this.c && typeof partial == 'string') {
57 | partial = this.c.compile(partial, this.options);
58 | }
59 |
60 | return partial.ri(context, partials, indent);
61 | },
62 |
63 | // render a section
64 | rs: function(context, partials, section) {
65 | var tail = context[context.length - 1];
66 |
67 | if (!isArray(tail)) {
68 | section(context, partials, this);
69 | return;
70 | }
71 |
72 | for (var i = 0; i < tail.length; i++) {
73 | context.push(tail[i]);
74 | section(context, partials, this);
75 | context.pop();
76 | }
77 | },
78 |
79 | // maybe start a section
80 | s: function(val, ctx, partials, inverted, start, end, tags) {
81 | var pass;
82 |
83 | if (isArray(val) && val.length === 0) {
84 | return false;
85 | }
86 |
87 | if (typeof val == 'function') {
88 | val = this.ls(val, ctx, partials, inverted, start, end, tags);
89 | }
90 |
91 | pass = (val === '') || !!val;
92 |
93 | if (!inverted && pass && ctx) {
94 | ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
95 | }
96 |
97 | return pass;
98 | },
99 |
100 | // find values with dotted names
101 | d: function(key, ctx, partials, returnFound) {
102 | var names = key.split('.'),
103 | val = this.f(names[0], ctx, partials, returnFound),
104 | cx = null;
105 |
106 | if (key === '.' && isArray(ctx[ctx.length - 2])) {
107 | return ctx[ctx.length - 1];
108 | }
109 |
110 | for (var i = 1; i < names.length; i++) {
111 | if (val && typeof val == 'object' && names[i] in val) {
112 | cx = val;
113 | val = val[names[i]];
114 | } else {
115 | val = '';
116 | }
117 | }
118 |
119 | if (returnFound && !val) {
120 | return false;
121 | }
122 |
123 | if (!returnFound && typeof val == 'function') {
124 | ctx.push(cx);
125 | val = this.lv(val, ctx, partials);
126 | ctx.pop();
127 | }
128 |
129 | return val;
130 | },
131 |
132 | // find values with normal names
133 | f: function(key, ctx, partials, returnFound) {
134 | var val = false,
135 | v = null,
136 | found = false;
137 |
138 | for (var i = ctx.length - 1; i >= 0; i--) {
139 | v = ctx[i];
140 | if (v && typeof v == 'object' && key in v) {
141 | val = v[key];
142 | found = true;
143 | break;
144 | }
145 | }
146 |
147 | if (!found) {
148 | return (returnFound) ? false : "";
149 | }
150 |
151 | if (!returnFound && typeof val == 'function') {
152 | val = this.lv(val, ctx, partials);
153 | }
154 |
155 | return val;
156 | },
157 |
158 | // higher order templates
159 | ho: function(val, cx, partials, text, tags) {
160 | var compiler = this.c;
161 | var options = this.options;
162 | options.delimiters = tags;
163 | var text = val.call(cx, text);
164 | text = (text == null) ? String(text) : text.toString();
165 | this.b(compiler.compile(text, options).render(cx, partials));
166 | return false;
167 | },
168 |
169 | // template result buffering
170 | b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
171 | function(s) { this.buf += s; },
172 | fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
173 | function() { var r = this.buf; this.buf = ''; return r; },
174 |
175 | // lambda replace section
176 | ls: function(val, ctx, partials, inverted, start, end, tags) {
177 | var cx = ctx[ctx.length - 1],
178 | t = null;
179 |
180 | if (!inverted && this.c && val.length > 0) {
181 | return this.ho(val, cx, partials, this.text.substring(start, end), tags);
182 | }
183 |
184 | t = val.call(cx);
185 |
186 | if (typeof t == 'function') {
187 | if (inverted) {
188 | return true;
189 | } else if (this.c) {
190 | return this.ho(t, cx, partials, this.text.substring(start, end), tags);
191 | }
192 | }
193 |
194 | return t;
195 | },
196 |
197 | // lambda replace variable
198 | lv: function(val, ctx, partials) {
199 | var cx = ctx[ctx.length - 1];
200 | var result = val.call(cx);
201 |
202 | if (typeof result == 'function') {
203 | result = coerceToString(result.call(cx));
204 | if (this.c && ~result.indexOf("{\u007B")) {
205 | return this.c.compile(result, this.options).render(cx, partials);
206 | }
207 | }
208 |
209 | return coerceToString(result);
210 | }
211 |
212 | };
213 |
214 | var rAmp = /&/g,
215 | rLt = //g,
217 | rApos =/\'/g,
218 | rQuot = /\"/g,
219 | hChars =/[&<>\"\']/;
220 |
221 |
222 | function coerceToString(val) {
223 | return String((val === null || val === undefined) ? '' : val);
224 | }
225 |
226 | function hoganEscape(str) {
227 | str = coerceToString(str);
228 | return hChars.test(str) ?
229 | str
230 | .replace(rAmp,'&')
231 | .replace(rLt,'<')
232 | .replace(rGt,'>')
233 | .replace(rApos,''')
234 | .replace(rQuot, '"') :
235 | str;
236 | }
237 |
238 | var isArray = Array.isArray || function(a) {
239 | return Object.prototype.toString.call(a) === '[object Array]';
240 | };
241 |
242 | })(typeof exports !== 'undefined' ? exports : Hogan);
243 |
244 |
245 |
246 |
247 | (function (Hogan) {
248 | // Setup regex assignments
249 | // remove whitespace according to Mustache spec
250 | var rIsWhitespace = /\S/,
251 | rQuot = /\"/g,
252 | rNewline = /\n/g,
253 | rCr = /\r/g,
254 | rSlash = /\\/g,
255 | tagTypes = {
256 | '#': 1, '^': 2, '/': 3, '!': 4, '>': 5,
257 | '<': 6, '=': 7, '_v': 8, '{': 9, '&': 10
258 | };
259 |
260 | Hogan.scan = function scan(text, delimiters) {
261 | var len = text.length,
262 | IN_TEXT = 0,
263 | IN_TAG_TYPE = 1,
264 | IN_TAG = 2,
265 | state = IN_TEXT,
266 | tagType = null,
267 | tag = null,
268 | buf = '',
269 | tokens = [],
270 | seenTag = false,
271 | i = 0,
272 | lineStart = 0,
273 | otag = '{{',
274 | ctag = '}}';
275 |
276 | function addBuf() {
277 | if (buf.length > 0) {
278 | tokens.push(new String(buf));
279 | buf = '';
280 | }
281 | }
282 |
283 | function lineIsWhitespace() {
284 | var isAllWhitespace = true;
285 | for (var j = lineStart; j < tokens.length; j++) {
286 | isAllWhitespace =
287 | (tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) ||
288 | (!tokens[j].tag && tokens[j].match(rIsWhitespace) === null);
289 | if (!isAllWhitespace) {
290 | return false;
291 | }
292 | }
293 |
294 | return isAllWhitespace;
295 | }
296 |
297 | function filterLine(haveSeenTag, noNewLine) {
298 | addBuf();
299 |
300 | if (haveSeenTag && lineIsWhitespace()) {
301 | for (var j = lineStart, next; j < tokens.length; j++) {
302 | if (!tokens[j].tag) {
303 | if ((next = tokens[j+1]) && next.tag == '>') {
304 | // set indent to token value
305 | next.indent = tokens[j].toString()
306 | }
307 | tokens.splice(j, 1);
308 | }
309 | }
310 | } else if (!noNewLine) {
311 | tokens.push({tag:'\n'});
312 | }
313 |
314 | seenTag = false;
315 | lineStart = tokens.length;
316 | }
317 |
318 | function changeDelimiters(text, index) {
319 | var close = '=' + ctag,
320 | closeIndex = text.indexOf(close, index),
321 | delimiters = trim(
322 | text.substring(text.indexOf('=', index) + 1, closeIndex)
323 | ).split(' ');
324 |
325 | otag = delimiters[0];
326 | ctag = delimiters[1];
327 |
328 | return closeIndex + close.length - 1;
329 | }
330 |
331 | if (delimiters) {
332 | delimiters = delimiters.split(' ');
333 | otag = delimiters[0];
334 | ctag = delimiters[1];
335 | }
336 |
337 | for (i = 0; i < len; i++) {
338 | if (state == IN_TEXT) {
339 | if (tagChange(otag, text, i)) {
340 | --i;
341 | addBuf();
342 | state = IN_TAG_TYPE;
343 | } else {
344 | if (text.charAt(i) == '\n') {
345 | filterLine(seenTag);
346 | } else {
347 | buf += text.charAt(i);
348 | }
349 | }
350 | } else if (state == IN_TAG_TYPE) {
351 | i += otag.length - 1;
352 | tag = tagTypes[text.charAt(i + 1)];
353 | tagType = tag ? text.charAt(i + 1) : '_v';
354 | if (tagType == '=') {
355 | i = changeDelimiters(text, i);
356 | state = IN_TEXT;
357 | } else {
358 | if (tag) {
359 | i++;
360 | }
361 | state = IN_TAG;
362 | }
363 | seenTag = i;
364 | } else {
365 | if (tagChange(ctag, text, i)) {
366 | tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
367 | i: (tagType == '/') ? seenTag - ctag.length : i + otag.length});
368 | buf = '';
369 | i += ctag.length - 1;
370 | state = IN_TEXT;
371 | if (tagType == '{') {
372 | if (ctag == '}}') {
373 | i++;
374 | } else {
375 | cleanTripleStache(tokens[tokens.length - 1]);
376 | }
377 | }
378 | } else {
379 | buf += text.charAt(i);
380 | }
381 | }
382 | }
383 |
384 | filterLine(seenTag, true);
385 |
386 | return tokens;
387 | }
388 |
389 | function cleanTripleStache(token) {
390 | if (token.n.substr(token.n.length - 1) === '}') {
391 | token.n = token.n.substring(0, token.n.length - 1);
392 | }
393 | }
394 |
395 | function trim(s) {
396 | if (s.trim) {
397 | return s.trim();
398 | }
399 |
400 | return s.replace(/^\s*|\s*$/g, '');
401 | }
402 |
403 | function tagChange(tag, text, index) {
404 | if (text.charAt(index) != tag.charAt(0)) {
405 | return false;
406 | }
407 |
408 | for (var i = 1, l = tag.length; i < l; i++) {
409 | if (text.charAt(index + i) != tag.charAt(i)) {
410 | return false;
411 | }
412 | }
413 |
414 | return true;
415 | }
416 |
417 | function buildTree(tokens, kind, stack, customTags) {
418 | var instructions = [],
419 | opener = null,
420 | token = null;
421 |
422 | while (tokens.length > 0) {
423 | token = tokens.shift();
424 | if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) {
425 | stack.push(token);
426 | token.nodes = buildTree(tokens, token.tag, stack, customTags);
427 | instructions.push(token);
428 | } else if (token.tag == '/') {
429 | if (stack.length === 0) {
430 | throw new Error('Closing tag without opener: /' + token.n);
431 | }
432 | opener = stack.pop();
433 | if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
434 | throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
435 | }
436 | opener.end = token.i;
437 | return instructions;
438 | } else {
439 | instructions.push(token);
440 | }
441 | }
442 |
443 | if (stack.length > 0) {
444 | throw new Error('missing closing tag: ' + stack.pop().n);
445 | }
446 |
447 | return instructions;
448 | }
449 |
450 | function isOpener(token, tags) {
451 | for (var i = 0, l = tags.length; i < l; i++) {
452 | if (tags[i].o == token.n) {
453 | token.tag = '#';
454 | return true;
455 | }
456 | }
457 | }
458 |
459 | function isCloser(close, open, tags) {
460 | for (var i = 0, l = tags.length; i < l; i++) {
461 | if (tags[i].c == close && tags[i].o == open) {
462 | return true;
463 | }
464 | }
465 | }
466 |
467 | Hogan.generate = function (tree, text, options) {
468 | var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
469 | if (options.asString) {
470 | return 'function(c,p,i){' + code + ';}';
471 | }
472 |
473 | return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options);
474 | }
475 |
476 | function esc(s) {
477 | return s.replace(rSlash, '\\\\')
478 | .replace(rQuot, '\\\"')
479 | .replace(rNewline, '\\n')
480 | .replace(rCr, '\\r');
481 | }
482 |
483 | function chooseMethod(s) {
484 | return (~s.indexOf('.')) ? 'd' : 'f';
485 | }
486 |
487 | function walk(tree) {
488 | var code = '';
489 | for (var i = 0, l = tree.length; i < l; i++) {
490 | var tag = tree[i].tag;
491 | if (tag == '#') {
492 | code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n),
493 | tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag);
494 | } else if (tag == '^') {
495 | code += invertedSection(tree[i].nodes, tree[i].n,
496 | chooseMethod(tree[i].n));
497 | } else if (tag == '<' || tag == '>') {
498 | code += partial(tree[i]);
499 | } else if (tag == '{' || tag == '&') {
500 | code += tripleStache(tree[i].n, chooseMethod(tree[i].n));
501 | } else if (tag == '\n') {
502 | code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i'));
503 | } else if (tag == '_v') {
504 | code += variable(tree[i].n, chooseMethod(tree[i].n));
505 | } else if (tag === undefined) {
506 | code += text('"' + esc(tree[i]) + '"');
507 | }
508 | }
509 | return code;
510 | }
511 |
512 | function section(nodes, id, method, start, end, tags) {
513 | return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' +
514 | 'c,p,0,' + start + ',' + end + ',"' + tags + '")){' +
515 | '_.rs(c,p,' +
516 | 'function(c,p,_){' +
517 | walk(nodes) +
518 | '});c.pop();}';
519 | }
520 |
521 | function invertedSection(nodes, id, method) {
522 | return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
523 | walk(nodes) +
524 | '};';
525 | }
526 |
527 | function partial(tok) {
528 | return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
529 | }
530 |
531 | function tripleStache(id, method) {
532 | return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));';
533 | }
534 |
535 | function variable(id, method) {
536 | return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));';
537 | }
538 |
539 | function text(id) {
540 | return '_.b(' + id + ');';
541 | }
542 |
543 | Hogan.parse = function(tokens, text, options) {
544 | options = options || {};
545 | return buildTree(tokens, '', [], options.sectionTags || []);
546 | },
547 |
548 | Hogan.cache = {};
549 |
550 | Hogan.compile = function(text, options) {
551 | // options
552 | //
553 | // asString: false (default)
554 | //
555 | // sectionTags: [{o: '_foo', c: 'foo'}]
556 | // An array of object with o and c fields that indicate names for custom
557 | // section tags. The example above allows parsing of {{_foo}}{{/foo}}.
558 | //
559 | // delimiters: A string that overrides the default delimiters.
560 | // Example: "<% %>"
561 | //
562 | options = options || {};
563 |
564 | var key = text + '||' + !!options.asString;
565 |
566 | var t = this.cache[key];
567 |
568 | if (t) {
569 | return t;
570 | }
571 |
572 | t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
573 | return this.cache[key] = t;
574 | };
575 | })(typeof exports !== 'undefined' ? exports : Hogan);
576 |
577 |
--------------------------------------------------------------------------------
/test/performance/hogan/hogan.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - mustache
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/hogan/template.html:
--------------------------------------------------------------------------------
1 |
2 | {{#persons}}
3 | - {{name}} [{{email}}] is {{age}} ages.
4 | {{/persons}}
5 |
--------------------------------------------------------------------------------
/test/performance/juicer/juicer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - juicer
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/juicer/juicer.js:
--------------------------------------------------------------------------------
1 | (function(){var c=function(){var e=[].slice.call(arguments);e.push(c.options);if(e[0].match(/^\s*#([\w:\-\.]+)\s*$/igm)){e[0].replace(/^\s*#([\w:\-\.]+)\s*$/igm,function(h,i){var f=document;var g=f&&f.getElementById(i);e[0]=g?(g.value||g.innerHTML):h;});}if(arguments.length==1){return c.compile.apply(c,e);}if(arguments.length>=2){return c.to_html.apply(c,e);}};var d={escapehash:{"<":"<",">":">","&":"&",'"':""","'":"'","/":"/"},escapereplace:function(e){return d.escapehash[e];},escaping:function(e){return typeof(e)!=="string"?e:e.replace(/[&<>"]/igm,this.escapereplace);},detection:function(e){return typeof(e)==="undefined"?"":e;}};var b=function(e){if(typeof(console)!=="undefined"){if(console.warn){console.warn(e);return;}if(console.log){console.log(e);return;}}throw (e);};var a=function(h,f){h=h!==Object(h)?{}:h;if(h.__proto__){h.__proto__=f;return h;}var g=function(){};var j=Object.create?Object.create(f):new (g.prototype=f,g);for(var e in h){if(h.hasOwnProperty(e)){j[e]=h[e];}}return j;};c.__cache={};c.version="0.6.5-stable";c.settings={};c.tags={operationOpen:"{@",operationClose:"}",interpolateOpen:"\\${",interpolateClose:"}",noneencodeOpen:"\\$\\${",noneencodeClose:"}",commentOpen:"\\{#",commentClose:"\\}"};c.options={cache:true,strip:true,errorhandling:true,detection:true,_method:a({__escapehtml:d,__throw:b,__juicer:c},{})};c.tagInit=function(){var f=c.tags.operationOpen+"each\\s*([^}]*?)\\s*as\\s*(\\w*?)\\s*(,\\s*\\w*?)?"+c.tags.operationClose;var h=c.tags.operationOpen+"\\/each"+c.tags.operationClose;var i=c.tags.operationOpen+"if\\s*([^}]*?)"+c.tags.operationClose;var j=c.tags.operationOpen+"\\/if"+c.tags.operationClose;var n=c.tags.operationOpen+"else"+c.tags.operationClose;var o=c.tags.operationOpen+"else if\\s*([^}]*?)"+c.tags.operationClose;var k=c.tags.interpolateOpen+"([\\s\\S]+?)"+c.tags.interpolateClose;var l=c.tags.noneencodeOpen+"([\\s\\S]+?)"+c.tags.noneencodeClose;var m=c.tags.commentOpen+"[^}]*?"+c.tags.commentClose;var g=c.tags.operationOpen+"each\\s*(\\w*?)\\s*in\\s*range\\(([^}]+?)\\s*,\\s*([^}]+?)\\)"+c.tags.operationClose;var e=c.tags.operationOpen+"include\\s*([^}]*?)\\s*,\\s*([^}]*?)"+c.tags.operationClose;c.settings.forstart=new RegExp(f,"igm");c.settings.forend=new RegExp(h,"igm");c.settings.ifstart=new RegExp(i,"igm");c.settings.ifend=new RegExp(j,"igm");c.settings.elsestart=new RegExp(n,"igm");c.settings.elseifstart=new RegExp(o,"igm");c.settings.interpolate=new RegExp(k,"igm");c.settings.noneencode=new RegExp(l,"igm");c.settings.inlinecomment=new RegExp(m,"igm");c.settings.rangestart=new RegExp(g,"igm");c.settings.include=new RegExp(e,"igm");};c.tagInit();c.set=function(f,j){var h=this;var e=function(i){return i.replace(/[\$\(\)\[\]\+\^\{\}\?\*\|\.]/igm,function(l){return"\\"+l;});};var k=function(l,m){var i=l.match(/^tag::(.*)$/i);if(i){h.tags[i[1]]=e(m);h.tagInit();return;}h.options[l]=m;};if(arguments.length===2){k(f,j);return;}if(f===Object(f)){for(var g in f){if(f.hasOwnProperty(g)){k(g,f[g]);}}}};c.register=function(g,f){var e=this.options._method;if(e.hasOwnProperty(g)){return false;}return e[g]=f;};c.unregister=function(f){var e=this.options._method;if(e.hasOwnProperty(f)){return delete e[f];}};c.template=function(e){var f=this;this.options=e;this.__interpolate=function(g,l,i){var h=g.split("|"),k=h[0]||"",j;if(h.length>1){g=h.shift();j=h.shift().split(",");k="_method."+j.shift()+".call({}, "+[g].concat(j)+")";}return"<%= "+(l?"_method.__escapehtml.escaping":"")+"("+(!i||i.detection!==false?"_method.__escapehtml.detection":"")+"("+k+")) %>";};this.__removeShell=function(h,g){var i=0;h=h.replace(c.settings.forstart,function(n,k,m,l){var m=m||"value",l=l&&l.substr(1);var j="i"+i++;return"<% ~function() {for(var "+j+" in "+k+") {if("+k+".hasOwnProperty("+j+")) {var "+m+"="+k+"["+j+"];"+(l?("var "+l+"="+j+";"):"")+" %>";}).replace(c.settings.forend,"<% }}}(); %>").replace(c.settings.ifstart,function(j,k){return"<% if("+k+") { %>";}).replace(c.settings.ifend,"<% } %>").replace(c.settings.elsestart,function(j){return"<% } else { %>";}).replace(c.settings.elseifstart,function(j,k){return"<% } else if("+k+") { %>";}).replace(c.settings.noneencode,function(k,j){return f.__interpolate(j,false,g);}).replace(c.settings.interpolate,function(k,j){return f.__interpolate(j,true,g);}).replace(c.settings.inlinecomment,"").replace(c.settings.rangestart,function(m,l,n,k){var j="j"+i++;return"<% ~function() {for(var "+j+"="+n+";"+j+"<"+k+";"+j+"++) {{var "+l+"="+j+"; %>";}).replace(c.settings.include,function(l,j,k){return"<%= _method.__juicer("+j+", "+k+"); %>";});if(!g||g.errorhandling!==false){h="<% try { %>"+h;h+='<% } catch(e) {_method.__throw("Juicer Render Exception: "+e.message);} %>';}return h;};this.__toNative=function(h,g){return this.__convert(h,!g||g.strip);};this.__lexicalAnalyze=function(k){var j=[];var o=[];var n="";var g=["if","each","_","_method","console","break","case","catch","continue","debugger","default","delete","do","finally","for","function","in","instanceof","new","return","switch","this","throw","try","typeof","var","void","while","with","null","typeof","class","enum","export","extends","import","super","implements","interface","let","package","private","protected","public","static","yield","const","arguments","true","false","undefined","NaN"];var m=function(r,q){if(Array.prototype.indexOf&&r.indexOf===Array.prototype.indexOf){return r.indexOf(q);}for(var p=0;p=,\(\)\[\]]\s*([A-Za-z_]+)/igm,h);for(var l=0;l";};this.__convert=function(h,i){var g=[].join("");g+="'use strict';";g+="var _=_||{};";g+="var _out='';_out+='";if(i!==false){g+=h.replace(/\\/g,"\\\\").replace(/[\r\t\n]/g," ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"';_out+=$1;_out+='").split("<%").join("';").split("%>").join("_out+='")+"';return _out;";return g;}g+=h.replace(/\\/g,"\\\\").replace(/[\r]/g,"\\r").replace(/[\t]/g,"\\t").replace(/[\n]/g,"\\n").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"';_out+=$1;_out+='").split("<%").join("';").split("%>").join("_out+='")+"';return _out.replace(/[\\r\\n]\\s+[\\r\\n]/g, '\\r\\n');";return g;};this.parse=function(h,g){var i=this;if(!g||g.loose!==false){h=this.__lexicalAnalyze(h)+h;}h=this.__removeShell(h,g);h=this.__toNative(h,g);this._render=new Function("_, _method",h);this.render=function(k,j){if(!j||j!==f.options._method){j=a(j,f.options._method);}return i._render.call(this,k,j);};return this;};};c.compile=function(g,f){if(!f||f!==this.options){f=a(f,this.options);}try{var h=this.__cache[g]?this.__cache[g]:new this.template(this.options).parse(g,f);if(!f||f.cache!==false){this.__cache[g]=h;}return h;}catch(i){b("Juicer Compile Exception: "+i.message);return{render:function(){}};}};c.to_html=function(f,g,e){if(!e||e!==this.options){e=a(e,this.options);}return this.compile(f,e).render(g,e._method);};typeof(module)!=="undefined"&&module.exports?module.exports=c:this.juicer=c;})();
--------------------------------------------------------------------------------
/test/performance/juicer/template.html:
--------------------------------------------------------------------------------
1 |
2 | {@each persons as person}
3 | - ${person.name} [${person.email}] is ${person.age} ages.
4 | {@/each}
5 |
--------------------------------------------------------------------------------
/test/performance/load-template.js:
--------------------------------------------------------------------------------
1 | function loadTemplate( url ) {
2 | var xhr = window.XMLHttpRequest
3 | ? new XMLHttpRequest()
4 | : new ActiveXObject( 'Microsoft.XMLHTTP' );
5 | xhr.open( 'GET', url, false );
6 | xhr.send( null );
7 |
8 | if ( xhr.status >= 200 && xhr.status < 300 ) {
9 | return xhr.responseText;
10 | }
11 |
12 | return '';
13 | }
--------------------------------------------------------------------------------
/test/performance/loops.js:
--------------------------------------------------------------------------------
1 | var loops = 100;
2 |
3 | if ( /^\?([0-9]+)\*([0-9]+)/.test(location.search) ) {
4 | loops = parseInt( RegExp.$2, 10 );
5 | }
--------------------------------------------------------------------------------
/test/performance/mustache/mustache.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Render Time Test - mustache
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/test/performance/mustache/mustache.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * mustache.js - Logic-less {{mustache}} templates with JavaScript
3 | * http://github.com/janl/mustache.js
4 | */
5 |
6 | /*global define: false*/
7 |
8 | (function (root, factory) {
9 | if (typeof exports === "object" && exports) {
10 | factory(exports); // CommonJS
11 | } else {
12 | var mustache = {};
13 | factory(mustache);
14 | if (typeof define === "function" && define.amd) {
15 | define(mustache); // AMD
16 | } else {
17 | root.Mustache = mustache; //
6 |
7 |
8 |
9 |
10 |
11 |
12 |
31 |
32 |