artTemplate源码
主要实现:通过拼接字符串的方式构建编译函数,辅助函数通过在编译函数字符串体内以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"
 - }
 
其他
[我的博客,欢迎交流!](http://rattenking.gitee.io/stone/index.html)
[我的CSDN博客,欢迎交流!](https://blog.csdn.net/m0_38082783)
[微信小程序专栏](https://blog.csdn.net/column/details/18335.html)
[前端笔记专栏](https://blog.csdn.net/column/details/18321.html)
[微信小程序实现部分高德地图功能的DEMO下载](http://download.csdn.net/download/m0_38082783/10244082)
[微信小程序实现MUI的部分效果的DEMO下载](http://download.csdn.net/download/m0_38082783/10196944)
[微信小程序实现MUI的GIT项目地址](https://github.com/Rattenking/WXTUI-DEMO)
[微信小程序实例列表](http://blog.csdn.net/m0_38082783/article/details/78853722)
[前端笔记列表](http://blog.csdn.net/m0_38082783/article/details/79208205)
[游戏列表](http://blog.csdn.net/m0_38082783/article/details/79035621)
                    
                
                
            
        
浙公网安备 33010602011771号