├── .gitignore ├── tests ├── 127.0.0.1-3000.url ├── 127.0.0.1-8888.url ├── test-speed.png ├── tpl │ ├── footer.tile.html │ ├── header.tile.html │ ├── list.tile.html │ ├── test.tile.html │ └── index.tile.html ├── output-modules.js ├── js │ ├── templates │ │ ├── tmpl.js │ │ ├── easytemplate.js │ │ ├── doT.js │ │ ├── template-native.js │ │ ├── jquery.tmpl.js │ │ ├── baiduTemplate.js │ │ ├── template.js │ │ ├── juicer.js │ │ └── mustache.js │ └── qunit │ │ └── qunit-1.16.0.css ├── express-test.js ├── test-tags.html ├── node-test.js ├── test-xss-filter.html ├── test-debug.html ├── test.html └── test-speed.html ├── examples ├── js │ ├── tpl.module.js │ ├── main.js │ ├── sea.js │ └── require.min.js ├── css │ └── style.css ├── requirejs-test.html ├── index.html └── seajs-test.html ├── bower.json ├── package.json ├── LICENSE ├── CHANGE.md ├── dist ├── tiletemplate.node.min.js ├── tiletemplate.min.js ├── tiletemplate.node.js └── tiletemplate.js ├── Gulpfile.js ├── src ├── tiletemplate.node.js └── tiletemplate.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ 3 | backup/ 4 | *.bat 5 | *.sh 6 | *.log -------------------------------------------------------------------------------- /tests/127.0.0.1-3000.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=http://127.0.0.1:3000/ 3 | -------------------------------------------------------------------------------- /tests/127.0.0.1-8888.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=http://127.0.0.1:8888/ 3 | -------------------------------------------------------------------------------- /tests/test-speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandao/tileTemplate/HEAD/tests/test-speed.png -------------------------------------------------------------------------------- /tests/tpl/footer.tile.html: -------------------------------------------------------------------------------- 1 | 2 |

Footer <%=title%>

3 | 8 | <% if (list.length > 1) { %> 9 |

total: <%=list.length%>

10 | <% } %> 11 |

fadfssdfsadf

-------------------------------------------------------------------------------- /tests/tpl/header.tile.html: -------------------------------------------------------------------------------- 1 | 2 |

Header <%=title%>

3 | 8 | <% if (list.length > 1) { %> 9 |

total: <%=list.length%>

10 | <% } %> 11 |

fadfssdfsadf

