主要实现:通过拼接字符串的方式构建编译函数,辅助函数通过在编译函数字符串体内以var methodName=function(){}方式传入,因此编译函数字符串体内就可以使用methodName方式加以调用;用户数据通过向编译函数传参注入,赋值给$data后,就可以使用$data.value的方式使用;if、each语句预先通过parser方法将其拼接为js形式的if、each语法。
1.构建编译函数
compile.js
/** * 编译模板 * 2012-6-6 @TooBug: define 方法名改为 compile,与 Node Express 保持一致 * @name template.compile * @param {String} 模板字符串 * @param {Object} 编译选项 * * - openTag {String} // 逻辑语法开始标签 "{{" 或 "<%" * - closeTag {String} // 逻辑语法开始标签 "}}" 或 "%>" * - filename {String} // 用于报错时提醒用户模板字符串的模板名,并作为cacheStore的属性存储编译函数 * - escape {Boolean} // html字符串转义,编码: <%=value%> 不编码:<%=#value%> * - compress {Boolean} // 是否压缩多余空白和注释 * - debug {Boolean} // 是否开启调试模式编译模板字符串 * - cache {Boolean} // 是否缓存模板字符串编译结果 * - parser {Function} // 语法转换插件钩子,"<%"、"%>"间内部值预处理,默认defaults.parser * * @return {Function} 渲染方法 */ // 通过compiler以字符串形式拼接编译函数体,最终转化成函数输出 var compile = template.compile = function (source, options) { // 合并默认配置 options = options || {}; for (var name in defaults) { if (options[name] === undefined) { options[name] = defaults[name]; } } var filename = options.filename; try { var Render = compiler(source, options); } catch (e) { e.filename = filename || 'anonymous'; e.name = 'Syntax Error'; return showDebugInfo(e); } // 对编译结果进行一次包装 function render (data) { try { return new Render(data, filename) + ''; } catch (e) { // 运行时出错后自动开启调试模式重新编译 if (!options.debug) { options.debug = true; return compile(source, options)(data); } return showDebugInfo(e)(); } } render.prototype = Render.prototype; render.toString = function () { return Render.toString(); }; if (filename && options.cache) { // 缓存模板字符串解析函数 cacheStore[filename] = render; } return render; }; // 数组迭代 var forEach = utils.$each; // 静态分析模板变量 var KEYWORDS = // 关键字 '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' // ECMA 5 - use strict + ',arguments,let,yield' + ',undefined'; // 滤除多行注释、单行注释、单双引号包裹字符串、点号+空格后的字符串 var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g; // 滤除变量,如{{if admin}}中的admin var SPLIT_RE = /[^\w$]+/g; // 滤除js关键字 var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g'); // 滤除数字 var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g; // 滤除起始、结尾的多个逗号 var BOUNDARY_RE = /^,+|,+$/g; // 以$或,分割 var SPLIT2_RE = /^$|,+/; // 获取变量 function getVariable (code) { return code .replace(REMOVE_RE, '') .replace(SPLIT_RE, ',') .replace(KEYWORDS_RE, '') .replace(NUMBER_RE, '') .replace(BOUNDARY_RE, '') .split(SPLIT2_RE); }; // 字符串转义 function stringify (code) { return "'" + code // 单引号与反斜杠转义 .replace(/('|\\)/g, '\\$1') // 换行符转义(windows + linux) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') + "'"; } // 通过模板字符串和options配置,拼接编译函数体,template.compile方法中转化成函数 function compiler (source, options) { var debug = options.debug;// 是否开启调试模式编译模板字符串 var openTag = options.openTag;// 逻辑语法开始标签 "{{" var closeTag = options.closeTag;// 逻辑语法闭合标签 "}}" var parser = options.parser;// 语法转换插件钩子,默认的钩子为拼接if、each、echo等语句 var compress = options.compress;// 是否压缩多余空白和注释 var escape = options.escape;// html字符串转义,编码: <%=value%> 不编码:<%=#value%> var line = 1; // uniq记录定义编译函数体内已定义的方法名或属性名,防止重复定义 var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1}; var isNewEngine = ''.trim;// '__proto__' in {} var replaces = isNewEngine ? ["$out='';", "$out+=", ";", "$out"] : ["$out=[];", "$out.push(", ");", "$out.join('')"]; var concat = isNewEngine ? "$out+=text;return $out;" : "$out.push(text);"; var print = "function(){" + "var text=''.concat.apply('',arguments);" + concat + "}"; var include = "function(filename,data){" + "data=data||$data;" + "var text=$utils.$include(filename,data,$filename);" + concat + "}"; var headerCode = "'use strict';" + "var $utils=this,$helpers=$utils.$helpers," + (debug ? "$line=0," : ""); var mainCode = replaces[0]; var footerCode = "return new String(" + replaces[3] + ");" // html与逻辑语法分离 forEach(source.split(openTag), function (code) { code = code.split(closeTag); var $0 = code[0]; var $1 = code[1]; // code: [html] 以openTag起始,无closeTag闭合,处理成html字符串形式 if (code.length === 1) { mainCode += html($0); // code: [logic, html] 以openTag起始,有closeTag闭合,处理成logic+html字符串形式 } else { mainCode += logic($0); if ($1) { mainCode += html($1); } } }); var code = headerCode + mainCode + footerCode; // 调试语句,试用try-catch方法捕获错误,报错 if (debug) { code = "try{" + code + "}catch(e){" + "throw {" + "filename:$filename," + "name:'Render Error'," + "message:e.message," + "line:$line," + "source:" + stringify(source) + ".split(/\\n/)[$line-1].replace(/^\\s+/,'')" + "};" + "}"; } try { // code用于拼接字符串构建函数 var Render = new Function("$data", "$filename", code); Render.prototype = utils; return Render; } catch (e) { e.temp = "function anonymous($data,$filename) {" + code + "}"; throw e; } // 处理 HTML 语句 function html (code) { // 记录行号,调试模式下输出处理失败的行号 line += code.split(/\n/).length - 1; // 压缩多余空白与注释 if (compress) { code = code .replace(/\s+/g, ' ') .replace(/<!--[\w\W]*?-->/g, ''); } if (code) { code = replaces[1] + stringify(code) + replaces[2] + "\n"; } return code; } // 处理逻辑语句 function logic (code) { var thisLine = line; if (parser) { // 语法转换插件钩子,默认的钩子为拼接if、each、echo等语句 code = parser(code, options); } else if (debug) { // 记录行号 code = code.replace(/\n/g, function () { line ++; return "$line=" + line + ";"; }); } // 输出语句. 编码: <%=value%> 不编码:<%=#value%> // <%=#value%> 等同 v2.0.3 之前的 <%==value%> if (code.indexOf('=') === 0) { var escapeSyntax = escape && !/^=[=#]/.test(code); code = code.replace(/^=[=#]?|[\s;]*$/g, ''); // 对内容编码 if (escapeSyntax) { var name = code.replace(/\s*\([^\)]+\)/, ''); // 排除 utils.* | include | print,当name值为utils中内部方法或print、include // headerCode中,this关键字指向$utils,$escape可直接调用,对html中'、"、<、>、&进行转义 if (!utils[name] && !/^(include|print)$/.test(name)) { code = "$escape(" + code + ")"; } // 不编码 } else { code = "$string(" + code + ")"; } code = replaces[1] + code + replaces[2]; } if (debug) { code = "$line=" + thisLine + ";" + code; } // 提取模板中的方法名,在headerCode中注入该方法的内容,拼接的函数体内就可以通过方法名调用 forEach(getVariable(code), function (name) { // name 值可能为空,在安卓低版本浏览器下 if (!name || uniq[name]) { return; } var value; // 声明模板变量 // 赋值优先级: // [include, print] > utils > helpers > data if (name === 'print') { value = print; } else if (name === 'include') { value = include; } else if (utils[name]) { value = "$utils." + name; } else if (helpers[name]) { value = "$helpers." + name; } else { value = "$data." + name; } headerCode += name + "=" + value + ","; uniq[name] = true; }); return code + "\n"; } };
2.if、each、print、echo、include语句及过滤函数预处理
syntax.js
// 语法转换插件钩子,"<%"、"%>"间内部值预处理,拼接if、each、print、include、echo语句等,参见compiler模块 defaults.openTag = '{{'; defaults.closeTag = '}}'; // {{value | filterA:'abcd' | filterB}}形式,调用$helpers下方法对value进行过滤处理 var filtered = function (js, filter) { var parts = filter.split(':'); var name = parts.shift(); var args = parts.join(':') || ''; if (args) { args = ', ' + args; } return '$helpers.' + name + '(' + js + args + ')'; } // 语法转换插件钩子,"<%"、"%>"间内部值预处理,拼接if、each、print、include、echo语句等,参见compiler模块 defaults.parser = function (code, options) { // var match = code.match(/([\w\$]*)(\b.*)/); // \b单词边界符 // var key = match[1]; // var args = match[2]; // var split = args.split(' '); // split.shift(); code = code.replace(/^\s/, '');// 滤除起始的空格 var split = code.split(' '); var key = split.shift(); var args = split.join(' '); switch (key) { // 拼接if语句 case 'if': code = 'if(' + args + '){'; break; case 'else': if (split.shift() === 'if') { split = ' if(' + split.join(' ') + ')'; } else { split = ''; } code = '}else' + split + '{'; break; case '/if': code = '}'; break; // 拼接each语句 case 'each': var object = split[0] || '$data'; var as = split[1] || 'as'; var value = split[2] || '$value'; var index = split[3] || '$index'; var param = value + ',' + index; if (as !== 'as') { object = '[]'; } code = '$each(' + object + ',function(' + param + '){'; break; case '/each': code = '});'; break; // 拼接print语句 case 'echo': code = 'print(' + args + ');'; break; // 拼接print、include语句 case 'print': case 'include': code = key + '(' + split.join(',') + ');'; break; default: // 过滤器(辅助方法),value为待过滤的变量,filterA为helpers下方法名,'abcd'为filterA参数 // {{value | filterA:'abcd' | filterB}} // >>> $helpers.filterB($helpers.filterA(value, 'abcd')) // TODO: {{ddd||aaa}} 不包含空格 if (/^\s*\|\s*[\w\$]/.test(args)) { var escape = true; // {{#value | link}} if (code.indexOf('#') === 0) { code = code.substr(1); escape = false; } var i = 0; var array = code.split('|'); var len = array.length; var val = array[i++]; for (; i < len; i ++) { val = filtered(val, array[i]); } code = (escape ? '=' : '=#') + val; // 即将弃用 {{helperName value}} } else if (template.helpers[key]) { code = '=#' + key + '(' + split.join(',') + ');'; // 内容直接输出 {{value}} } else { code = '=' + code; } break; } return code; };
3.辅助函数
utils.js
var toString = function (value, type) { if (typeof value !== 'string') { type = typeof value; if (type === 'number') { value += ''; } else if (type === 'function') { value = toString(value.call(value)); } else { value = ''; } } return value; }; var escapeMap = { "<": "<", ">": ">", '"': """, "'": "'", "&": "&" }; var escapeFn = function (s) { return escapeMap[s]; }; var escapeHTML = function (content) { return toString(content) .replace(/&(?![\w#]+;)|[<>"']/g, escapeFn); }; var isArray = Array.isArray || function (obj) { return ({}).toString.call(obj) === '[object Array]'; }; var each = function (data, callback) { var i, len; if (isArray(data)) { for (i = 0, len = data.length; i < len; i++) { callback.call(data, data[i], i, data); } } else { for (i in data) { callback.call(data, data[i], i); } } }; var utils = template.utils = { $helpers: {}, $include: renderFile, $string: toString, $escape: escapeHTML, $each: each };
helper.js,可由用户添加过滤函数等
/** * 添加模板辅助方法 * @name template.helper * @param {String} 名称 * @param {Function} 方法 */ template.helper = function (name, helper) { helpers[name] = helper; }; var helpers = template.helpers = utils.$helpers;
4.编译接口
template.js
/** * 模板引擎 * @name template * @param {String} 模板名 * @param {Object, String} 数据。如果为字符串,则作为模板字符串进行编译,缓存并返回编译函数 * 如果为对象,则作为传给编译函数的数据,最终返回编译结果 * @return {String, Function} 渲染好的HTML字符串或者渲染方法 */ var template = function (filename, content) { return typeof content === 'string' ? compile(content, { filename: filename }) : renderFile(filename, content); }; template.version = '3.0.0';
renderFile.js
/** * 渲染模板(根据模板名) * @name template.render * @param {String} 模板名,页面元素id * @param {Object} 数据,data传入为空时,返回结果为编译函数 * @return {String} 渲染好的字符串 */ var renderFile = template.renderFile = function (filename, data) { var fn = template.get(filename) || showDebugInfo({ filename: filename, name: 'Render Error', message: 'Template not found' }); return data ? fn(data) : fn; };
get.js
/** * 获取编译缓存(可由外部重写此方法) * @param {String} 模板名 * @param {Function} 编译好的函数 */ template.get = function (filename) { var cache; if (cacheStore[filename]) { // 获取使用内存缓存的编译函数 cache = cacheStore[filename]; } else if (typeof document === 'object') { // 通过模板名获取模板字符串,编译,并返回编译函数 var elem = document.getElementById(filename); if (elem) { var source = (elem.value || elem.innerHTML) .replace(/^\s*|\s*$/g, ''); cache = compile(source, { filename: filename }); } } return cache; };
render.js
/** * 渲染模板 * @name template.render * @param {String} 模板字符串 * @param {Object} 数据 * @return {String} 编译函数 */ template.render = function (source, options) { return compile(source, options); };
5.错误提示
onerror.js
/** * 模板错误事件(可由外部重写此方法),触发console.error提示错误信息 * @name template.onerror * @event */ template.onerror = function (e) { var message = 'Template Error\n\n'; for (var name in e) { message += '<' + name + '>\n' + e[name] + '\n\n'; } if (typeof console === 'object') { console.error(message); } }; // 模板调试器 var showDebugInfo = function (e) { template.onerror(e); return function () { return '{Template Error}'; }; };
6.配置
compile.js
/** * 设置全局配置 * @name template.config * @param {String} 名称 * @param {Any} 值 */ template.config = function (name, value) { defaults[name] = value; }; var defaults = template.defaults = { openTag: '<%', // 逻辑语法开始标签 closeTag: '%>', // 逻辑语法结束标签 escape: true, // 是否编码输出变量的 HTML 字符 cache: true, // 是否开启缓存(依赖 options 的 filename 字段) compress: false, // 是否压缩输出 parser: null // 自定义语法格式器 @see: template-syntax.js };
7.外层包裹,适用于amd/cmd/commonjs环境,同seajs
intro.js
/*! * artTemplate - Template Engine * https://github.com/aui/artTemplate * Released under the MIT, BSD, and GPL Licenses */ !(function () {
outro.js
// RequireJS && SeaJS if (typeof define === 'function') { define(function() { return template; }); // NodeJS } else if (typeof exports !== 'undefined') { module.exports = template; } else { this.template = template; } })();
附:拼接js文件实现使用grunt
Gruntfile.js配置
module.exports = function (grunt) { var sources_native = [ 'src/intro.js', 'src/template.js', 'src/config.js', 'src/cache.js', 'src/render.js', 'src/renderFile.js', 'src/get.js', 'src/utils.js', 'src/helper.js', 'src/onerror.js', 'src/compile.js', //<<<< 'src/syntax.js', 'src/outro.js' ]; var sources_simple = Array.apply(null, sources_native); sources_simple.splice(sources_native.length - 1, 0, 'src/syntax.js'); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), meta: { banner: '/*!<%= pkg.name %> - Template Engine | <%= pkg.homepage %>*/\n' }, concat: { options: { separator: '' }, 'native': { src: sources_native, dest: 'dist/template-native-debug.js' }, simple: { src: sources_simple, dest: 'dist/template-debug.js' } }, uglify: { options: { banner: '<%= meta.banner %>' }, 'native': { src: '<%= concat.native.dest %>', dest: 'dist/template-native.js' }, simple: { src: '<%= concat.simple.dest %>', dest: 'dist/template.js' } }, qunit: { files: ['test/**/*.html'] }, jshint: { files: [ 'dist/template-native.js', 'dist/template.js' ], options: { curly: true, eqeqeq: true, immed: true, latedef: true, newcap: true, noarg: true, sub: true, undef: true, boss: true, eqnull: true, browser: true }, globals: { console: true, define: true, global: true, module: true } }, watch: { files: '<config:lint.files>', tasks: 'lint qunit' } }); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-jshint'); //grunt.loadNpmTasks('grunt-contrib-qunit'); //grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.registerTask('default', ['concat', /*'jshint',*/ 'uglify']); };
package.json
{ "name": "art-template", "description": "JavaScript Template Engine", "homepage": "http://aui.github.com/artTemplate/", "keywords": [ "util", "functional", "template" ], "author": "tangbin <sugarpie.tang@gmail.com>", "repository": { "type": "git", "url": "git://github.com/aui/artTemplate.git" }, "main": "./node/template.js", "version": "3.0.3", "devDependencies": { "express": "~4.4.3", "grunt-cli": "*", "grunt": "*", "grunt-contrib-jshint": "*", "grunt-contrib-concat": "*", "grunt-contrib-uglify": "*" }, "license": "BSD" }
相关推荐
**ArtTemplate模板引擎** ArtTemplate是由腾讯开发的一款高效、轻量级的JavaScript模板引擎,它致力于解决HTML字符串拼接导致的性能问题,提供安全的数据渲染功能,并具备自动防范XSS攻击的能力。通过简单的引入和...
**正文** `artTemplate` 是一个轻量级的前端模板引擎,主要应用于JavaScript环境中,用于实现...在使用过程中,可以结合压缩包中的`artTemplate-master`文件,查看源码、文档和示例,深入理解其工作原理和使用方法。
ArtTemplate 有一个活跃的社区,开发者可以在GitHub上找到源码、文档和示例。同时,有许多优秀的项目和插件基于ArtTemplate,如Sea.js的模块化支持,以及各种定制的模板语法扩展。 总的来说,ArtTemplate 是一个...
"artTemplate-master"可能包含的是该模板引擎的源码和一些示例项目,源码可以帮助开发者深入理解其内部工作原理,而示例项目则提供了实际应用的参考,帮助初学者快速上手。通过查看和运行这些示例,可以直观地看到...
NULL 博文链接:https://dagmom.iteye.com/blog/1671498
包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。 包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python...
本来想把之前对artTemplate源码解析的注释放上来分享下,不过隔了一年,找不到了,只好把当时分析模板引擎原理后,自己尝试 写下的模板引擎与大家分享下,留个纪念,记得当时还对比了好几个模板引擎来着。 这里所说...
后台采用Spring、SpringMVC、MyBatis、FreeMarker等技术,数据库用到MySQL、Redis,前端用到jQuery、artTemplate。 3.本项目采用Maven构建,导入后需要:修改df.properties中的mysql.password,redis.password,...
template 模板放的位置 app.js 项目入口及程序启动文件 package.json 配置信息 modules 数据库模型 controllers 控制器,对请求的操作 tools 工具库 config 配置目录 TODO ------------------------------------ 1. ...
在前后端分离的架构中,前端通过ArtTemplate接收后端API返回的数据,渲染成用户可见的网页内容。 5. **前后端分离**:前后端分离意味着前端和后端职责明确,前端专注于用户体验和视图渲染,后端专注于业务逻辑和...
本项目采用torando + mysql + redis + 七牛 + artTemplate.js+jquery等开源库 tornado作为用户产品后台服务器核心框架 redis 保存session数据、短时间房源信息、地域信息等,保存页面缓存数据,提高服务器响应速度...
template/user/ 为系统会员中心的模版及相关css和js ****************************模板规范化管理 结束**************************** ****************************系统内置JS、CSS说明 开始**************...
在`art-template-docs`这个压缩包中,我们很可能会找到关于Art Template的详细文档和示例,包括`art-template-docs-master`这样的目录,它可能包含了源码、API参考、教程等内容。 **1. 模板引擎的概念与作用** 模板...
组件用到:jquery.jseasyui.js,way.js,artTemplate.js。 本组件可以完成脱离任何平台,只需修改一下就可以在Java,C#,PHP等平台使用。 1.本组件的配置页面用way.js来绑定后面数据,不依赖任何开发平台。 2.搜索...
在这个项目中,开发者运用了AJAX(异步JavaScript和XML)以及artTemplate模板引擎来实现数据的动态加载和页面的高效渲染。 首先,让我们深入了解AJAX。AJAX是一种在无需重新加载整个网页的情况下,能够更新部分网页...
`template`目录下的`art-template`可能是指ArtTemplate,一个JavaScript模板引擎,用于分离HTML和JavaScript,方便数据渲染。 6. **文件组织结构**:压缩包中的文件和目录结构展示了典型的前端项目组织方式,如将...
在本项目实践中,我们主要关注的是使用Node.js作为后端开发平台,Express作为服务器框架,以及Express-Art-Template作为前端模板引擎构建一个信息管理系统。这个系统可能涉及到人工智能的应用,但核心是构建一个高效...
包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。 包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python...
5. **模板引擎**:Layui可能使用了诸如ArtTemplate这样的模板引擎,用于渲染动态数据,提高视图层的效率。 6. **权限控制**:在电商项目中,权限控制是非常重要的一环。Layui可能结合后端的权限框架,如Shiro或...
artTemplate 一款高效的js模板引擎 下划线 非常实用的js函数库 轻弹 小而美的手机横滑框架 延迟加载 一个动态请求资源文件并且加载成功后才执行对应的函数的经典小框架 刮胡子 shave是一个零依赖,轻量级JavaScript...