-------------------------------------------------------------------------------- /examples/js/tpl.module.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) {module.exports = function (__data__ 2 | /**/) { 3 | function __escape(str) {return str.replace(/['|"|>|<|;]?/igm, "");}function objectCount(data){var total=0;for(var i in data){total++} return total;};var str = __data__.str; 4 | var __out__ = '', __line__=0; 5 | __out__+='

Hello '+str+'

';__out__+='

fasdfasdf

'; 6 | return __out__; 7 | }}) -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tileTemplate", 3 | "version": "1.6.0", 4 | "homepage": "https://github.com/pandao/tileTemplate", 5 | "authors": [ 6 | "Pandao" 7 | ], 8 | "description": "A simple, high performance Javascript template engine.", 9 | "main": "dist/tiletemplate.min.js", 10 | "keywords": [ 11 | "javascript", 12 | "template", 13 | "tileTemplate" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tests/output-modules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 预编译模板文件为 CMD 模块 3 | * 4 | * @createDate 2016-12-04 14:26:16 5 | */ 6 | 7 | "use strict"; 8 | 9 | console.time('tileTemplate'); 10 | 11 | var fs = require('fs'); 12 | var tileTemplate = require("../src/tiletemplate.node"); 13 | 14 | var tpl = tileTemplate.render("

Hello <%=str%>

\n

fasdfasdf

", {str:"wolrd!"}, { isModule : true}); 15 | 16 | // 输出为模块文件 17 | fs.writeFileSync(__dirname + '/../examples/js/' + 'tpl.module.js', tpl, { 18 | encoding : 'utf-8' 19 | }); 20 | 21 | console.log(tpl); 22 | 23 | console.timeEnd('tileTemplate'); -------------------------------------------------------------------------------- /tests/tpl/list.tile.html: -------------------------------------------------------------------------------- 1 | 2 | 引用的模板list.tile <%=title%> <%=tag:em:haha%> 3 | <%=tag:em:12%> 4 | 5 | <% if (list.length > 1) { %> 6 |

total: <%=list.length%>

7 | <% } %> 8 | 13 | <% if (list.length > 1) { %> 14 |

total: <%=list.length%>

15 | 20 | <% } %> -------------------------------------------------------------------------------- /tests/js/templates/tmpl.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var cache = {}; 3 | 4 | this.tmpl = function (str, data){ 5 | var fn = !/\W/.test(str) ? 6 | cache[str] = cache[str] || 7 | tmpl(document.getElementById(str).innerHTML) : 8 | 9 | new Function("obj", 10 | "var p=[],print=function(){p.push.apply(p,arguments);};" + 11 | "with(obj){p.push('" + 12 | 13 | str 14 | .replace(/[\r\t\n]/g, " ") 15 | .split("<%").join("\t") 16 | .replace(/((^|%>)[^\t]*)'/g, "$1\r") 17 | .replace(/\t=(.*?)%>/g, "',$1,'") 18 | .split("\t").join("');") 19 | .split("%>").join("p.push('") 20 | .split("\r").join("\\'") 21 | + "');}return p.join('');"); 22 | 23 | return data ? fn( data ) : fn; 24 | }; 25 | })(); -------------------------------------------------------------------------------- /examples/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Examples page style.css 4 | * 5 | * @FileName: style.css 6 | * @Auther: Pandao 7 | * Copyright@2014 all right reserved. 8 | */ 9 | 10 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{margin:0;padding:0;} 11 | body{font-size:14px;color:#444;font-family:"微软雅黑",Arial;background:#eee;} 12 | a{color:#444;text-decoration: none;} 13 | a:hover{color:#065BC2;text-decoration: none;} 14 | .clear{clear:both;} 15 | img{border:none;vertical-align: middle;} 16 | ul{list-style: none;} 17 | #debuger, .buttons{padding:30px;line-height:22px;border:1px solid #ddd;margin:15px;background:#fff;} 18 | 19 | .buttons {padding:10px 30px;} 20 | 21 | .buttons h4 {margin-bottom:10px;} 22 | 23 | .buttons input[type=button] {cursor: pointer;padding: 5px 10px;margin-right:5px;} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tileTemplate", 3 | "version": "1.6.0", 4 | "description": "A simple, high performance Javascript template engine.", 5 | "main": "src/tiletemplate.node.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/pandao/tileTemplate.git" 15 | }, 16 | "keywords": [ 17 | "tileTemplate", 18 | "javascript", 19 | "template" 20 | ], 21 | "author": "Pandao", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/pandao/tileTemplate/issues" 25 | }, 26 | "homepage": "https://github.com/pandao/tileTemplate", 27 | "devDependencies": { 28 | "dateformatter": "^0.1.0", 29 | "express": "^4.14.0", 30 | "gulp": "^3.9.1", 31 | "gulp-header": "^1.8.8", 32 | "gulp-notify": "^2.2.0", 33 | "gulp-rename": "^1.2.2", 34 | "gulp-replace": "^0.5.4", 35 | "gulp-uglify": "^2.0.0", 36 | "gulp-util": "^3.0.7", 37 | "gulp-x-includer": "^0.1.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 pandao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tests/tpl/test.tile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tileTemplate Test in Node.js 7 | 8 | 9 |

tileTemplate Test in Node.js

10 | <% include("header") %> 11 |

<%=title%>

12 | 17 | <% if (list.length > 1) { %> 18 |

total: <%=list.length%>

19 | <% } %> 20 | 21 |

fadfssdfsadf

22 | <% include("list") %> 23 | 24 | <% var total=list.length; %> 25 | 26 | 27 | 28 | //这是注释 29 | 30 | 31 | 32 | 33 | <% var a=10; while(a>0) {a--;} %> 34 | 35 | <% if(typeof console == "object") console.log("console is object."); %> 36 | <% if(a==0) {} %> 37 | <% if(a==0) { %> 38 | a==0 39 | <% } %> 40 | 41 |

fadfssdfsadf

42 | 43 | <% include("footer") %> 44 | 45 | -------------------------------------------------------------------------------- /tests/tpl/index.tile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tileTemplate Test in Express.js 7 | 8 | 9 |

tileTemplate Test in Express.js

10 | <% include("header") %> 11 |

<%=title%>

12 | 17 | <% if (list.length > 1) { %> 18 |

total: <%=list.length%>

19 | <% } %> 20 | 21 |

fadfssdfsadf

22 | <% include("list") %> 23 | 24 | <% var total=list.length; %> 25 | 26 | 27 | 28 | //这是注释 29 | 30 | 31 | 32 | 33 | <% var a=10; while(a>0) {a--;} %> 34 | 35 | <% if(typeof console == "object") console.log("console is object."); %> 36 | <% if(a==0) {} %> 37 | <% if(a==0) { %> 38 | a==0 39 | <% } %> 40 | 41 |

fadfssdfsadf

42 | 43 | <% include("footer") %> 44 | 45 | -------------------------------------------------------------------------------- /CHANGE.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | ## v1.x 4 | 5 | #### v1.6.0 6 | 7 | - 优化:改进和修复数据变量作用域引起的问题; 8 | - 更新:不再使用 Grunt.js 构建,改为使用 Gulp.js 构建; 9 | - 新增:`compiler` 方法必须传入参数 `data`,以实现模板预编译; 10 | - 新增:`compiler` 和 `render` 方法新增参数 `isModule`,用于支持预编译输出模板 CMD 模块; 11 | - 更新文档及测试用例; 12 | 13 | #### v1.5.0 14 | 15 | 改进在 `Node.js` 环境下的使用,并对 `Express.js` 进行支持。 16 | 17 | - 增加 `expressInit()`方法,用于支持 `Express.js` 时的初始化; 18 | - 增加和修改`Express.js`、`Node.js` 的测试用例,并将所有测试模板文件的扩展名改为 `tile.html`; 19 | - 在 `Node.js` 下,配置项 `basePath` 的默认值改为 `./`; 20 | - 增加配置项 `ext`,默认值为 `tile.html`,在 `Node.js` 下免去填写模板的扩展名; 21 | - 添加源码的文档注释; 22 | - 修改 `README.md` 介绍文档; 23 | 24 | #### v1.4.0 25 | 26 | 主要改进安全转义功能。 27 | 28 | - 删除 `escape()` 和 `unescape()` 方法; 29 | - 增加 `htmlEncode()` 和 `htmlDecode()` 方法; 30 | - `xssFilter()` 方法更名为 `filter()`; 31 | - 添加xxs过滤的测试用例; 32 | 33 | #### v1.3.0 34 | 35 | - 优化性能; 36 | - 修正一些bug和修改文档; 37 | 38 | #### v1.2.0 39 | 40 | - 优化性能; 41 | - 修正一些bug和修改文档; 42 | 43 | #### v1.1.1 44 | 45 | 修正一些bug和修改文档; 46 | 47 | #### v1.1.0 48 | 49 | 重写include语句的编译方法,以便能在node.js环境下重写; 50 | 51 | - 新增 `compileInclude()` 方法; 52 | - 新增 `regex` 属性; 53 | - 重写`node.js`版的 `compileInclude()` 和 `render()` 方法; 54 | 55 | #### v1.0.2 56 | 57 | 修正一些bug和修改文档; 58 | 59 | ##### v1.0.1 60 | 61 | 修正一些bug和修改文档; 62 | 63 | #### v1.0.0 64 | 65 | 基本功能完成,1.0版; -------------------------------------------------------------------------------- /tests/express-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var tileTemplate = require("../src/tiletemplate.node"); 6 | 7 | console.time('tileTemplate'); 8 | 9 | var data = { 10 | version : "1.0.0", 11 | title : "标题XXX", 12 | avatar : 'http://tp1.sinaimg.cn/2287297600/180/40014697506/1', 13 | avatar2 : 'http://tp1.sinaimg.cn/2287297600/180/40014697506/1" onload="alert(123)', 14 | list : [] 15 | }; 16 | 17 | for (var i = 0; i < 10; i ++) { 18 | data.list.push({ 19 | index: (i+1), 20 | user: 'tileTemplate '+(i+1)+'', 21 | site: 'https://github.com/pandao/tileTemplate' 22 | }); 23 | }; 24 | 25 | // 设置表情标签 26 | tileTemplate.tag("em", function(content) { 27 | if (content == 12) { 28 | return 'em'+content+''; 29 | } else { 30 | return content.toString(); 31 | } 32 | }); 33 | 34 | 35 | // 设置时间戳标签 36 | tileTemplate.tag("time", function() { 37 | return " time: " + (new Date).getTime(); 38 | }); 39 | 40 | // 初始化Express支持 41 | tileTemplate.expressInit(app, __dirname + "/tpl/"); 42 | 43 | app.get('/', function (req, res) { 44 | res.render('index', data); 45 | }); 46 | 47 | var server = app.listen(3000, function() { 48 | console.log('Listening on port %d', server.address().port); 49 | }); 50 | 51 | console.timeEnd('tileTemplate'); 52 | -------------------------------------------------------------------------------- /tests/test-tags.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tileTemplate tag tests 6 | 7 | 8 |
9 | 16 | 17 | 40 | 41 | -------------------------------------------------------------------------------- /dist/tiletemplate.node.min.js: -------------------------------------------------------------------------------- 1 | /*! tileTemplate v1.6.0 | Copyright (c) 2016 Pandao | https://github.com/pandao/tileTemplate | 2016-12-04 15:02:47 */ 2 | "use strict";var tileTemplate=require("./tiletemplate");tileTemplate.config({ext:"tile.html",basePath:"./"}),tileTemplate.readFile=function(e,t){t=t||"utf-8";var i=require("fs"),s=this.settings.basePath,n="."+this.settings.ext;e=s+e+n;var a=!!i.existsSync(e)&&i.readFileSync(e,t);return a},tileTemplate.compileInclude=function(e,t,i){e=e||"",i=i||"utf-8";var s=this,n=this.settings.openTag,a=this.settings.closeTag,r=this.settings.basePath;return function(e,l,u){var o=(new Date).getTime(),c=l.replace("('","").replace("')","").replace('("',"").replace('")',""),p=s.readFile(c,i),h=s.compile(p,t,{include:!0,name:r+c}).toString().split("\n").join("");return h=h.replace("anonymous","anonymous"+o),h+=' if (typeof __data__ !== "undefined") { __out__ += anonymous'+o+"(__data__); }",n+" "+h+" "+a}},tileTemplate.render=function(e,t,i,s){"undefined"==typeof i&&(i=!1),e=e||"",s=s||"utf-8";var n=require("fs"),a=this.caches,r=this.settings.cached,l=this.settings.basePath,u="."+this.settings.ext,o=n.existsSync(l+e+u)?n.readFileSync(l+e+u,s):e;if(n.existsSync(l+e+u)&&(e=l+e+u),r&&"undefined"!=typeof a[e])return a[e];var c=this.compile(o,t,{name:e,isModule:i});return c=i?c:c(t),r&&(a[e]=c),c},tileTemplate.__express=function(e,t,i){var s=require("fs");s.readFile(e,function(e,s){if(e)throw new Error(e);var n=tileTemplate.render(s.toString(),t);return i(null,n)})},tileTemplate.expressInit=function(e,t,i){this.config({basePath:t||"./",ext:i||"tile.html"}),i=this.settings.ext,e.set("views",this.settings.basePath),e.engine(i,this.__express),e.set("view engine",i)},module.exports=tileTemplate; -------------------------------------------------------------------------------- /tests/js/templates/easytemplate.js: -------------------------------------------------------------------------------- 1 | var easyTemplate = function(s,d){ 2 | if(!s){return '';} 3 | if(s!==easyTemplate.template){ 4 | easyTemplate.template = s; 5 | easyTemplate.aStatement = easyTemplate.parsing(easyTemplate.separate(s)); 6 | } 7 | var aST = easyTemplate.aStatement; 8 | var process = function(d2){ 9 | if(d2){d = d2;} 10 | return arguments.callee; 11 | }; 12 | process.toString = function(){ 13 | return (new Function(aST[0],aST[1]))(d); 14 | }; 15 | return process; 16 | }; 17 | easyTemplate.separate = function(s){ 18 | var r = /\\'/g; 19 | var sRet = s.replace(/(<(\/?)#(.*?(?:\(.*?\))*)>)|(')|([\r\n\t])|(\$\{([^\}]*?)\})/g,function(a,b,c,d,e,f,g,h){ 20 | if(b){return '{|}'+(c?'-':'+')+d+'{|}';} 21 | if(e){return '\\\'';} 22 | if(f){return '';} 23 | if(g){return '\'+('+h.replace(r,'\'')+')+\'';} 24 | }); 25 | return sRet; 26 | }; 27 | easyTemplate.parsing = function(s){ 28 | var mName,vName,sTmp,aTmp,sFL,sEl,aList,aStm = ['var aRet = [];']; 29 | aList = s.split(/\{\|\}/); 30 | var r = /\s/; 31 | while(aList.length){ 32 | sTmp = aList.shift(); 33 | if(!sTmp){continue;} 34 | sFL = sTmp.charAt(0); 35 | if(sFL!=='+'&&sFL!=='-'){ 36 | sTmp = '\''+sTmp+'\'';aStm.push('aRet.push('+sTmp+');'); 37 | continue; 38 | } 39 | aTmp = sTmp.split(r); 40 | switch(aTmp[0]){ 41 | case '+macro':mName = aTmp[1];vName = aTmp[2];aStm.push('aRet.push("/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,y=/^$|,+/;"function"==typeof define?define(function(){return d}):"undefined"!=typeof exports?module.exports=d:this.template=d}(); -------------------------------------------------------------------------------- /dist/tiletemplate.min.js: -------------------------------------------------------------------------------- 1 | /*! tileTemplate v1.6.0 | Copyright (c) 2016 Pandao | https://github.com/pandao/tileTemplate | 2016-12-04 15:02:47 */ 2 | !function(e){"function"==typeof require&&"object"==typeof exports&&"object"==typeof module?e(require,exports,module):"function"==typeof define?define(e):e(function(){},window.tileTemplate={},{})}(function(e,t,n){"use strict";function i(e){return"undefined"!=typeof document?document.getElementById(e):e}"function"!=typeof"".trim&&(String.prototype.trim=function(){return this.replace(/(^\s*)|(\s*$)/g,"")});var s=t;t.version="1.5.0",t.tags=[],t.caches=[],t.regex={},t.extend=function(e,t){t=t||this;for(var n in e)"undefined"==typeof t[n]&&(t[n]=e[n]);return t},t.settings={debug:!1,cached:!0,filter:!0,openTag:"<%",closeTag:"%>"},t.set=t.config=function(){arguments&&arguments.length>1?this.settings[arguments[0]]=arguments[1]:this.settings=this.extend(this.settings,arguments[0])},t.htmlEncode=function(e){return e.replace(/\/gim,">").replace(/\"/gim,""").replace(/\'/gim,"'")},t.htmlDecode=function(e){return e.replace(/\</gim,"<").replace(/\>/gim,">").replace(/\"/gim,'"').replace(/\'/gim,"'")},t.filter=function(e,t,n){return e=e.replace(/\s*<\s*iframe\s*(.*)\s*>\s*<\s*\/iframe\s*>\s*/gim,""),e=e.replace(new RegExp(t+"s*(.*)s*alerts*(s*(.*)s*)s*;?s*(.*)"+n,"igm"),""),e=e.replace(new RegExp(t+"s*(.*)s*confirms*(s*(.*)s*)s*;?s*(.*)"+n,"igm"),""),e=e.replace(new RegExp(t+"s*(.*)s*prompts*(s*(.*)s*)s*;?s*(.*)"+n,"igm"),""),e=e.replace(new RegExp(t+"s*(.*)s*windows*(.|;)s*(.*)"+n,"igm"),""),e=e.replace(new RegExp(t+"s*(.*)s*documents*(.|;)s*(.*)"+n,"igm"),""),e=e.replace(new RegExp(t+"s*(.*)s*createElements*(s*(.*)s*)s*(.*)"+n,"igm"),""),e=e.replace(new RegExp(t+"s*(.*)s*(\\$|jQuery)s*(.*)s*(s*(.*)s*)s*(.*)"+n,"igm"),""),e=e.replace(new RegExp(t+"s*(.*)s*eval(s*(.*)s*)s*(.*)"+n,"igm"),""),e.trim()},t.compileInclude=function(e){var t=this.settings.openTag,n=this.settings.closeTag;return function(r,o,a){var c=(new Date).getTime(),l=o.replace("('","").replace("')","").replace('("',"").replace('")',""),u="string"==typeof i(l)?l:null==i(l)?l:i(l).value||i(l).innerHTML,p=s.compile(u,e,{include:!0,name:l}).toString().split("\n").join("");return p=p.replace("anonymous","anonymous"+c),p+=' if (typeof __data__ !== "undefined") { __out__ += anonymous'+c+"(__data__); }",p=t+" "+p+" "+n}},t.compile=function(e,t,n){var i=(new Date).getTime(),r={include:!1,name:"tile"+i,isModule:!1};t=t||{},n=n||{},n=this.extend(r,n);var o=(n.include,this.settings.filter),a=this.settings.debug,c=this.settings.openTag,l=this.settings.closeTag,u=new RegExp(c+"=(.*?)"+l,"igm"),p=new RegExp(c+"=#(.*?)"+l,"igm"),g=new RegExp(c+"=@(.*?)"+l,"igm"),f=new RegExp("//"+c+"?\\s*(.*?)\\s*"+l+"?","igm"),_=/\s*tag:(\w+):?([\u4e00-\u9fa5]+|\w+|\d+)?\s*/gim,m=new RegExp(c+"\\s*include\\s*((.*?))\\s*;?\\s*"+l,"igm"),d=function(e,t,n){var i="undefined"==typeof n?s.tags[t]():s.tags[t](n.toString());return"'"+i.toString()+"'"};if(this.regex.include=m,e=e.replace(/(^\s*)|(\s*$)/g,"").replace(p,function(e,t){return"'+'"}).replace(f,"").replace(this.regex.include,this.compileInclude(t)).replace(g,function(e,t){return"'+__escape("+t+")+'"}).replace(u,function(e,t){return"'+"+t+"+'"}).split("\n"),a){for(var h=0,v=e.length;h|<|;]?/igm, "");}',y="function objectCount(data){var total=0;for(var i in data){total++} return total;};",w="if (typeof console === 'object') {console.error('[tileTemplate]\\n"+n.name+"\\n\\n[type]\\nRender error\\n\\nmessage: '+e.message+'\\nline: '+(e.line+1));}",E="try {"+e.join("")+"\n return __out__; } catch(e) { e.line=__line__; if(objectCount(data) > 0) { "+w+" }}";e="var __out__ = '', __line__=0;\n "+(a?E:e.join("")+"\n return __out__;");var R=[];for(var h in t)R.push(h+" = __data__."+h);R=R.length>0?"var "+R.join(", ")+";\n":"";var T=new Function("__data__",x+y+R+e);return n.isModule&&(T="define(function(require, exports, module) {module.exports = "+T.toLocaleString().replace("anonymous","")+"})"),T},t.render=function(e,t,n,s){"undefined"==typeof s&&(s=!1),t=t||{},n=n||"";var r=this.settings.cached,o="string"==typeof i(e)?e:null==i(e)?e:i(e).value||i(e).innerHTML,a=""==n?e:n;if(r&&"undefined"!=typeof this.caches[a])return this.caches[a];var c=this.compile(o,t,{name:""==n?e:n,isModule:s})(t);return r&&(this.caches[a]=c),c},t.tag=function(e,t){this.tags[e]=t},t.clear=function(e){return delete this.caches[e],!0}}); -------------------------------------------------------------------------------- /src/tiletemplate.node.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var tileTemplate = require("./tiletemplate"); 4 | 5 | tileTemplate.config({ 6 | ext : "tile.html", 7 | basePath : "./" 8 | }); 9 | 10 | /** 11 | * @description 读取模板文件 12 | * @param {String} filename 文件路径 13 | * @param {String} encoding 文件的编码,默认为 utf-8 14 | * @return {String} file 返回文件的内容 15 | */ 16 | 17 | tileTemplate.readFile = function(filename, encoding) { 18 | encoding = encoding || "utf-8"; 19 | 20 | var fs = require('fs'); 21 | var basePath = this.settings.basePath; 22 | var ext = "." + this.settings.ext; 23 | 24 | filename = basePath + filename + ext; 25 | 26 | var file = ( fs.existsSync(filename) ) ? fs.readFileSync(filename, encoding) : false; 27 | 28 | return file; 29 | }; 30 | 31 | /** 32 | * @description 编译嵌套(include)的模板文件的回调函数 33 | * @param {String} filename 文件名,不含扩展名和父级目录路径 34 | * @param {String} data 传入的数据 35 | * @param {String} encoding 文件的编码,默认为 utf-8 36 | * @return {String} result 一个以字符串为形式的函数 37 | */ 38 | 39 | tileTemplate.compileInclude = function(filename, data, encoding) { 40 | 41 | filename = filename || ""; 42 | encoding = encoding || "utf-8"; 43 | 44 | var _this = this; 45 | var openTag = this.settings.openTag; 46 | var closeTag = this.settings.closeTag; 47 | var basePath = this.settings.basePath; 48 | 49 | return (function($1, $2, $3) { 50 | var guid = (new Date).getTime(); 51 | var includeFilename = $2.replace("('", "").replace("')", "").replace("(\"", "").replace("\")", ""); 52 | var includeContent = _this.readFile(includeFilename, encoding); 53 | var compile = _this.compile(includeContent, data, {include: true, name: basePath + includeFilename }).toString().split("\n").join(''); 54 | 55 | compile = compile.replace('anonymous', 'anonymous' + guid); 56 | 57 | compile += ' if (typeof __data__ !== "undefined") { __out__ += anonymous' + guid + '(__data__); }'; 58 | 59 | return openTag + " " + compile + " " + closeTag; 60 | }); 61 | }; 62 | 63 | /** 64 | * @description 渲染模板文件 65 | * @param {String} filename 文件名,不含扩展名和父级目录路径 66 | * @param {String} data 传入的数据 67 | * @param {Boolean} [isModule=false] 是否定义为模块 68 | * @param {String} encoding 文件的编码,默认为 utf-8 69 | * @return {String} result 模板编译后返回的HTML(字符串) 70 | */ 71 | 72 | tileTemplate.render = function(filename, data, isModule, encoding) { 73 | if (typeof isModule === 'undefined') { 74 | isModule = false; 75 | } 76 | 77 | filename = filename || ""; 78 | encoding = encoding || "utf-8"; 79 | 80 | var fs = require('fs'); 81 | var _this = this; 82 | var caches = this.caches; 83 | var cached = this.settings.cached; 84 | var basePath = this.settings.basePath; 85 | var ext = "." + this.settings.ext; 86 | 87 | var tpl = ( fs.existsSync(basePath + filename + ext) ) ? fs.readFileSync(basePath + filename + ext, encoding) : filename; 88 | 89 | if ( fs.existsSync(basePath + filename + ext) ) { 90 | filename = basePath + filename + ext; 91 | } 92 | 93 | if (cached && typeof caches[filename] !== "undefined") { // use cache 94 | return caches[filename]; 95 | } else { 96 | var html = this.compile( tpl, data, { name: filename, isModule : isModule} ); 97 | 98 | html = isModule ? html : html(data); 99 | 100 | if (cached) { 101 | caches[filename] = html; 102 | } 103 | 104 | return html; 105 | } 106 | }; 107 | 108 | /** 109 | * @description Express的接口 110 | * @param {String} filePath 文件名,不含扩展名和父级目录路径 111 | * @param {String} data 传入的数据 112 | * @param {Function} callback 回调函数 113 | * @return {String} result 文件流 114 | */ 115 | 116 | tileTemplate.__express = function(filePath, data, callback) { 117 | var fs = require('fs'); 118 | 119 | fs.readFile(filePath, function (err, content) { 120 | if (err) throw new Error(err); 121 | 122 | var rendered = tileTemplate.render(content.toString(), data); 123 | 124 | return callback(null, rendered); 125 | }); 126 | }; 127 | 128 | /** 129 | * @description Express的支持接口 130 | * @param {String} app Express的app对象 131 | * @param {String} path 模板的根目录 132 | * @param {String} ext 模板的扩展名 133 | * @return {Void} result 无 134 | */ 135 | 136 | tileTemplate.expressInit = function(app, path, ext) { 137 | this.config({ 138 | basePath : path || "./", 139 | ext : ext || "tile.html" 140 | }); 141 | 142 | ext = this.settings.ext; 143 | 144 | app.set('views', this.settings.basePath); 145 | app.engine(ext, this.__express); 146 | app.set('view engine', ext); 147 | }; 148 | 149 | module.exports = tileTemplate; -------------------------------------------------------------------------------- /dist/tiletemplate.node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tileTemplate 3 | * 4 | * @file tiletemplate.node.js 5 | * @description A simple, high performance Javascript template engine. 6 | * @version v1.6.0 7 | * @author Pandao 8 | * {@link https://github.com/pandao/tileTemplate} 9 | * @updateTime 2016-12-04 15:02:47 10 | */ 11 | 12 | "use strict"; 13 | 14 | var tileTemplate = require("./tiletemplate"); 15 | 16 | tileTemplate.config({ 17 | ext : "tile.html", 18 | basePath : "./" 19 | }); 20 | 21 | /** 22 | * @description 读取模板文件 23 | * @param {String} filename 文件路径 24 | * @param {String} encoding 文件的编码,默认为 utf-8 25 | * @return {String} file 返回文件的内容 26 | */ 27 | 28 | tileTemplate.readFile = function(filename, encoding) { 29 | encoding = encoding || "utf-8"; 30 | 31 | var fs = require('fs'); 32 | var basePath = this.settings.basePath; 33 | var ext = "." + this.settings.ext; 34 | 35 | filename = basePath + filename + ext; 36 | 37 | var file = ( fs.existsSync(filename) ) ? fs.readFileSync(filename, encoding) : false; 38 | 39 | return file; 40 | }; 41 | 42 | /** 43 | * @description 编译嵌套(include)的模板文件的回调函数 44 | * @param {String} filename 文件名,不含扩展名和父级目录路径 45 | * @param {String} data 传入的数据 46 | * @param {String} encoding 文件的编码,默认为 utf-8 47 | * @return {String} result 一个以字符串为形式的函数 48 | */ 49 | 50 | tileTemplate.compileInclude = function(filename, data, encoding) { 51 | 52 | filename = filename || ""; 53 | encoding = encoding || "utf-8"; 54 | 55 | var _this = this; 56 | var openTag = this.settings.openTag; 57 | var closeTag = this.settings.closeTag; 58 | var basePath = this.settings.basePath; 59 | 60 | return (function($1, $2, $3) { 61 | var guid = (new Date).getTime(); 62 | var includeFilename = $2.replace("('", "").replace("')", "").replace("(\"", "").replace("\")", ""); 63 | var includeContent = _this.readFile(includeFilename, encoding); 64 | var compile = _this.compile(includeContent, data, {include: true, name: basePath + includeFilename }).toString().split("\n").join(''); 65 | 66 | compile = compile.replace('anonymous', 'anonymous' + guid); 67 | 68 | compile += ' if (typeof __data__ !== "undefined") { __out__ += anonymous' + guid + '(__data__); }'; 69 | 70 | return openTag + " " + compile + " " + closeTag; 71 | }); 72 | }; 73 | 74 | /** 75 | * @description 渲染模板文件 76 | * @param {String} filename 文件名,不含扩展名和父级目录路径 77 | * @param {String} data 传入的数据 78 | * @param {Boolean} [isModule=false] 是否定义为模块 79 | * @param {String} encoding 文件的编码,默认为 utf-8 80 | * @return {String} result 模板编译后返回的HTML(字符串) 81 | */ 82 | 83 | tileTemplate.render = function(filename, data, isModule, encoding) { 84 | if (typeof isModule === 'undefined') { 85 | isModule = false; 86 | } 87 | 88 | filename = filename || ""; 89 | encoding = encoding || "utf-8"; 90 | 91 | var fs = require('fs'); 92 | var _this = this; 93 | var caches = this.caches; 94 | var cached = this.settings.cached; 95 | var basePath = this.settings.basePath; 96 | var ext = "." + this.settings.ext; 97 | 98 | var tpl = ( fs.existsSync(basePath + filename + ext) ) ? fs.readFileSync(basePath + filename + ext, encoding) : filename; 99 | 100 | if ( fs.existsSync(basePath + filename + ext) ) { 101 | filename = basePath + filename + ext; 102 | } 103 | 104 | if (cached && typeof caches[filename] !== "undefined") { // use cache 105 | return caches[filename]; 106 | } else { 107 | var html = this.compile( tpl, data, { name: filename, isModule : isModule} ); 108 | 109 | html = isModule ? html : html(data); 110 | 111 | if (cached) { 112 | caches[filename] = html; 113 | } 114 | 115 | return html; 116 | } 117 | }; 118 | 119 | /** 120 | * @description Express的接口 121 | * @param {String} filePath 文件名,不含扩展名和父级目录路径 122 | * @param {String} data 传入的数据 123 | * @param {Function} callback 回调函数 124 | * @return {String} result 文件流 125 | */ 126 | 127 | tileTemplate.__express = function(filePath, data, callback) { 128 | var fs = require('fs'); 129 | 130 | fs.readFile(filePath, function (err, content) { 131 | if (err) throw new Error(err); 132 | 133 | var rendered = tileTemplate.render(content.toString(), data); 134 | 135 | return callback(null, rendered); 136 | }); 137 | }; 138 | 139 | /** 140 | * @description Express的支持接口 141 | * @param {String} app Express的app对象 142 | * @param {String} path 模板的根目录 143 | * @param {String} ext 模板的扩展名 144 | * @return {Void} result 无 145 | */ 146 | 147 | tileTemplate.expressInit = function(app, path, ext) { 148 | this.config({ 149 | basePath : path || "./", 150 | ext : ext || "tile.html" 151 | }); 152 | 153 | ext = this.settings.ext; 154 | 155 | app.set('views', this.settings.basePath); 156 | app.engine(ext, this.__express); 157 | app.set('view engine', ext); 158 | }; 159 | 160 | module.exports = tileTemplate; -------------------------------------------------------------------------------- /examples/js/sea.js: -------------------------------------------------------------------------------- 1 | /*! Sea.js 2.3.0 | seajs.org/LICENSE.md */ 2 | !function(a,b){function c(a){return function(b){return{}.toString.call(b)=="[object "+a+"]"}}function d(){return z++}function e(a){return a.match(C)[0]}function f(a){for(a=a.replace(D,"/"),a=a.replace(F,"$1/");a.match(E);)a=a.replace(E,"/");return a}function g(a){var b=a.length-1,c=a.charAt(b);return"#"===c?a.substring(0,b):".js"===a.substring(b-2)||a.indexOf("?")>0||"/"===c?a:a+".js"}function h(a){var b=u.alias;return b&&w(b[a])?b[a]:a}function i(a){var b=u.paths,c;return b&&(c=a.match(G))&&w(b[c[1]])&&(a=b[c[1]]+c[2]),a}function j(a){var b=u.vars;return b&&a.indexOf("{")>-1&&(a=a.replace(H,function(a,c){return w(b[c])?b[c]:a})),a}function k(a){var b=u.map,c=a;if(b)for(var d=0,e=b.length;e>d;d++){var f=b[d];if(c=y(f)?f(a)||a:a.replace(f[0],f[1]),c!==a)break}return c}function l(a,b){var c,d=a.charAt(0);if(I.test(a))c=a;else if("."===d)c=f((b?e(b):u.cwd)+a);else if("/"===d){var g=u.cwd.match(J);c=g?g[0]+a.substring(1):a}else c=u.base+a;return 0===c.indexOf("//")&&(c=location.protocol+c),c}function m(a,b){if(!a)return"";a=h(a),a=i(a),a=j(a),a=g(a);var c=l(a,b);return c=k(c)}function n(a){return a.hasAttribute?a.src:a.getAttribute("src",4)}function o(a,b,c){var d=K.createElement("script");if(c){var e=y(c)?c(a):c;e&&(d.charset=e)}p(d,b,a),d.async=!0,d.src=a,R=d,Q?P.insertBefore(d,Q):P.appendChild(d),R=null}function p(a,b,c){function d(){a.onload=a.onerror=a.onreadystatechange=null,u.debug||P.removeChild(a),a=null,b()}var e="onload"in a;e?(a.onload=d,a.onerror=function(){B("error",{uri:c,node:a}),d()}):a.onreadystatechange=function(){/loaded|complete/.test(a.readyState)&&d()}}function q(){if(R)return R;if(S&&"interactive"===S.readyState)return S;for(var a=P.getElementsByTagName("script"),b=a.length-1;b>=0;b--){var c=a[b];if("interactive"===c.readyState)return S=c}}function r(a){var b=[];return a.replace(U,"").replace(T,function(a,c,d){d&&b.push(d)}),b}function s(a,b){this.uri=a,this.dependencies=b||[],this.exports=null,this.status=0,this._waitings={},this._remain=0}if(!a.seajs){var t=a.seajs={version:"2.3.0"},u=t.data={},v=c("Object"),w=c("String"),x=Array.isArray||c("Array"),y=c("Function"),z=0,A=u.events={};t.on=function(a,b){var c=A[a]||(A[a]=[]);return c.push(b),t},t.off=function(a,b){if(!a&&!b)return A=u.events={},t;var c=A[a];if(c)if(b)for(var d=c.length-1;d>=0;d--)c[d]===b&&c.splice(d,1);else delete A[a];return t};var B=t.emit=function(a,b){var c=A[a],d;if(c){c=c.slice();for(var e=0,f=c.length;f>e;e++)c[e](b)}return t},C=/[^?#]*\//,D=/\/\.\//g,E=/\/[^/]+\/\.\.\//,F=/([^:/])\/+\//g,G=/^([^/:]+)(\/.+)$/,H=/{([^{]+)}/g,I=/^\/\/.|:\//,J=/^.*?\/\/.*?\//,K=document,L=location.href&&0!==location.href.indexOf("about:")?e(location.href):"",M=K.scripts,N=K.getElementById("seajsnode")||M[M.length-1],O=e(n(N)||L);t.resolve=m;var P=K.head||K.getElementsByTagName("head")[0]||K.documentElement,Q=P.getElementsByTagName("base")[0],R,S;t.request=o;var T=/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g,U=/\\\\/g,V=t.cache={},W,X={},Y={},Z={},$=s.STATUS={FETCHING:1,SAVED:2,LOADING:3,LOADED:4,EXECUTING:5,EXECUTED:6};s.prototype.resolve=function(){for(var a=this,b=a.dependencies,c=[],d=0,e=b.length;e>d;d++)c[d]=s.resolve(b[d],a.uri);return c},s.prototype.load=function(){var a=this;if(!(a.status>=$.LOADING)){a.status=$.LOADING;var c=a.resolve();B("load",c);for(var d=a._remain=c.length,e,f=0;d>f;f++)e=s.get(c[f]),e.status<$.LOADED?e._waitings[a.uri]=(e._waitings[a.uri]||0)+1:a._remain--;if(0===a._remain)return a.onload(),b;var g={};for(f=0;d>f;f++)e=V[c[f]],e.status<$.FETCHING?e.fetch(g):e.status===$.SAVED&&e.load();for(var h in g)g.hasOwnProperty(h)&&g[h]()}},s.prototype.onload=function(){var a=this;a.status=$.LOADED,a.callback&&a.callback();var b=a._waitings,c,d;for(c in b)b.hasOwnProperty(c)&&(d=V[c],d._remain-=b[c],0===d._remain&&d.onload());delete a._waitings,delete a._remain},s.prototype.fetch=function(a){function c(){t.request(g.requestUri,g.onRequest,g.charset)}function d(){delete X[h],Y[h]=!0,W&&(s.save(f,W),W=null);var a,b=Z[h];for(delete Z[h];a=b.shift();)a.load()}var e=this,f=e.uri;e.status=$.FETCHING;var g={uri:f};B("fetch",g);var h=g.requestUri||f;return!h||Y[h]?(e.load(),b):X[h]?(Z[h].push(e),b):(X[h]=!0,Z[h]=[e],B("request",g={uri:f,requestUri:h,onRequest:d,charset:u.charset}),g.requested||(a?a[g.requestUri]=c:c()),b)},s.prototype.exec=function(){function a(b){return s.get(a.resolve(b)).exec()}var c=this;if(c.status>=$.EXECUTING)return c.exports;c.status=$.EXECUTING;var e=c.uri;a.resolve=function(a){return s.resolve(a,e)},a.async=function(b,c){return s.use(b,c,e+"_async_"+d()),a};var f=c.factory,g=y(f)?f(a,c.exports={},c):f;return g===b&&(g=c.exports),delete c.factory,c.exports=g,c.status=$.EXECUTED,B("exec",c),g},s.resolve=function(a,b){var c={id:a,refUri:b};return B("resolve",c),c.uri||t.resolve(c.id,b)},s.define=function(a,c,d){var e=arguments.length;1===e?(d=a,a=b):2===e&&(d=c,x(a)?(c=a,a=b):c=b),!x(c)&&y(d)&&(c=r(""+d));var f={id:a,uri:s.resolve(a),deps:c,factory:d};if(!f.uri&&K.attachEvent){var g=q();g&&(f.uri=g.src)}B("define",f),f.uri?s.save(f.uri,f):W=f},s.save=function(a,b){var c=s.get(a);c.status<$.SAVED&&(c.id=b.id||a,c.dependencies=b.deps||[],c.factory=b.factory,c.status=$.SAVED,B("save",c))},s.get=function(a,b){return V[a]||(V[a]=new s(a,b))},s.use=function(b,c,d){var e=s.get(d,x(b)?b:[b]);e.callback=function(){for(var b=[],d=e.resolve(),f=0,g=d.length;g>f;f++)b[f]=V[d[f]].exec();c&&c.apply(a,b),delete e.callback},e.load()},t.use=function(a,b){return s.use(a,b,u.cwd+"_use_"+d()),t},s.define.cmd={},a.define=s.define,t.Module=s,u.fetchedList=Y,u.cid=d,t.require=function(a){var b=s.get(s.resolve(a));return b.status<$.EXECUTING&&(b.onload(),b.exec()),b.exports},u.base=O,u.dir=O,u.cwd=L,u.charset="utf-8",t.config=function(a){for(var b in a){var c=a[b],d=u[b];if(d&&v(d))for(var e in c)d[e]=c[e];else x(d)?c=d.concat(c):"base"===b&&("/"!==c.slice(-1)&&(c+="/"),c=l(c)),u[b]=c}return B("config",a),t}}}(this); -------------------------------------------------------------------------------- /tests/js/templates/jquery.tmpl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Templates Plugin 1.0.0pre 3 | * http://github.com/jquery/jquery-tmpl 4 | * Requires jQuery 1.4.2 5 | * 6 | * Copyright 2011, Software Freedom Conservancy, Inc. 7 | * Dual licensed under the MIT or GPL Version 2 licenses. 8 | * http://jquery.org/license 9 | */ 10 | (function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i").join(">").split('"').join(""").split("'").join("'")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery); -------------------------------------------------------------------------------- /tests/test-debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tileTemplate Example 7 | 8 | 9 |

查看调试输出,请打开浏览器控制台

10 | 13 |

output1

14 |
15 |

output2

16 |
17 |

output3

18 |
19 | 66 | 86 | 89 | 90 | 91 | 144 | 145 | -------------------------------------------------------------------------------- /tests/js/qunit/qunit-1.16.0.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 1.16.0 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright 2006, 2014 jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: 2014-12-03T16:32Z 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 19 | #qunit-tests { font-size: smaller; } 20 | 21 | 22 | /** Resets */ 23 | 24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | 30 | /** Header */ 31 | 32 | #qunit-header { 33 | padding: 0.5em 0 0.5em 1em; 34 | 35 | color: #8699A4; 36 | background-color: #0D3349; 37 | 38 | font-size: 1.5em; 39 | line-height: 1em; 40 | font-weight: 400; 41 | 42 | border-radius: 5px 5px 0 0; 43 | } 44 | 45 | #qunit-header a { 46 | text-decoration: none; 47 | color: #C2CCD1; 48 | } 49 | 50 | #qunit-header a:hover, 51 | #qunit-header a:focus { 52 | color: #FFF; 53 | } 54 | 55 | #qunit-testrunner-toolbar label { 56 | display: inline-block; 57 | padding: 0 0.5em 0 0.1em; 58 | } 59 | 60 | #qunit-banner { 61 | height: 5px; 62 | } 63 | 64 | #qunit-testrunner-toolbar { 65 | padding: 0.5em 1em 0.5em 1em; 66 | color: #5E740B; 67 | background-color: #EEE; 68 | overflow: hidden; 69 | } 70 | 71 | #qunit-userAgent { 72 | padding: 0.5em 1em 0.5em 1em; 73 | background-color: #2B81AF; 74 | color: #FFF; 75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 76 | } 77 | 78 | #qunit-modulefilter-container { 79 | float: right; 80 | } 81 | 82 | /** Tests: Pass/Fail */ 83 | 84 | #qunit-tests { 85 | list-style-position: inside; 86 | } 87 | 88 | #qunit-tests li { 89 | padding: 0.4em 1em 0.4em 1em; 90 | border-bottom: 1px solid #FFF; 91 | list-style-position: inside; 92 | } 93 | 94 | #qunit-tests > li { 95 | display: none; 96 | } 97 | 98 | #qunit-tests li.pass, #qunit-tests li.running, #qunit-tests li.fail { 99 | display: list-item; 100 | } 101 | 102 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 103 | display: none; 104 | } 105 | 106 | #qunit-tests li strong { 107 | cursor: pointer; 108 | } 109 | 110 | #qunit-tests li.skipped strong { 111 | cursor: default; 112 | } 113 | 114 | #qunit-tests li a { 115 | padding: 0.5em; 116 | color: #C2CCD1; 117 | text-decoration: none; 118 | } 119 | #qunit-tests li a:hover, 120 | #qunit-tests li a:focus { 121 | color: #000; 122 | } 123 | 124 | #qunit-tests li .runtime { 125 | float: right; 126 | font-size: smaller; 127 | } 128 | 129 | .qunit-assert-list { 130 | margin-top: 0.5em; 131 | padding: 0.5em; 132 | 133 | background-color: #FFF; 134 | 135 | border-radius: 5px; 136 | } 137 | 138 | .qunit-collapsed { 139 | display: none; 140 | } 141 | 142 | #qunit-tests table { 143 | border-collapse: collapse; 144 | margin-top: 0.2em; 145 | } 146 | 147 | #qunit-tests th { 148 | text-align: right; 149 | vertical-align: top; 150 | padding: 0 0.5em 0 0; 151 | } 152 | 153 | #qunit-tests td { 154 | vertical-align: top; 155 | } 156 | 157 | #qunit-tests pre { 158 | margin: 0; 159 | white-space: pre-wrap; 160 | word-wrap: break-word; 161 | } 162 | 163 | #qunit-tests del { 164 | background-color: #E0F2BE; 165 | color: #374E0C; 166 | text-decoration: none; 167 | } 168 | 169 | #qunit-tests ins { 170 | background-color: #FFCACA; 171 | color: #500; 172 | text-decoration: none; 173 | } 174 | 175 | /*** Test Counts */ 176 | 177 | #qunit-tests b.counts { color: #000; } 178 | #qunit-tests b.passed { color: #5E740B; } 179 | #qunit-tests b.failed { color: #710909; } 180 | 181 | #qunit-tests li li { 182 | padding: 5px; 183 | background-color: #FFF; 184 | border-bottom: none; 185 | list-style-position: inside; 186 | } 187 | 188 | /*** Passing Styles */ 189 | 190 | #qunit-tests li li.pass { 191 | color: #3C510C; 192 | background-color: #FFF; 193 | border-left: 10px solid #C6E746; 194 | } 195 | 196 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 197 | #qunit-tests .pass .test-name { color: #366097; } 198 | 199 | #qunit-tests .pass .test-actual, 200 | #qunit-tests .pass .test-expected { color: #999; } 201 | 202 | #qunit-banner.qunit-pass { background-color: #C6E746; } 203 | 204 | /*** Failing Styles */ 205 | 206 | #qunit-tests li li.fail { 207 | color: #710909; 208 | background-color: #FFF; 209 | border-left: 10px solid #EE5757; 210 | white-space: pre; 211 | } 212 | 213 | #qunit-tests > li:last-child { 214 | border-radius: 0 0 5px 5px; 215 | } 216 | 217 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 218 | #qunit-tests .fail .test-name, 219 | #qunit-tests .fail .module-name { color: #000; } 220 | 221 | #qunit-tests .fail .test-actual { color: #EE5757; } 222 | #qunit-tests .fail .test-expected { color: #008000; } 223 | 224 | #qunit-banner.qunit-fail { background-color: #EE5757; } 225 | 226 | /*** Skipped tests */ 227 | 228 | #qunit-tests .skipped { 229 | background-color: #EBECE9; 230 | } 231 | 232 | #qunit-tests .qunit-skipped-label { 233 | background-color: #F4FF77; 234 | display: inline-block; 235 | font-style: normal; 236 | color: #366097; 237 | line-height: 1.8em; 238 | padding: 0 0.5em; 239 | margin: -0.4em 0.4em -0.4em 0; 240 | } 241 | 242 | /** Result */ 243 | 244 | #qunit-testresult { 245 | padding: 0.5em 1em 0.5em 1em; 246 | 247 | color: #2B81AF; 248 | background-color: #D2E0E6; 249 | 250 | border-bottom: 1px solid #FFF; 251 | } 252 | #qunit-testresult .module-name { 253 | font-weight: 700; 254 | } 255 | 256 | /** Fixture */ 257 | 258 | #qunit-fixture { 259 | position: absolute; 260 | top: -10000px; 261 | left: -10000px; 262 | width: 1000px; 263 | height: 1000px; 264 | } 265 | -------------------------------------------------------------------------------- /examples/requirejs-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | tileTemplate Example in Require.js 8 | 9 | 10 | 13 |

output1

14 |
15 |

output2

16 |
17 |

output3

18 |
19 | 68 | 88 | 91 | 92 | 156 | 157 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | tileTemplate Example 8 | 9 | 10 | 13 |

output1

14 |
15 |

output2

16 |
17 |

output3

18 |
19 | 68 | 88 | 91 | 92 | 156 | 157 | -------------------------------------------------------------------------------- /examples/seajs-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | tileTemplate Example in Sea.js 8 | 9 | 10 | 13 |

output1

14 |
15 |

output2

16 |
17 |

output3

18 |
19 | 73 | 110 | 130 | 133 | 137 | 140 | 162 | 163 | 174 | 175 | -------------------------------------------------------------------------------- /tests/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | tileTemplate Test : basic 8 | 9 | 10 |
11 |
12 | 15 | 18 | 25 | 30 | 34 | 39 | 46 | 47 | 48 | 49 | 208 | 209 | -------------------------------------------------------------------------------- /tests/js/templates/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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tileTemplate 2 | 3 | A simple, high performance Javascript template engine. 4 | 5 | 一个简单的、高性能的 Javascript 模板引擎。 6 | 7 | ![](http://pandao.github.io/tiletemplate/tests/test-speed.png) 8 | 9 | > 注:测试结果会因环境而有所不同,仅供参考。 10 | 11 | ### 主要特性 12 | 13 | - 简单小巧,精简后只有 `5K`,开启 gzip 后只有 `2.4K`; 14 | - 原生语法,高性能预编译和渲染模板 [(性能测试)](http://pandao.github.io/tiletemplate/tests/test-speed.html "(性能测试)"); 15 | - 安全机制,过滤和转义危险语句[(安全测试)](http://pandao.github.io/tiletemplate/tests/test-xss-filter.html "(安全测试)"); 16 | - 支持各种前端模块化加载器(CommonJS / AMD / CMD 等)[( Require.js示例 ](http://pandao.github.io/tiletemplate/examples/requirejs-test.html) 、[Sea.js示例 )](http://pandao.github.io/tiletemplate/examples/seajs-test.html); 17 | - 支持在 `Node.js` 环境下运行(`v1.6.0 起支持预编译模板文件为 CMD 模块`),同时也支持 `Express.js`; 18 | - 支持调试,精确定位并通过控制台输出和显示错误或异常信息([查看调试](http://pandao.github.io/tiletemplate/tests/test-debug.html)); 19 | - 支持所有主流的浏览器(`IE6+`); 20 | - 支持 `include` 和自定义标签语法; 21 | 22 | ### 下载和安装 23 | 24 | - [源码 tiletemplate.js](https://github.com/pandao/tileTemplate/tree/master/src/tiletemplate.js "源码") 25 | - [压缩版 tiletemplate.min.js](https://github.com/pandao/tileTemplate/tree/master/dist/tiletemplate.min.js "压缩版") 26 | 27 | 通过 NPM 安装: 28 | 29 | ```shell 30 | npm install tiletemplate 31 | ``` 32 | 33 | 通过 Bower 安装: 34 | 35 | ```shell 36 | bower install tiletemplate 37 | ``` 38 | 39 | ### 使用方法 40 | 41 | 编写模板: 42 | 43 | ```html 44 | 45 | 56 | ``` 57 | 58 | 预编译模板: 59 | 60 | ```javascript 61 | var data = { 62 | title : "标题XXX", 63 | list : [] 64 | }; 65 | 66 | // 返回一个函数 67 | var compiler = tileTemplate.compile(document.getElementById('test-tpl').innerHTML, data); 68 | ``` 69 | 70 | 渲染模板: 71 | 72 | ```javascript 73 | var data = { 74 | title : "标题XXX", 75 | list : [] 76 | }; 77 | 78 | for (var i = 0; i < 10; i ++) { 79 | data.list.push({ 80 | index: (i+1), 81 | user: 'tileTemplate '+(i+1)+'', 82 | site: 'https://github.com/pandao/tileTemplate' 83 | }); 84 | } 85 | 86 | // 输出HTML 87 | // document.getElementById('output').innerHTML = compiler(data); 88 | document.getElementById('output3').innerHTML = tileTemplate.render("test-tpl", data); 89 | ``` 90 | 91 | > 注:同时也支持在 [Require.js](http://pandao.github.io/tiletemplate/examples/requirejs-test.html) 和 [Sea.js](http://pandao.github.io/tiletemplate/examples/seajs-test.html) 中使用。 92 | 93 | ### 在 Node.js 使用: 94 | 95 | ```javascript 96 | var tileTemplate = require("../src/tiletemplate.node"); 97 | 98 | // 通过 NPM 安装的 99 | // var tileTemplate = require('tiletemplate'); 100 | 101 | // 设置基本目录 102 | tileTemplate.config("basePath", __dirname + "/tpl/"); 103 | 104 | // tileTemplate.render(文件名/模板内容, 数据, 编码); 105 | // console.log(tileTemplate.render("Hello <%=str%>", {str:"wolrd!"})); 106 | 107 | // 预编译某个模板,用于循环渲染 108 | //var compiler = tileTemplate.compile(tileTemplate.readFile("list"), {list : [...]}); 109 | 110 | // v1.5.0 版本起无需填写扩展名,默认为 tile.html,可另行配置 111 | var html = tileTemplate.render("test", data); 112 | var http = require('http'); 113 | 114 | http.createServer(function (request, response) { 115 | response.writeHead(200, {'Content-Type': 'text/html'}); 116 | response.end(html); 117 | }).listen(8888); 118 | 119 | console.log('Server running at http://127.0.0.1:8888/'); 120 | ``` 121 | 122 | > 注:`tileTemplate.readFile(文件名, 编码)` 方法只能在 `Node.js` 下使用。 123 | 124 | ### 在 Express.js 中使用 125 | 126 | ```javascript 127 | var express = require('express'); 128 | var app = express(); 129 | var tileTemplate = require("tiletemplate"); 130 | 131 | // 初始化Express支持 132 | tileTemplate.expressInit(app, __dirname + "/tpl/"); 133 | 134 | app.get('/', function (req, res) { 135 | res.render('index', data); // v1.5.0 版本起无需填写扩展名,默认为 tile.html,可另行配置 136 | }); 137 | 138 | var server = app.listen(3000, function() { 139 | console.log('Listening on port %d', server.address().port); 140 | }); 141 | ``` 142 | 143 | ### 主要语法 144 | 145 | `tileTemplate` 目前只支持原生语法。 146 | 147 | 文本输出: 148 | 149 | ```html 150 | <%=变量%> 151 | 152 | ``` 153 | 154 | JS语句: 155 | 156 | ```html 157 | <% if (list.length > 0) { %> 158 |

Total: <%=list.length%>

159 | <% } else { %> 160 |

暂时没有

161 | <% } %> 162 | 163 | <% var total = list.length; %> 164 | 165 | <%=(list.index>1?'>1':'<1')%> 166 | ... 167 | ``` 168 | 169 | 变量注释: 170 | 171 | ```html 172 | <%=#变量%> 173 | ``` 174 | 175 | 行注释: 176 | 177 | ```html 178 | //注释文本 179 | //<%=(list.index>1?'>1':'<1')%> 180 | <% /* 注释文本 */ %> 181 | 182 | ``` 183 | 184 | 嵌套模板(支持多级嵌套): 185 | 186 | ```html 187 | <% include('模板id') %> 188 | ``` 189 | 190 | 转义字符(默认不转义字符,需要的在前面加上@): 191 | 192 | ```html 193 | 194 | ``` 195 | 196 | > 作用:过滤和防止 XSS 攻击。例如:当 `avatar` 的值为 `http://xxxx/xx.jpg" onload="alert(123)`。 197 | 198 | 自定义标签语句: 199 | 200 | ```javascript 201 | // 定义标签语句 202 | tileTemplate.tag("em", function(content) { 203 | if(content == 12) { 204 | var img = "http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/0b/tootha_thumb.gif"; 205 | return 'em'+content+''; 206 | } else { 207 | return content.toString(); 208 | } 209 | }); 210 | 211 | tileTemplate.tag("time", function() { 212 | return " time: " + (new Date).getTime(); 213 | }); 214 | 215 | /* 216 | // 使用标签语句 217 | <%=tag:em:12%> 218 | <%=tag:em:haha%> 219 | <%=tag:em:哈哈%> 220 | <%=tag:time%> 221 | */ 222 | ``` 223 | 224 | > 注:自定义标签语句只能输出字符串。 225 | 226 | ### 主要方法 227 | 228 | 默认选项: 229 | 230 | ```javascript 231 | { 232 | debug : false, // 是否开启调试功能,默认不开启,在生产环境下,这样能获得更高的性能和速度;开发时,请开启; 233 | cached : true, // 是否开启缓存,默认开启,性能更好 234 | filter : true, // 是否过滤模板中的危险语句等,如alert等 235 | openTag : "<%", // 模板开始标签 236 | closeTag : "%>" // 模板结束标签 237 | } 238 | ``` 239 | 240 | 修改和设置配置选项: 241 | 242 | ```javascript 243 | // 使用set或config方法修改配置选项,config为别名 244 | // 批量设置 245 | tileTemplate.set({ 246 | openTag : "{{", 247 | closeTag : "}}" 248 | }); 249 | 250 | tileTemplate.config({ 251 | openTag : "{{", 252 | closeTag : "}}" 253 | }); 254 | 255 | // 单个设置 256 | tileTemplate.set("openTag", "{{"); 257 | tileTemplate.config("openTag", "{{"); 258 | ``` 259 | 260 | 渲染模板: 261 | 262 | ```javascript 263 | /** 264 | * @param {String} id 模板的 ID,或者直接传入模板内容 265 | * @param {Object} data 传入模板的数据 266 | * @param {String} filename 当不通过ID获取模板,而是直接传入模板,需要设置一个模板名称 267 | * @param {Boolean} isModule 是否预编译为 CMD 模块 268 | * @return {Function|String} html 返回模板渲染后的 HTML 或预编译模块的字符串 269 | */ 270 | 271 | tileTemplate.render(id, data, filename, isModule); 272 | ``` 273 | 274 | 预编译模板: 275 | 276 | ```javascript 277 | /** 278 | * @param {String} tpl 模板的内容 279 | * @param {Object} data 传入模板的数据,默认为 {} 280 | * @param {Object} options 配置选项,参数为:include 是否为嵌套的模板,name 模板ID,isModule 是否预编译为 CMD 模块 281 | * @return {Function|String} fn 返回预编译函数或预编译模块的字符串 282 | */ 283 | 284 | tileTemplate.compile(tpl, data, options); 285 | ``` 286 | 287 | 自定义标签语句: 288 | 289 | ```javascript 290 | /** 291 | * @param {String} name 标签名称 292 | * @param {Function} callback 处理标签的回调方法,参数为content,代表标签语句传入的参数 293 | * @return {Void} void 无返回值 294 | */ 295 | 296 | tileTemplate.tag(name, callback); 297 | ``` 298 | 299 | 清除某个模板的缓存: 300 | 301 | ```javascript 302 | /** 303 | * @param {String} id 模板的ID或者文件名 304 | * @return {Boolean} bool 返回 true 305 | */ 306 | 307 | tileTemplate.clear(id); 308 | ``` 309 | 310 | 扩展 tileTemplate: 311 | 312 | ```javascript 313 | tileTemplate.xxx = xxxx; 314 | 315 | // 或者 316 | tileTemplate.extend({ 317 | xxx : "xxxx" 318 | methodXXX : function() { 319 | //... 320 | } 321 | }); 322 | ``` 323 | 324 | ### 更新日志 325 | 326 | [查看更新日志](https://github.com/pandao/tileTemplate/blob/master/CHANGE.md) 327 | 328 | ### License 329 | 330 | The MIT License (MIT) 331 | 332 | Copyright (c) 2014-2016 Pandao 333 | -------------------------------------------------------------------------------- /tests/js/templates/template.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012, KISSY UI Library v1.20 3 | MIT Licensed 4 | build time: Jan 10 19:03 5 | */ 6 | /** 7 | * @fileoverview KISSY Template Engine. 8 | * @author yyfrankyy@gmail.com 9 | */ 10 | KISSY.add('template/base', function(S) { 11 | 12 | var // Template Cache 13 | templateCache = {}, 14 | 15 | // start/end tag mark 16 | tagStartEnd = { 17 | '#': 'start', 18 | '/': 'end' 19 | }, 20 | 21 | // static string 22 | KS_TEMPL_STAT_PARAM = 'KS_TEMPL_STAT_PARAM', 23 | KS_TEMPL_STAT_PARAM_REG = new RegExp(KS_TEMPL_STAT_PARAM, "g"), 24 | KS_TEMPL = 'KS_TEMPL', 25 | KS_DATA = 'KS_DATA_', 26 | KS_AS = 'as', 27 | 28 | // note : double quote for generated code 29 | PREFIX = '");', 30 | SUFFIX = KS_TEMPL + '.push("', 31 | 32 | PARSER_SYNTAX_ERROR = 'KISSY.Template: Syntax Error. ', 33 | PARSER_RENDER_ERROR = 'KISSY.Template: Render Error. ', 34 | 35 | PARSER_PREFIX = 'var ' + KS_TEMPL + '=[],' + 36 | KS_TEMPL_STAT_PARAM + '=false;with(', 37 | 38 | PARSER_MIDDLE = '||{}){try{' + KS_TEMPL + '.push("', 39 | 40 | PARSER_SUFFIX = '");}catch(e){' + KS_TEMPL + '=["' + 41 | PARSER_RENDER_ERROR + '" + e.message]}};return ' + 42 | KS_TEMPL + '.join("");', 43 | 44 | // restore double quote in logic template variable 45 | restoreQuote = function(str) { 46 | return str.replace(/\\"/g, '"'); 47 | }, 48 | 49 | // escape double quote in template 50 | escapeQuote = function(str) { 51 | return str.replace(/"/g, '\\"'); 52 | }, 53 | 54 | trim = S.trim, 55 | 56 | // build a static parser 57 | buildParser = function(tpl) { 58 | var _parser, 59 | _empty_index; 60 | return escapeQuote(trim(tpl) 61 | .replace(/[\r\t\n]/g, ' ') 62 | // escape escape ... . in case \ is consumed when run tpl parser function 63 | // '{{y}}\\x{{/y}}' =>tmpl.push('\x'); => tmpl.push('\\x'); 64 | .replace(/\\/g, '\\\\')) 65 | .replace(/\{\{([#/]?)(?!\}\})([^}]*)\}\}/g, 66 | function(all, expr, body) { 67 | _parser = ""; 68 | // must restore quote , if str is used as code directly 69 | body = restoreQuote(trim(body)); 70 | //body = trim(body); 71 | // is an expression 72 | if (expr) { 73 | _empty_index = body.indexOf(' '); 74 | body = _empty_index === -1 ? 75 | [ body, '' ] : 76 | [ 77 | body.substring(0, _empty_index), 78 | body.substring(_empty_index) 79 | ]; 80 | 81 | var operator = body[0], 82 | fn, 83 | args = trim(body[1]), 84 | opStatement = Statements[operator]; 85 | 86 | if (opStatement && tagStartEnd[expr]) { 87 | // get expression definition function/string 88 | fn = opStatement[tagStartEnd[expr]]; 89 | _parser = String(S.isFunction(fn) ? 90 | fn.apply(this, args.split(/\s+/)) : 91 | fn.replace(KS_TEMPL_STAT_PARAM_REG, args)); 92 | } 93 | } 94 | // return array directly 95 | else { 96 | _parser = KS_TEMPL + 97 | '.push(' + 98 | // prevent variable undefined error when look up in with ,simple variable substitution 99 | // with({}){alert(x);} => ReferenceError: x is not defined 100 | 'typeof (' + body + ') ==="undefined"?"":' + body + 101 | ');'; 102 | } 103 | return PREFIX + _parser + SUFFIX; 104 | 105 | }); 106 | }, 107 | 108 | // expression 109 | Statements = { 110 | 'if': { 111 | start: 'if(typeof (' + KS_TEMPL_STAT_PARAM + ') !=="undefined" && ' + KS_TEMPL_STAT_PARAM + '){', 112 | end: '}' 113 | }, 114 | 115 | 'else': { 116 | start: '}else{' 117 | }, 118 | 119 | 'elseif': { 120 | start: '}else if(' + KS_TEMPL_STAT_PARAM + '){' 121 | }, 122 | 123 | // KISSY.each function wrap 124 | 'each': { 125 | start: function(obj, as, v, k) { 126 | var _ks_value = '_ks_value', 127 | _ks_index = '_ks_index'; 128 | if (as === KS_AS && v) { 129 | _ks_value = v || _ks_value, 130 | _ks_index = k || _ks_index; 131 | } 132 | return 'KISSY.each(' + obj + 133 | ', function(' + _ks_value + 134 | ', ' + _ks_index + '){'; 135 | }, 136 | end: '});' 137 | }, 138 | 139 | // comments 140 | '!': { 141 | start: '/*' + KS_TEMPL_STAT_PARAM + '*/' 142 | } 143 | }; 144 | 145 | /** 146 | * Template 147 | * @param {String} tpl template to be rendered. 148 | * @return {Object} return this for chain. 149 | */ 150 | function Template(tpl) { 151 | if (!(templateCache[tpl])) { 152 | var _ks_data = S.guid(KS_DATA), 153 | func, 154 | o, 155 | _parser = [ 156 | PARSER_PREFIX, 157 | _ks_data, 158 | PARSER_MIDDLE, 159 | o = buildParser(tpl), 160 | PARSER_SUFFIX 161 | ]; 162 | 163 | try { 164 | func = new Function(_ks_data, _parser.join("")); 165 | } catch (e) { 166 | _parser[3] = PREFIX + SUFFIX + 167 | PARSER_SYNTAX_ERROR + ',' + 168 | e.message + PREFIX + SUFFIX; 169 | func = new Function(_ks_data, _parser.join("")); 170 | } 171 | 172 | templateCache[tpl] = { 173 | name: _ks_data, 174 | o:o, 175 | parser: _parser.join(""), 176 | render: func 177 | }; 178 | } 179 | return templateCache[tpl]; 180 | } 181 | 182 | S.mix(Template, { 183 | /** 184 | * Logging Compiled Template Codes 185 | * @param {String} tpl template string. 186 | */ 187 | log: function(tpl) { 188 | if (tpl in templateCache) { 189 | if ('js_beautify' in window) { 190 | // S.log(js_beautify(templateCache[tpl].parser, { 191 | // indent_size: 4, 192 | // indent_char: ' ', 193 | // preserve_newlines: true, 194 | // braces_on_own_line: false, 195 | // keep_array_indentation: false, 196 | // space_after_anon_function: true 197 | // }), 'info'); 198 | } else { 199 | S.log(templateCache[tpl].parser, 'info'); 200 | } 201 | } else { 202 | Template(tpl); 203 | this.log(tpl); 204 | } 205 | }, 206 | 207 | /** 208 | * add statement for extending template tags 209 | * @param {String} statement tag name. 210 | * @param {String} o extent tag object. 211 | */ 212 | addStatement: function(statement, o) { 213 | if (S.isString(statement)) { 214 | Statements[statement] = o; 215 | } else { 216 | S.mix(Statements, statement); 217 | } 218 | } 219 | 220 | }); 221 | 222 | return Template; 223 | 224 | }); 225 | /** 226 | * 2011-09-20 note by yiminghe : 227 | * - code style change 228 | * - remove reg cache , ugly to see 229 | * - fix escape by escape 230 | * - expect(T('{{#if a=="a"}}{{b}}\\"{{/if}}').render({a:"a",b:"b"})).toBe('b\\"'); 231 | */ 232 | /** 233 | * @fileoverview KISSY.Template Node. 234 | * @author 文河 235 | */ 236 | KISSY.add('template/node', function(S, Template, Node) { 237 | var $ = Node.all; 238 | S.mix(S, { 239 | tmpl: function(selector, data) { 240 | return $(Template($(selector).html()).render(data)); 241 | } 242 | }); 243 | 244 | }, {requires:["./base",'node']}); 245 | KISSY.add("template", function(S, T) { 246 | S.Template = T; 247 | return T; 248 | }, { 249 | requires:["template/base","template/node"] 250 | }); 251 | -------------------------------------------------------------------------------- /tests/js/templates/juicer.js: -------------------------------------------------------------------------------- 1 | /* 2 | ********** Juicer ********** 3 | ${A Fast template engine} 4 | Project Home: http://juicer.name 5 | 6 | Author: Guokai 7 | Gtalk: badkaikai@gmail.com 8 | Blog: http://benben.cc 9 | Licence: MIT License 10 | Version: 0.4.0-dev 11 | */ 12 | 13 | (function() { 14 | var juicer = function() { 15 | var args = [].slice.call(arguments); 16 | args.push(juicer.options); 17 | 18 | if(arguments.length == 1) { 19 | return juicer.compile.apply(juicer, args); 20 | } 21 | 22 | if(arguments.length >= 2) { 23 | return juicer.to_html.apply(juicer, args); 24 | } 25 | }; 26 | 27 | var __escapehtml = { 28 | escapehash: { 29 | '<': '<', 30 | '>': '>', 31 | '&': '&', 32 | '"': '"', 33 | "'": ''', 34 | '/': '/' 35 | }, 36 | escapereplace: function(k) { 37 | return __escapehtml.escapehash[k]; 38 | }, 39 | escaping: function(str) { 40 | return typeof(str) !== 'string' ? str : str.replace(/[&<>"]/igm, this.escapereplace); 41 | }, 42 | detection: function(data) { 43 | return typeof(data) === 'undefined' ? '' : data; 44 | } 45 | }; 46 | 47 | var __throw = function(error) { 48 | if(console) { 49 | if(console.warn) { 50 | console.warn(error); 51 | return; 52 | } 53 | 54 | if(console.log) { 55 | console.log(error); 56 | return; 57 | } 58 | } 59 | 60 | throw(error); 61 | }; 62 | 63 | var __creator = function(o, proto) { 64 | o = o !== Object(o) ? {} : o; 65 | 66 | if(o.__proto__) { 67 | o.__proto__ = proto; 68 | return o; 69 | } 70 | 71 | var _Empty = function() {}; 72 | var n = new((_Empty).prototype = proto, _Empty); 73 | 74 | for(var i in o) { 75 | if(o.hasOwnProperty(i)) { 76 | n[i] = o[i]; 77 | } 78 | } 79 | 80 | return n; 81 | }; 82 | 83 | juicer.__cache = {}; 84 | juicer.version = '0.4.0-dev'; 85 | 86 | juicer.settings = { 87 | forstart: /{@each\s*([\w\.]*?)\s*as\s*(\w*?)\s*(,\s*\w*?)?}/igm, 88 | forend: /{@\/each}/igm, 89 | ifstart: /{@if\s*([^}]*?)}/igm, 90 | ifend: /{@\/if}/igm, 91 | elsestart: /{@else}/igm, 92 | elseifstart: /{@else if\s*([^}]*?)}/igm, 93 | interpolate: /\${([\s\S]+?)}/igm, 94 | noneencode: /\$\${([\s\S]+?)}/igm, 95 | inlinecomment: /{#[^}]*?}/igm, 96 | rangestart: /{@each\s*(\w*?)\s*in\s*range\((\d+?),(\d+?)\)}/igm 97 | }; 98 | 99 | juicer.options = { 100 | cache: true, 101 | strip: true, 102 | errorhandling: true, 103 | detection: true, 104 | _method: __creator({ 105 | __escapehtml: __escapehtml, 106 | __throw: __throw 107 | }, this) 108 | }; 109 | 110 | juicer.set = function(conf, value) { 111 | if(arguments.length === 2) { 112 | this.options[conf] = value; 113 | return; 114 | } 115 | 116 | if(conf === Object(conf)) { 117 | for(var i in conf) { 118 | if(conf.hasOwnProperty(i)) { 119 | this.options[i] = conf[i]; 120 | } 121 | } 122 | } 123 | }; 124 | 125 | juicer.register = function(fname, fn) { 126 | var _method = this.options._method; 127 | 128 | if(_method.hasOwnProperty(fname)) { 129 | return false; 130 | } 131 | 132 | return _method[fname] = fn; 133 | }; 134 | 135 | juicer.unregister = function(fname) { 136 | var _method = this.options._method; 137 | 138 | if(_method.hasOwnProperty(fname)) { 139 | return delete _method[fname]; 140 | } 141 | }; 142 | 143 | juicer.template = function(options) { 144 | var that = this; 145 | 146 | this.options = options; 147 | 148 | this.__interpolate = function(_name, _escape, options) { 149 | var _define = _name.split('|'), _fn = ''; 150 | 151 | if(_define.length > 1) { 152 | _name = _define.shift(); 153 | _fn = '_method.' + _define.shift(); 154 | } 155 | 156 | return '<%= ' + (_escape ? '_method.__escapehtml.escaping' : '') + '(' + 157 | (!options || options.detection !== false ? '_method.__escapehtml.detection' : '') + '(' + 158 | _fn + '(' + 159 | _name + 160 | ')' + 161 | ')' + 162 | ')' + 163 | ' %>'; 164 | }; 165 | 166 | this.__removeShell = function(tpl, options) { 167 | var _counter = 0; 168 | 169 | tpl = tpl 170 | //for expression 171 | .replace(juicer.settings.forstart, function($, _name, alias, key) { 172 | var alias = alias || 'value', key = key && key.substr(1); 173 | var _iterate = 'i' + _counter++; 174 | return '<% for(var ' + _iterate + '=0, l' + _iterate + '=' + _name + '.length;' + _iterate + ''; 178 | }) 179 | .replace(juicer.settings.forend, '<% } %>') 180 | 181 | //if expression 182 | .replace(juicer.settings.ifstart, function($, condition) { 183 | return '<% if(' + condition + ') { %>'; 184 | }) 185 | .replace(juicer.settings.ifend, '<% } %>') 186 | 187 | //else expression 188 | .replace(juicer.settings.elsestart, function($) { 189 | return '<% } else { %>'; 190 | }) 191 | 192 | //else if expression 193 | .replace(juicer.settings.elseifstart, function($, condition) { 194 | return '<% } else if(' + condition + ') { %>'; 195 | }) 196 | 197 | //interpolate without escape 198 | .replace(juicer.settings.noneencode, function($, _name) { 199 | return that.__interpolate(_name, false, options); 200 | }) 201 | 202 | //interpolate with escape 203 | .replace(juicer.settings.interpolate, function($, _name) { 204 | return that.__interpolate(_name, true, options); 205 | }) 206 | 207 | //clean up comments 208 | .replace(juicer.settings.inlinecomment, '') 209 | 210 | //range expression 211 | .replace(juicer.settings.rangestart, function($, _name, start, end) { 212 | var _iterate = 'j' + _counter++; 213 | return '<% for(var ' + _iterate + '=0;' + _iterate + '<' + (end - start) + ';' + _iterate + '++) {' + 214 | 'var ' + _name + '=' + _iterate + ';' + 215 | ' %>'; 216 | }); 217 | 218 | //exception handling 219 | if(!options || options.errorhandling !== false) { 220 | tpl = '<% try { %>' + tpl; 221 | tpl += '<% } catch(e) {_method.__throw("Juicer Render Exception: "+e.message);} %>'; 222 | } 223 | 224 | return tpl; 225 | }; 226 | 227 | this.__toNative = function(tpl, options) { 228 | return this.__convert(tpl, !options || options.strip); 229 | }; 230 | 231 | this.__lexicalAnalyze = function(tpl) { 232 | var buffer = []; 233 | var prefix = ''; 234 | 235 | var indexOf = function(array, item) { 236 | if (Array.prototype.indexOf && array.indexOf === Array.prototype.indexOf) { 237 | return array.indexOf(item); 238 | } 239 | 240 | for(var i=0; i < array.length; i++) { 241 | if(array[i] === item) return i; 242 | } 243 | 244 | return -1; 245 | }; 246 | 247 | var variableAnalyze = function($, statement) { 248 | statement = statement.match(/\w+/igm)[0]; 249 | 250 | if(indexOf(buffer, statement) === -1) { 251 | buffer.push(statement); //fuck ie 252 | } 253 | }; 254 | 255 | tpl.replace(juicer.settings.forstart, variableAnalyze). 256 | replace(juicer.settings.interpolate, variableAnalyze). 257 | replace(juicer.settings.ifstart, variableAnalyze); 258 | 259 | for(var i = 0;i < buffer.length; i++) { 260 | prefix += 'var ' + buffer[i] + '=_.' + buffer[i] + ';'; 261 | } 262 | return '<% ' + prefix + ' %>'; 263 | }; 264 | 265 | this.__convert=function(tpl, strip) { 266 | var buffer = [].join(''); 267 | 268 | buffer += "'use strict';"; //use strict mode 269 | buffer += "var _=_||{};"; 270 | buffer += "var _out='';_out+='"; 271 | 272 | if(strip !== false) { 273 | buffer += tpl 274 | .replace(/\\/g, "\\\\") 275 | .replace(/[\r\t\n]/g, " ") 276 | .replace(/'(?=[^%]*%>)/g, "\t") 277 | .split("'").join("\\'") 278 | .split("\t").join("'") 279 | .replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='") 280 | .split("<%").join("';") 281 | .split("%>").join("_out+='")+ 282 | "';return _out;"; 283 | 284 | return buffer; 285 | } 286 | 287 | buffer += tpl 288 | .replace(/\\/g, "\\\\") 289 | .replace(/[\r]/g, "\\r") 290 | .replace(/[\t]/g, "\\t") 291 | .replace(/[\n]/g, "\\n") 292 | .replace(/'(?=[^%]*%>)/g, "\t") 293 | .split("'").join("\\'") 294 | .split("\t").join("'") 295 | .replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='") 296 | .split("<%").join("';") 297 | .split("%>").join("_out+='")+ 298 | "';return _out.replace(/[\\r\\n]\\s+[\\r\\n]/g, '\\r\\n');"; 299 | 300 | return buffer; 301 | }; 302 | 303 | this.parse = function(tpl, options) { 304 | var _that = this; 305 | 306 | if(!options || options.loose !== false) { 307 | tpl = this.__lexicalAnalyze(tpl) + tpl; 308 | } 309 | 310 | tpl = this.__removeShell(tpl, options); 311 | tpl = this.__toNative(tpl, options); 312 | 313 | this._render = new Function('_, _method', tpl); 314 | 315 | this.render = function(_, _method) { 316 | if(!_method || _method !== that.options._method) { 317 | _method = __creator(_method, that.options._method); 318 | } 319 | 320 | return _that._render.call(this, _, _method); 321 | }; 322 | 323 | return this; 324 | }; 325 | }; 326 | 327 | juicer.compile = function(tpl, options) { 328 | if(!options || options !== this.options) { 329 | options = __creator(options, this.options); 330 | } 331 | 332 | try { 333 | var engine = this.__cache[tpl] ? 334 | this.__cache[tpl] : 335 | new this.template(this.options).parse(tpl, options); 336 | 337 | if(!options || options.cache !== false) { 338 | this.__cache[tpl] = engine; 339 | } 340 | 341 | return engine; 342 | 343 | } catch(e) { 344 | __throw('Juicer Compile Exception: ' + e.message); 345 | 346 | return { 347 | render: function() {} //noop 348 | }; 349 | } 350 | }; 351 | 352 | juicer.to_html = function(tpl, data, options) { 353 | if(!options || options !== this.options) { 354 | options = __creator(options, this.options); 355 | } 356 | 357 | return this.compile(tpl, options).render(data, options._method); 358 | }; 359 | 360 | typeof(module) !== 'undefined' && module.exports ? module.exports = juicer : this.juicer = juicer; 361 | })(); -------------------------------------------------------------------------------- /tests/test-speed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | templates test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 337 | 338 | 339 | 340 | 341 | 348 | 349 | 350 | 358 | 359 | 360 | 367 | 368 | 369 | 377 | 378 | 379 | 386 | 387 | 388 | 395 | 396 | 397 | 404 | 405 | 406 | 414 | 415 | 416 | 423 | 424 | 425 | 432 | 433 | 434 | 441 | 442 | 443 | 444 | 452 | 453 | 454 | 461 | 462 | 463 | 464 |

引擎渲染速度测试

465 |

特别说明:为了能直观有效的进行“比较”,本测试用例使用的是 artTemplate 的测试用例,感谢artTemplate的启发和工作。

466 |

条数据 × 次渲染测试

467 |

tileTemplate [debug:false, filter:false, cached:true]

468 |

artTemplate [escape:false, cache:true]

469 |

建议在拥有 v8 javascript 引擎的 chrome 浏览器上进行测试,避免浏览器停止响应

470 |

471 |
472 | 473 | -------------------------------------------------------------------------------- /src/tiletemplate.js: -------------------------------------------------------------------------------- 1 | ;(function(tileTemplate) { 2 | if(typeof require === 'function' && typeof exports === "object" && typeof module === "object") { 3 | // CommonJS/Node.js 4 | tileTemplate(require, exports, module); 5 | } else if(typeof define === "function") { 6 | define(tileTemplate); // AMD/CMD/Sea.js 7 | } else { 8 | tileTemplate(function(){}, window['tileTemplate'] = {}, {}); 9 | } 10 | }(function(require, exports, module) { 11 | "use strict"; 12 | 13 | /** 14 | * @name $ 15 | * @description 通过ID获取DOM对象 16 | * @param {String} id ID名称 17 | * @return {Mixed} return 返回DOM对象或ID 18 | */ 19 | 20 | function $(id) { 21 | return (typeof document !== "undefined") ? document.getElementById(id) : id; 22 | } 23 | 24 | /** 25 | * @name trim 26 | * @description 扩展String对象的清除两边空格的方法 27 | * @return {String} return 返回String对象本身 28 | */ 29 | 30 | if (typeof ''.trim !== "function") { 31 | String.prototype.trim = function () { 32 | return this.replace(/(^\s*)|(\s*$)/g, ""); 33 | }; 34 | } 35 | 36 | var _this = exports; 37 | 38 | /** 39 | * @name version 40 | * @description 版本号 41 | */ 42 | 43 | exports.version = "1.5.0"; 44 | 45 | /** 46 | * @name tags 47 | * @description 存放自定义标签语句的数组 48 | */ 49 | 50 | exports.tags = []; 51 | 52 | /** 53 | * @name caches 54 | * @description 存放缓存的数组 55 | */ 56 | 57 | exports.caches = []; 58 | 59 | /** 60 | * @name regex 61 | * @description 存放正则的对象 62 | */ 63 | 64 | exports.regex = {}; 65 | 66 | /** 67 | * @name extend 68 | * @description 扩展对象 69 | * @param {Object} defaults 要扩展进来的对象 70 | * @param {Object} options 扩展对象本身 71 | * @return {Object} options 返回扩展后的对象 72 | */ 73 | 74 | exports.extend = function(defaults, options) { 75 | options = options || this; 76 | 77 | for (var i in defaults) { 78 | if(typeof options[i] == "undefined") options[i] = defaults[i]; 79 | } 80 | 81 | return options; 82 | }; 83 | 84 | /** 85 | * @name settings 86 | * @description 配置选项 87 | */ 88 | 89 | exports.settings = { 90 | debug : false, // 是否开启调试功能,默认不开启,在生产环境下,这样能获得更高的性能和速度;开发时,请开启; 91 | cached : true, // 是否开启缓存,默认开启,性能更好 92 | filter : true, // 是否过滤模板中的危险语句等,如alert等 93 | openTag : "<%", // 模板开始标签 94 | closeTag : "%>" // 模板结束标签 95 | }; 96 | 97 | /** 98 | * @name set 99 | * @alias config 100 | * @description 过滤模板中的危险语句 101 | * @param {String|Object} key|options 模板文件的内容 102 | * @param {String} value 模板语句的开始标签 103 | * @return {String} result 返回模板文件的内容 104 | */ 105 | 106 | exports.set = exports.config = function() { 107 | if(arguments && arguments.length > 1) { 108 | this.settings[arguments[0]] = arguments[1]; 109 | } else { 110 | this.settings = this.extend(this.settings, arguments[0]); 111 | } 112 | }; 113 | 114 | /** 115 | * @name htmlEncode 116 | * @description 对HTML内容进行编码 117 | * @param {String} html HTML内容 118 | * @return {String} result 返回编码后的HTML内容 119 | */ 120 | 121 | exports.htmlEncode = function(html) { 122 | return html.replace(/\/igm, ">").replace(/\"/igm, """).replace(/\'/igm, "'"); 123 | }; 124 | 125 | /** 126 | * @name htmlDecode 127 | * @description 对编码后的HTML内容进行解码 128 | * @param {String} html 编码后的HTML内容 129 | * @return {String} result 返回解码后HTML内容 130 | */ 131 | 132 | exports.htmlDecode = function(html) { 133 | return html.replace(/\</igm, "<").replace(/\>/igm, ">").replace(/\"/igm, "\"").replace(/\'/igm, "'"); 134 | }; 135 | 136 | /** 137 | * @name filter 138 | * @description 过滤模板中的危险语句 139 | * @param {String} tpl 模板文件的内容 140 | * @param {String} openTag 模板语句的开始标签 141 | * @param {String} closeTag 模板语句的结束标签 142 | * @return {String} result 返回模板文件的内容 143 | */ 144 | 145 | exports.filter = function(tpl, openTag, closeTag) { 146 | tpl = tpl.replace(/\s*<\s*iframe\s*(.*)\s*>\s*<\s*\/iframe\s*>\s*/igm, ""); 147 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*alert\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), ""); 148 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*confirm\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), ""); 149 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*prompt\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), ""); 150 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*window\s*(.|;)\s*(.*)"+closeTag, "igm"), ""); 151 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*document\s*(.|;)\s*(.*)"+closeTag, "igm"), ""); 152 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*createElement\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), ""); 153 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*(\\$|jQuery)\s*(.*)\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), ""); 154 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*eval\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), ""); 155 | 156 | return tpl.trim(); 157 | }; 158 | 159 | /** 160 | * @name compileInclude 161 | * @description 编译嵌套(include)的模板文件的回调函数 162 | * @param {String} data 传入的数据 163 | * @return {String} result 一个以字符串为形式的函数 164 | */ 165 | 166 | exports.compileInclude = function(data) { 167 | var openTag = this.settings.openTag; 168 | var closeTag = this.settings.closeTag; 169 | 170 | return (function($1, $2, $3) { 171 | var guid = (new Date).getTime(); 172 | var id = $2.replace("('", "").replace("')", "").replace("(\"", "").replace("\")", ""); 173 | var includeContent = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML; 174 | var compile = _this.compile(includeContent, data, {include: true, name: id}).toString().split("\n").join(''); 175 | 176 | compile = compile.replace('anonymous', 'anonymous' + guid); 177 | compile += ' if (typeof __data__ !== "undefined") { __out__ += anonymous' + guid + '(__data__); }'; 178 | compile = openTag + " " + compile + " " + closeTag; 179 | 180 | return compile; 181 | }); 182 | }; 183 | 184 | /** 185 | * @name compile 186 | * @description 编译模板文件 187 | * @param {String} tpl 模板文件的内容 188 | * @param {Object} data 传入的数据 189 | * @param {Object} options 配置选项,参数为:include 是否为嵌套的模板,name 模板ID,isModule 是否预编译为 CMD 模块 190 | * @return {Function|String} fn 返回预编译函数或预编译模块的字符串 191 | */ 192 | 193 | exports.compile = function(tpl, data, options) { 194 | var guid = (new Date).getTime(); 195 | var defaults = { 196 | include : false, // 是否为引入 197 | name : "tile" + guid, // 匿名函数 ID 198 | isModule : false // 是否定义为模块,Sea.js / Require.js CMD 模块 199 | }; 200 | 201 | data = data || {}; 202 | options = options || {}; 203 | options = this.extend(defaults, options); 204 | 205 | var include = options.include; 206 | var filter = this.settings.filter; 207 | var debug = this.settings.debug; 208 | var openTag = this.settings.openTag; 209 | var closeTag = this.settings.closeTag; 210 | var regex = new RegExp(openTag + "=(.*?)" + closeTag, "igm"); // or use eval 211 | var varCommentRegex = new RegExp(openTag + "=#(.*?)" + closeTag, "igm"); // variable comment 212 | var escapeRegex = new RegExp(openTag + "=@(.*?)" + closeTag, "igm"); // escape regex 213 | var lineCommentRegex = new RegExp("\/\/" + openTag + "?\\s*(.*?)\\s*" + closeTag + "?", "igm"); // line comment 214 | var tagRegex = /\s*tag:(\w+):?([\u4e00-\u9fa5]+|\w+|\d+)?\s*/igm; 215 | var includeRegex = new RegExp(openTag + "\\s*include\\s*((.*?))\\s*;?\\s*" + closeTag, "igm"); 216 | 217 | var tagRegexHandler = function ($1, $2, $3) { 218 | var str = (typeof $3 == "undefined") ? _this.tags[$2]() : _this.tags[$2]($3.toString()); 219 | 220 | return "'" + str.toString() + "'"; 221 | }; 222 | 223 | this.regex.include = includeRegex; 224 | 225 | tpl = tpl.replace(/(^\s*)|(\s*$)/g, "") // clear spaces 226 | .replace(varCommentRegex, function($1, $2) { return "'+'"; }) 227 | .replace(lineCommentRegex, "") 228 | .replace(this.regex.include, this.compileInclude(data)) 229 | .replace(escapeRegex, function($1, $2) { return "'+__escape(" + $2 + ")+'"; }) 230 | .replace(regex, function($1, $2) { return "'+" + $2 + "+'"; }) 231 | .split("\n"); 232 | 233 | if (debug) { 234 | for (var i = 0, len = tpl.length; i < len; i++) { 235 | tpl[i] = tpl[i].toString().replace(/(^\s*)|(\s*$)/g, "") + "_&_" + openTag + " __line__=" + (i + 1) +"; " + closeTag + "_&_"; 236 | } 237 | 238 | tpl = tpl.join('').split("_&_"); 239 | } 240 | 241 | for (var i = 0, len = tpl.length; i < len; i++) { 242 | tpl[i] = tpl[i].trim(); 243 | 244 | if(filter) tpl[i] = this.filter(tpl[i], openTag, closeTag); 245 | 246 | tpl[i] = "__out__+='" + tpl[i] + "';"; 247 | 248 | tpl[i] = tpl[i].replace(tagRegex, tagRegexHandler); 249 | 250 | tpl[i] = tpl[i].replace("__out__+='" + openTag, "").replace(closeTag + "';", "").replace("}';", "}"); 251 | tpl[i] = tpl[i].replace(/^__out__\+\=\'\/\/(.*?)\'\;$/, function($1, $2, $3) { return "/* " + $2 + " */"; }).replace("'';';", "'';"); 252 | } 253 | 254 | var __escape = 'function __escape(str) {return str.replace(/[\'|\"|\>|\<|;]?/igm, "");}'; 255 | var objectCount = "function objectCount(data){var total=0;for(var i in data){total++} return total;};"; 256 | var errorHandler = "if (typeof console === 'object') {console.error('[tileTemplate]\\n" + options.name + "\\n\\n[type]\\nRender error\\n\\nmessage: '+e.message+'\\nline: '+(e.line+1));}"; 257 | var debugCode = "try {" + tpl.join('') + "\n return __out__; } catch(e) { e.line=__line__; if(objectCount(data) > 0) { "+errorHandler+" }}"; 258 | 259 | tpl = "var __out__ = '', __line__=0;\n " + ( (debug) ? debugCode : tpl.join('') + "\n return __out__;" ); 260 | 261 | var vars = []; 262 | 263 | for (var i in data) { 264 | vars.push(i + ' = __data__.' + i); 265 | } 266 | 267 | vars = (vars.length > 0) ? 'var ' + vars.join(', ') + ';\n' : ''; 268 | 269 | var fn = new Function('__data__', __escape + objectCount + vars + tpl); 270 | 271 | if (options.isModule) { 272 | fn = 'define(function(require, exports, module) {module.exports = '+ fn.toLocaleString().replace('anonymous', '') + '})'; 273 | } 274 | 275 | return fn; 276 | }; 277 | 278 | /** 279 | * @name render 280 | * @description 渲染模板文件 281 | * @param {String} id 模板ID或直接传入模板内容 282 | * @param {Object} data 传入的数据 283 | * @param {String} filename 指定模板名称 284 | * @param {Boolean} [isModule=false] 是否定义为模块 285 | * @return {Function|String} html 返回模板渲染后的 HTML 或预编译模块的字符串 286 | */ 287 | 288 | exports.render = function(id, data, filename, isModule) { 289 | if (typeof isModule === 'undefined') { 290 | isModule = false; 291 | } 292 | 293 | data = data || {}; 294 | filename = filename || ""; 295 | 296 | var cached = this.settings.cached; 297 | var tpl = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML; 298 | var tplName = (filename == "") ? id : filename; 299 | 300 | if (cached && typeof this.caches[tplName] !== "undefined") { // use cache 301 | return this.caches[tplName]; 302 | } else { 303 | var html = this.compile(tpl, data, { 304 | name : (filename == "") ? id : filename, 305 | isModule : isModule 306 | })(data); 307 | 308 | if (cached) { 309 | this.caches[tplName] = html; 310 | } 311 | 312 | return html; 313 | } 314 | }; 315 | 316 | /** 317 | * @name tag 318 | * @description 自定义模板标签语句 319 | * @param {String} name 标签的名称 320 | * @param {Function} callback 处理标签内容的回调函数 321 | * @return {Void} void 无 322 | */ 323 | 324 | exports.tag = function(name, callback) { 325 | this.tags[name] = callback; 326 | }; 327 | 328 | /** 329 | * @name clear 330 | * @description 清除模板缓存 331 | * @param {String} id 要清除的模板 ID 332 | * @return {Boolean} bool 返回 true 333 | */ 334 | 335 | exports.clear = function(id) { 336 | delete this.caches[id]; 337 | 338 | return true; 339 | }; 340 | })); -------------------------------------------------------------------------------- /dist/tiletemplate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tileTemplate 3 | * 4 | * @file tiletemplate.js 5 | * @description A simple, high performance Javascript template engine. 6 | * @version v1.6.0 7 | * @author Pandao 8 | * {@link https://github.com/pandao/tileTemplate} 9 | * @updateTime 2016-12-04 15:02:47 10 | */ 11 | 12 | ;(function(tileTemplate) { 13 | if(typeof require === 'function' && typeof exports === "object" && typeof module === "object") { 14 | // CommonJS/Node.js 15 | tileTemplate(require, exports, module); 16 | } else if(typeof define === "function") { 17 | define(tileTemplate); // AMD/CMD/Sea.js 18 | } else { 19 | tileTemplate(function(){}, window['tileTemplate'] = {}, {}); 20 | } 21 | }(function(require, exports, module) { 22 | "use strict"; 23 | 24 | /** 25 | * @name $ 26 | * @description 通过ID获取DOM对象 27 | * @param {String} id ID名称 28 | * @return {Mixed} return 返回DOM对象或ID 29 | */ 30 | 31 | function $(id) { 32 | return (typeof document !== "undefined") ? document.getElementById(id) : id; 33 | } 34 | 35 | /** 36 | * @name trim 37 | * @description 扩展String对象的清除两边空格的方法 38 | * @return {String} return 返回String对象本身 39 | */ 40 | 41 | if (typeof ''.trim !== "function") { 42 | String.prototype.trim = function () { 43 | return this.replace(/(^\s*)|(\s*$)/g, ""); 44 | }; 45 | } 46 | 47 | var _this = exports; 48 | 49 | /** 50 | * @name version 51 | * @description 版本号 52 | */ 53 | 54 | exports.version = "1.5.0"; 55 | 56 | /** 57 | * @name tags 58 | * @description 存放自定义标签语句的数组 59 | */ 60 | 61 | exports.tags = []; 62 | 63 | /** 64 | * @name caches 65 | * @description 存放缓存的数组 66 | */ 67 | 68 | exports.caches = []; 69 | 70 | /** 71 | * @name regex 72 | * @description 存放正则的对象 73 | */ 74 | 75 | exports.regex = {}; 76 | 77 | /** 78 | * @name extend 79 | * @description 扩展对象 80 | * @param {Object} defaults 要扩展进来的对象 81 | * @param {Object} options 扩展对象本身 82 | * @return {Object} options 返回扩展后的对象 83 | */ 84 | 85 | exports.extend = function(defaults, options) { 86 | options = options || this; 87 | 88 | for (var i in defaults) { 89 | if(typeof options[i] == "undefined") options[i] = defaults[i]; 90 | } 91 | 92 | return options; 93 | }; 94 | 95 | /** 96 | * @name settings 97 | * @description 配置选项 98 | */ 99 | 100 | exports.settings = { 101 | debug : false, // 是否开启调试功能,默认不开启,在生产环境下,这样能获得更高的性能和速度;开发时,请开启; 102 | cached : true, // 是否开启缓存,默认开启,性能更好 103 | filter : true, // 是否过滤模板中的危险语句等,如alert等 104 | openTag : "<%", // 模板开始标签 105 | closeTag : "%>" // 模板结束标签 106 | }; 107 | 108 | /** 109 | * @name set 110 | * @alias config 111 | * @description 过滤模板中的危险语句 112 | * @param {String|Object} key|options 模板文件的内容 113 | * @param {String} value 模板语句的开始标签 114 | * @return {String} result 返回模板文件的内容 115 | */ 116 | 117 | exports.set = exports.config = function() { 118 | if(arguments && arguments.length > 1) { 119 | this.settings[arguments[0]] = arguments[1]; 120 | } else { 121 | this.settings = this.extend(this.settings, arguments[0]); 122 | } 123 | }; 124 | 125 | /** 126 | * @name htmlEncode 127 | * @description 对HTML内容进行编码 128 | * @param {String} html HTML内容 129 | * @return {String} result 返回编码后的HTML内容 130 | */ 131 | 132 | exports.htmlEncode = function(html) { 133 | return html.replace(/\/igm, ">").replace(/\"/igm, """).replace(/\'/igm, "'"); 134 | }; 135 | 136 | /** 137 | * @name htmlDecode 138 | * @description 对编码后的HTML内容进行解码 139 | * @param {String} html 编码后的HTML内容 140 | * @return {String} result 返回解码后HTML内容 141 | */ 142 | 143 | exports.htmlDecode = function(html) { 144 | return html.replace(/\</igm, "<").replace(/\>/igm, ">").replace(/\"/igm, "\"").replace(/\'/igm, "'"); 145 | }; 146 | 147 | /** 148 | * @name filter 149 | * @description 过滤模板中的危险语句 150 | * @param {String} tpl 模板文件的内容 151 | * @param {String} openTag 模板语句的开始标签 152 | * @param {String} closeTag 模板语句的结束标签 153 | * @return {String} result 返回模板文件的内容 154 | */ 155 | 156 | exports.filter = function(tpl, openTag, closeTag) { 157 | tpl = tpl.replace(/\s*<\s*iframe\s*(.*)\s*>\s*<\s*\/iframe\s*>\s*/igm, ""); 158 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*alert\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), ""); 159 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*confirm\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), ""); 160 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*prompt\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), ""); 161 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*window\s*(.|;)\s*(.*)"+closeTag, "igm"), ""); 162 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*document\s*(.|;)\s*(.*)"+closeTag, "igm"), ""); 163 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*createElement\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), ""); 164 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*(\\$|jQuery)\s*(.*)\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), ""); 165 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*eval\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), ""); 166 | 167 | return tpl.trim(); 168 | }; 169 | 170 | /** 171 | * @name compileInclude 172 | * @description 编译嵌套(include)的模板文件的回调函数 173 | * @param {String} data 传入的数据 174 | * @return {String} result 一个以字符串为形式的函数 175 | */ 176 | 177 | exports.compileInclude = function(data) { 178 | var openTag = this.settings.openTag; 179 | var closeTag = this.settings.closeTag; 180 | 181 | return (function($1, $2, $3) { 182 | var guid = (new Date).getTime(); 183 | var id = $2.replace("('", "").replace("')", "").replace("(\"", "").replace("\")", ""); 184 | var includeContent = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML; 185 | var compile = _this.compile(includeContent, data, {include: true, name: id}).toString().split("\n").join(''); 186 | 187 | compile = compile.replace('anonymous', 'anonymous' + guid); 188 | compile += ' if (typeof __data__ !== "undefined") { __out__ += anonymous' + guid + '(__data__); }'; 189 | compile = openTag + " " + compile + " " + closeTag; 190 | 191 | return compile; 192 | }); 193 | }; 194 | 195 | /** 196 | * @name compile 197 | * @description 编译模板文件 198 | * @param {String} tpl 模板文件的内容 199 | * @param {Object} data 传入的数据 200 | * @param {Object} options 配置选项,参数为:include 是否为嵌套的模板,name 模板ID,isModule 是否预编译为 CMD 模块 201 | * @return {Function|String} fn 返回预编译函数或预编译模块的字符串 202 | */ 203 | 204 | exports.compile = function(tpl, data, options) { 205 | var guid = (new Date).getTime(); 206 | var defaults = { 207 | include : false, // 是否为引入 208 | name : "tile" + guid, // 匿名函数 ID 209 | isModule : false // 是否定义为模块,Sea.js / Require.js CMD 模块 210 | }; 211 | 212 | data = data || {}; 213 | options = options || {}; 214 | options = this.extend(defaults, options); 215 | 216 | var include = options.include; 217 | var filter = this.settings.filter; 218 | var debug = this.settings.debug; 219 | var openTag = this.settings.openTag; 220 | var closeTag = this.settings.closeTag; 221 | var regex = new RegExp(openTag + "=(.*?)" + closeTag, "igm"); // or use eval 222 | var varCommentRegex = new RegExp(openTag + "=#(.*?)" + closeTag, "igm"); // variable comment 223 | var escapeRegex = new RegExp(openTag + "=@(.*?)" + closeTag, "igm"); // escape regex 224 | var lineCommentRegex = new RegExp("\/\/" + openTag + "?\\s*(.*?)\\s*" + closeTag + "?", "igm"); // line comment 225 | var tagRegex = /\s*tag:(\w+):?([\u4e00-\u9fa5]+|\w+|\d+)?\s*/igm; 226 | var includeRegex = new RegExp(openTag + "\\s*include\\s*((.*?))\\s*;?\\s*" + closeTag, "igm"); 227 | 228 | var tagRegexHandler = function ($1, $2, $3) { 229 | var str = (typeof $3 == "undefined") ? _this.tags[$2]() : _this.tags[$2]($3.toString()); 230 | 231 | return "'" + str.toString() + "'"; 232 | }; 233 | 234 | this.regex.include = includeRegex; 235 | 236 | tpl = tpl.replace(/(^\s*)|(\s*$)/g, "") // clear spaces 237 | .replace(varCommentRegex, function($1, $2) { return "'+'"; }) 238 | .replace(lineCommentRegex, "") 239 | .replace(this.regex.include, this.compileInclude(data)) 240 | .replace(escapeRegex, function($1, $2) { return "'+__escape(" + $2 + ")+'"; }) 241 | .replace(regex, function($1, $2) { return "'+" + $2 + "+'"; }) 242 | .split("\n"); 243 | 244 | if (debug) { 245 | for (var i = 0, len = tpl.length; i < len; i++) { 246 | tpl[i] = tpl[i].toString().replace(/(^\s*)|(\s*$)/g, "") + "_&_" + openTag + " __line__=" + (i + 1) +"; " + closeTag + "_&_"; 247 | } 248 | 249 | tpl = tpl.join('').split("_&_"); 250 | } 251 | 252 | for (var i = 0, len = tpl.length; i < len; i++) { 253 | tpl[i] = tpl[i].trim(); 254 | 255 | if(filter) tpl[i] = this.filter(tpl[i], openTag, closeTag); 256 | 257 | tpl[i] = "__out__+='" + tpl[i] + "';"; 258 | 259 | tpl[i] = tpl[i].replace(tagRegex, tagRegexHandler); 260 | 261 | tpl[i] = tpl[i].replace("__out__+='" + openTag, "").replace(closeTag + "';", "").replace("}';", "}"); 262 | tpl[i] = tpl[i].replace(/^__out__\+\=\'\/\/(.*?)\'\;$/, function($1, $2, $3) { return "/* " + $2 + " */"; }).replace("'';';", "'';"); 263 | } 264 | 265 | var __escape = 'function __escape(str) {return str.replace(/[\'|\"|\>|\<|;]?/igm, "");}'; 266 | var objectCount = "function objectCount(data){var total=0;for(var i in data){total++} return total;};"; 267 | var errorHandler = "if (typeof console === 'object') {console.error('[tileTemplate]\\n" + options.name + "\\n\\n[type]\\nRender error\\n\\nmessage: '+e.message+'\\nline: '+(e.line+1));}"; 268 | var debugCode = "try {" + tpl.join('') + "\n return __out__; } catch(e) { e.line=__line__; if(objectCount(data) > 0) { "+errorHandler+" }}"; 269 | 270 | tpl = "var __out__ = '', __line__=0;\n " + ( (debug) ? debugCode : tpl.join('') + "\n return __out__;" ); 271 | 272 | var vars = []; 273 | 274 | for (var i in data) { 275 | vars.push(i + ' = __data__.' + i); 276 | } 277 | 278 | vars = (vars.length > 0) ? 'var ' + vars.join(', ') + ';\n' : ''; 279 | 280 | var fn = new Function('__data__', __escape + objectCount + vars + tpl); 281 | 282 | if (options.isModule) { 283 | fn = 'define(function(require, exports, module) {module.exports = '+ fn.toLocaleString().replace('anonymous', '') + '})'; 284 | } 285 | 286 | return fn; 287 | }; 288 | 289 | /** 290 | * @name render 291 | * @description 渲染模板文件 292 | * @param {String} id 模板ID或直接传入模板内容 293 | * @param {Object} data 传入的数据 294 | * @param {String} filename 指定模板名称 295 | * @param {Boolean} [isModule=false] 是否定义为模块 296 | * @return {Function|String} html 返回模板渲染后的 HTML 或预编译模块的字符串 297 | */ 298 | 299 | exports.render = function(id, data, filename, isModule) { 300 | if (typeof isModule === 'undefined') { 301 | isModule = false; 302 | } 303 | 304 | data = data || {}; 305 | filename = filename || ""; 306 | 307 | var cached = this.settings.cached; 308 | var tpl = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML; 309 | var tplName = (filename == "") ? id : filename; 310 | 311 | if (cached && typeof this.caches[tplName] !== "undefined") { // use cache 312 | return this.caches[tplName]; 313 | } else { 314 | var html = this.compile(tpl, data, { 315 | name : (filename == "") ? id : filename, 316 | isModule : isModule 317 | })(data); 318 | 319 | if (cached) { 320 | this.caches[tplName] = html; 321 | } 322 | 323 | return html; 324 | } 325 | }; 326 | 327 | /** 328 | * @name tag 329 | * @description 自定义模板标签语句 330 | * @param {String} name 标签的名称 331 | * @param {Function} callback 处理标签内容的回调函数 332 | * @return {Void} void 无 333 | */ 334 | 335 | exports.tag = function(name, callback) { 336 | this.tags[name] = callback; 337 | }; 338 | 339 | /** 340 | * @name clear 341 | * @description 清除模板缓存 342 | * @param {String} id 要清除的模板 ID 343 | * @return {Boolean} bool 返回 true 344 | */ 345 | 346 | exports.clear = function(id) { 347 | delete this.caches[id]; 348 | 349 | return true; 350 | }; 351 | })); -------------------------------------------------------------------------------- /examples/js/require.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.15 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||g.onError!==ca)try{f=i.execCb(c,l,b,f)}catch(d){a=d}else f=i.execCb(c,l,b,f);this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports:this.usingExports&& 19 | (f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,g.onResourceLoad))g.onResourceLoad(i,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a= 20 | this.map,b=a.id,d=p(a.prefix);this.depMaps.push(d);q(d,"defined",u(this,function(f){var l,d;d=m(aa,this.map.id);var e=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,n=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(e=f.normalize(e,function(a){return c(a,P,!0)})||""),f=p(a.prefix+"!"+e,this.map.parentMap),q(f,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(h,f.id)){this.depMaps.push(f); 21 | if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=i.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(h,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,e=p(d),P=M;c&&(f=c);P&&(M=!1);s(e);t(j.config,b)&&(j.config[d]=j.config[b]);try{g.exec(f)}catch(h){return w(C("fromtexteval", 22 | "fromText eval for "+b+" failed: "+h,h,[b]))}P&&(M=!0);this.depMaps.push(e);i.completeLoad(d);n([d],l)}),f.load(a.name,n,l,j))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=p(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;q(a,"defined",u(this,function(a){this.defineDep(b, 23 | a);this.check()}));this.errback&&q(a,"error",u(this,this.errback))}c=a.id;f=h[c];!t(L,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,u(this,function(a){var b=m(h,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:j,contextName:b,registry:h,defined:r,urlFetched:S,defQueue:A,Module:Z,makeModuleMap:p, 24 | nextTick:g.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(j[b]||(j[b]={}),U(j[b],a,!0,!0)):j[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(aa[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);b[c]=a}),j.shim=b);a.packages&&v(a.packages,function(a){var b, 25 | a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(j.paths[b]=a.location);j.pkgs[b]=a.name+"/"+(a.main||"main").replace(ia,"").replace(Q,"")});B(h,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=p(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,e){function j(c,d,m){var n,q;e.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild= 26 | !0);if("string"===typeof c){if(G(d))return w(C("requireargs","Invalid require call"),m);if(a&&t(L,c))return L[c](h[a.id]);if(g.get)return g.get(i,c,a,j);n=p(c,a,!1,!0);n=n.id;return!t(r,n)?w(C("notloaded",'Module name "'+n+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[n]}J();i.nextTick(function(){J();q=s(p(null,a));q.skipMap=e.skipMap;q.init(c,d,m,{enabled:!0});D()});return j}e=e||{};U(j,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),k=b.split("/")[0];if(-1!== 27 | e&&(!("."===k||".."===k)||1e.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,e.attachEvent("onreadystatechange",b.onScriptLoad)): 34 | (e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)),e.src=d,J=e,D?y.insertBefore(e,D):y.appendChild(e),J=null,e;if(ea)try{importScripts(d),b.completeLoad(c)}catch(m){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,m,[c]))}};z&&!q.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return s=I,q.baseUrl||(E=s.split("/"),s=E.pop(),O=E.length?E.join("/")+"/":"./",q.baseUrl= 35 | O),s=s.replace(Q,""),g.jsExtRegExp.test(s)&&(s=I),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ka,"").replace(la,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b|| 36 | (b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}(g?g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(q)}})(this); -------------------------------------------------------------------------------- /tests/js/templates/mustache.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * mustache.js - Logic-less {{mustache}} templates with JavaScript 3 | * http://github.com/janl/mustache.js 4 | */ 5 | var Mustache = (typeof module !== "undefined" && module.exports) || {}; 6 | 7 | (function (exports) { 8 | 9 | exports.name = "mustache.js"; 10 | exports.version = "0.5.0-dev"; 11 | exports.tags = ["{{", "}}"]; 12 | exports.parse = parse; 13 | exports.compile = compile; 14 | exports.render = render; 15 | exports.clearCache = clearCache; 16 | 17 | // This is here for backwards compatibility with 0.4.x. 18 | exports.to_html = function (template, view, partials, send) { 19 | var result = render(template, view, partials); 20 | 21 | if (typeof send === "function") { 22 | send(result); 23 | } else { 24 | return result; 25 | } 26 | }; 27 | 28 | var _toString = Object.prototype.toString; 29 | var _isArray = Array.isArray; 30 | var _forEach = Array.prototype.forEach; 31 | var _trim = String.prototype.trim; 32 | 33 | var isArray; 34 | if (_isArray) { 35 | isArray = _isArray; 36 | } else { 37 | isArray = function (obj) { 38 | return _toString.call(obj) === "[object Array]"; 39 | }; 40 | } 41 | 42 | var forEach; 43 | if (_forEach) { 44 | forEach = function (obj, callback, scope) { 45 | return _forEach.call(obj, callback, scope); 46 | }; 47 | } else { 48 | forEach = function (obj, callback, scope) { 49 | for (var i = 0, len = obj.length; i < len; ++i) { 50 | callback.call(scope, obj[i], i, obj); 51 | } 52 | }; 53 | } 54 | 55 | var spaceRe = /^\s*$/; 56 | 57 | function isWhitespace(string) { 58 | return spaceRe.test(string); 59 | } 60 | 61 | var trim; 62 | if (_trim) { 63 | trim = function (string) { 64 | return string == null ? "" : _trim.call(string); 65 | }; 66 | } else { 67 | var trimLeft, trimRight; 68 | 69 | if (isWhitespace("\xA0")) { 70 | trimLeft = /^\s+/; 71 | trimRight = /\s+$/; 72 | } else { 73 | // IE doesn't match non-breaking spaces with \s, thanks jQuery. 74 | trimLeft = /^[\s\xA0]+/; 75 | trimRight = /[\s\xA0]+$/; 76 | } 77 | 78 | trim = function (string) { 79 | return string == null ? "" : 80 | String(string).replace(trimLeft, "").replace(trimRight, ""); 81 | }; 82 | } 83 | 84 | var escapeMap = { 85 | "&": "&", 86 | "<": "<", 87 | ">": ">", 88 | '"': '"', 89 | "'": ''' 90 | }; 91 | 92 | function escapeHTML(string) { 93 | return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { 94 | return escapeMap[s] || s; 95 | }); 96 | } 97 | 98 | /** 99 | * Adds the `template`, `line`, and `file` properties to the given error 100 | * object and alters the message to provide more useful debugging information. 101 | */ 102 | function debug(e, template, line, file) { 103 | file = file || "