diff --git a/CHANGELOG.md b/CHANGELOG.md index 16836c94..5a52b14a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## v4.10.0 + +1. 修复标准语法的 BUG [#408](https://github.com/aui/art-template/issues/408),并且不再兼容 v3 的辅助方法调用:`{{helper args}}` +2. 修复 EJS `<%- include(src) %>` 语句兼容问题 + ## v4.9.1 1. 修复模板内部 `$escape` 与 `$each` 变量可能没有定义的问题 [#3](https://github.com/aui/express-art-template/issues/3) [#1](https://github.com/aui/express-art-template/issues/1) diff --git a/lib/compile/adapter/rule.art.js b/lib/compile/adapter/rule.art.js index 3e0deb25..45850310 100644 --- a/lib/compile/adapter/rule.art.js +++ b/lib/compile/adapter/rule.art.js @@ -21,7 +21,7 @@ var artRule = { // 旧版语法升级提示 var warn = function warn(oldSyntax, newSyntax) { - console.warn('Template upgrade:', '{{' + oldSyntax + '}}', '->', '{{' + newSyntax + '}}', '\n', options.filename || ''); + console.warn((options.filename || 'anonymous') + ':' + (match.line + 1) + ':' + (match.start + 1) + '\n' + ('Template upgrade: {{' + oldSyntax + '}} -> {{' + newSyntax + '}}')); }; // v3 compat: #value @@ -141,16 +141,6 @@ var artRule = { filter.unshift(accumulator); return code = '$imports.' + name + '(' + filter.join(',') + ')'; }, target); - } else if (options.imports[key]) { - - // ... v3 compat ... - warn('filterName value', 'value | filterName'); - - group = artRule._split(esTokens); - group.shift(); - - code = key + '(' + group.join(',') + ')'; - output = 'raw'; } else { code = '' + key + values.join(''); } diff --git a/lib/compile/adapter/rule.native.js b/lib/compile/adapter/rule.native.js index 008bbedc..690c5d22 100644 --- a/lib/compile/adapter/rule.native.js +++ b/lib/compile/adapter/rule.native.js @@ -5,7 +5,7 @@ */ var nativeRule = { test: /<%(#?)((?:==|=#|[=-])?)([\w\W]*?)(-?)%>/, - use: function use(match, comment, output, code) { + use: function use(match, comment, output, code /*, trimMode*/) { output = { '-': 'raw', @@ -23,7 +23,7 @@ var nativeRule = { } // ejs compat: trims following newline - // if (trtimMode) {} + // if (trimMode) {} return { code: code, diff --git a/lib/compile/compiler.js b/lib/compile/compiler.js index 30c5588f..de416936 100644 --- a/lib/compile/compiler.js +++ b/lib/compile/compiler.js @@ -38,6 +38,9 @@ var LINE = '$$line'; /** 所有“模板块”变量 */ var BLOCKS = '$$blocks'; +/** 截取模版输出“流”的函数 */ +var SLICE = '$$slice'; + /** 继承的布局模板的文件地址变量 */ var FROM = '$$from'; @@ -85,10 +88,10 @@ var Compiler = function () { this.ignore = [DATA, IMPORTS, OPTIONS].concat(options.ignore); // 按需编译到模板渲染函数的内置变量 - this.internal = (_internal = {}, _internal[OUT] = '\'\'', _internal[LINE] = '[0,0]', _internal[BLOCKS] = 'arguments[1]||{}', _internal[FROM] = 'null', _internal[PRINT] = 'function(){' + OUT + '+=\'\'.concat.apply(\'\',arguments)}', _internal[INCLUDE] = 'function(src,data){' + OUT + '+=' + OPTIONS + '.include(src,data||' + DATA + ',arguments[2]||' + BLOCKS + ',' + OPTIONS + ')}', _internal[EXTEND] = 'function(from){' + FROM + '=from}', _internal[BLOCK] = 'function(name,callback){if(' + FROM + '){' + OUT + '=\'\';callback();' + BLOCKS + '[name]=' + OUT + '}else{if(typeof ' + BLOCKS + '[name]===\'string\'){' + OUT + '+=' + BLOCKS + '[name]}else{callback()}}}', _internal); + this.internal = (_internal = {}, _internal[OUT] = '\'\'', _internal[LINE] = '[0,0]', _internal[BLOCKS] = 'arguments[1]||{}', _internal[FROM] = 'null', _internal[PRINT] = 'function(){var s=\'\'.concat.apply(\'\',arguments);' + OUT + '+=s;return s}', _internal[INCLUDE] = 'function(src,data){var s=' + OPTIONS + '.include(src,data||' + DATA + ',arguments[2]||' + BLOCKS + ',' + OPTIONS + ');' + OUT + '+=s;return s}', _internal[EXTEND] = 'function(from){' + FROM + '=from}', _internal[SLICE] = 'function(c,p,s){p=' + OUT + ';' + OUT + '=\'\';c();s=' + OUT + ';' + OUT + '=p+s;return s}', _internal[BLOCK] = 'function(){var a=arguments,s;if(typeof a[0]===\'function\'){return ' + SLICE + '(a[0])}else if(' + FROM + '){' + BLOCKS + '[a[0]]=' + SLICE + '(a[1])}else{s=' + BLOCKS + '[a[0]];if(typeof s===\'string\'){' + OUT + '+=s}else{s=' + SLICE + '(a[1])}return s}}', _internal); // 内置函数依赖关系声明 - this.dependencies = (_dependencies = {}, _dependencies[PRINT] = [OUT], _dependencies[INCLUDE] = [OUT, OPTIONS, DATA, BLOCKS], _dependencies[EXTEND] = [FROM, /*[*/INCLUDE /*]*/], _dependencies[BLOCK] = [FROM, OUT, BLOCKS], _dependencies); + this.dependencies = (_dependencies = {}, _dependencies[PRINT] = [OUT], _dependencies[INCLUDE] = [OUT, OPTIONS, DATA, BLOCKS], _dependencies[EXTEND] = [FROM, /*[*/INCLUDE /*]*/], _dependencies[BLOCK] = [SLICE, FROM, OUT, BLOCKS], _dependencies); this.importContext(OUT); @@ -448,6 +451,7 @@ Compiler.CONSTS = { OUT: OUT, LINE: LINE, BLOCKS: BLOCKS, + SLICE: SLICE, FROM: FROM, ESCAPE: ESCAPE, EACH: EACH diff --git a/lib/compile/error.js b/lib/compile/error.js index 04d535e8..d3dfe807 100644 --- a/lib/compile/error.js +++ b/lib/compile/error.js @@ -8,61 +8,55 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" /** * 模板错误处理类 + * @param {Object} options */ var TemplateError = function (_Error) { _inherits(TemplateError, _Error); - function TemplateError(error) { + function TemplateError(options) { _classCallCheck(this, TemplateError); - var _this = _possibleConstructorReturn(this, _Error.call(this, error)); - - var message = error.message; - - if (TemplateError.debugTypes[error.name]) { - - if (error.source) { - message = TemplateError.debug(error); - } - - _this.path = error.path; - } + var _this = _possibleConstructorReturn(this, _Error.call(this, options.message)); _this.name = 'TemplateError'; - _this.message = message; + _this.message = formatMessage(options); + if (Error.captureStackTrace) { + Error.captureStackTrace(_this, _this.constructor); + } return _this; } - TemplateError.debug = function debug(error) { - var source = error.source, - path = error.path, - line = error.line, - column = error.column; + return TemplateError; +}(Error); +; - var lines = source.split(/\n/); - var start = Math.max(line - 3, 0); - var end = Math.min(lines.length, line + 3); +function formatMessage(_ref) { + var name = _ref.name, + source = _ref.source, + path = _ref.path, + line = _ref.line, + column = _ref.column, + message = _ref.message; - // Error context - var context = lines.slice(start, end).map(function (code, index) { - var number = index + start + 1; - var left = number === line ? ' >> ' : ' '; - return '' + left + number + '| ' + code; - }).join('\n'); - // Alter exception message - return (path || 'anonymous') + ':' + line + ':' + column + '\n' + (context + '\n\n') + ('' + error.message); - }; + if (!source) { + return message; + } - return TemplateError; -}(Error); + var lines = source.split(/\n/); + var start = Math.max(line - 3, 0); + var end = Math.min(lines.length, line + 3); -; + // Error context + var context = lines.slice(start, end).map(function (code, index) { + var number = index + start + 1; + var left = number === line ? ' >> ' : ' '; + return '' + left + number + '| ' + code; + }).join('\n'); -TemplateError.debugTypes = { - 'RuntimeError': true, - 'CompileError': true -}; + // Alter exception message + return (path || 'anonymous') + ':' + line + ':' + column + '\n' + (context + '\n\n') + (name + ': ' + message); +} module.exports = TemplateError; \ No newline at end of file diff --git a/lib/compile/runtime.js b/lib/compile/runtime.js index 87f06a19..8acd4efa 100644 --- a/lib/compile/runtime.js +++ b/lib/compile/runtime.js @@ -4,9 +4,36 @@ var detectNode = require('detect-node'); var runtime = Object.create(detectNode ? global : window); +var ESCAPE_REG = /["&'<>]/; + +/** + * 编码模板输出的内容 + * @param {any} content + * @return {string} + */ +runtime.$escape = function (content) { + return xmlEscape(toString(content)); +}; + +/** + * 迭代器,支持数组与对象 + * @param {array|Object} data + * @param {function} callback + */ +runtime.$each = function (data, callback) { + if (Array.isArray(data)) { + for (var i = 0, len = data.length; i < len; i++) { + callback(data[i], i); + } + } else { + for (var _i in data) { + callback(data[_i], _i); + } + } +}; // 将目标转成字符 -var toString = function toString(value) { +function toString(value) { if (typeof value !== 'string') { if (value === undefined || value === null) { value = ''; @@ -21,8 +48,7 @@ var toString = function toString(value) { }; // 编码 HTML 内容 -var ESCAPE_REG = /["&'<>]/; -var xmlEscape = function xmlEscape(content) { +function xmlEscape(content) { var html = '' + content; var regexResult = ESCAPE_REG.exec(html); if (!regexResult) { @@ -70,33 +96,4 @@ var xmlEscape = function xmlEscape(content) { } }; -/** - * 编码模板输出的内容 - * @param {any} content - * @return {string} - */ -var escape = function escape(content) { - return xmlEscape(toString(content)); -}; - -/** - * 迭代器,支持数组与对象 - * @param {array|Object} data - * @param {function} callback - */ -var each = function each(data, callback) { - if (Array.isArray(data)) { - for (var i = 0, len = data.length; i < len; i++) { - callback(data[i], i, data); - } - } else { - for (var _i in data) { - callback(data[_i], _i); - } - } -}; - -runtime.$each = each; -runtime.$escape = escape; - module.exports = runtime; \ No newline at end of file diff --git a/lib/compile/tpl-tokenizer.js b/lib/compile/tpl-tokenizer.js index 36f0589e..301b7824 100644 --- a/lib/compile/tpl-tokenizer.js +++ b/lib/compile/tpl-tokenizer.js @@ -5,6 +5,17 @@ var TYPE_EXPRESSION = 'expression'; var TYPE_RAW = 'raw'; var TYPE_ESCAPE = 'escape'; +function Match(content, line, start, end) { + this.content = content; + this.line = line; + this.start = start; + this.end = end; +}; + +Match.prototype.toString = function () { + return this.content; +}; + /** * 将模板转换为 Tokens * @param {string} source @@ -12,7 +23,9 @@ var TYPE_ESCAPE = 'escape'; * @param {Object} context * @return {Object[]} */ -var tplTokenizer = function tplTokenizer(source, rules, context) { +var tplTokenizer = function tplTokenizer(source, rules) { + var context = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var tokens = [{ type: TYPE_STRING, @@ -74,6 +87,7 @@ var tplTokenizer = function tplTokenizer(source, rules, context) { } } else { + values[0] = new Match(values[0], line, start, end); var script = rule.use.apply(context, values); token.script = script; substitute.push(token); diff --git a/lib/template-web.js b/lib/template-web.js index 28d98ac1..dd795063 100644 --- a/lib/template-web.js +++ b/lib/template-web.js @@ -1,3 +1,3 @@ -/*! art-template@4.9.1 for browser | https://github.com/aui/art-template */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.template=t():e.template=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=22)}([function(e,t,n){(function(t){e.exports=!1;try{e.exports="[object process]"===Object.prototype.toString.call(t.process)}catch(n){}}).call(t,n(4))},function(e,t,n){"use strict";var r=n(17),o=n(2),i=n(18),s=function(e,t){t.onerror(e,t);var n=function(){return"{Template Error}"};return n.mappings=[],n.sourcesContent=[],n},a=function c(e){var t=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};"string"!=typeof e?t=e:t.source=e,t=o.$extend(t),e=t.source,t.debug&&(t.cache=!1,t.bail=!1,t.minimize=!1,t.compileDebug=!0),t.compileDebug&&(t.minimize=!1),t.filename&&(t.filename=t.resolveFilename(t.filename,t));var n=t.filename,a=t.cache,u=t.caches;if(a&&n){var p=u.get(n);if(p)return p}if(!e)try{e=t.loader(n,t),t.source=e}catch(d){var l=new i({name:"CompileError",path:n,message:"template not found: "+d.message,stack:d.stack});if(t.bail)throw l;return s(l,t)}var f=void 0,h=new r(t);try{f=h.build()}catch(l){if(l=new i(l),t.bail)throw l;return s(l,t)}var m=function(e,n){try{return f(e,n)}catch(l){if(!t.compileDebug)return t.cache=!1,t.compileDebug=!0,c(t)(e,n);if(l=new i(l),t.bail)throw l;return s(l,t)()}};return m.mappings=f.mappings,m.sourcesContent=f.sourcesContent,m.toString=function(){return f.toString()},a&&n&&u.set(n,m),m};a.Compiler=r,e.exports=a},function(e,t,n){"use strict";function r(){this.$extend=function(e){return e=e||{},s(e,e instanceof r?e:this)}}var o=n(0),i=n(20),s=n(9),a=n(11),c=n(13),u=n(8),p=n(12),l=n(15),f=n(16),h=n(10),m=n(14),d={source:null,filename:null,rules:[f,l],escape:!0,debug:!!o&&"production"!==process.env.NODE_ENV,bail:!1,cache:!0,minimize:!0,compileDebug:!1,resolveFilename:m,include:a,htmlMinifier:h,htmlMinifierOptions:{collapseWhitespace:!0,minifyCSS:!0,minifyJS:!0,ignoreCustomFragments:[]},onerror:c,loader:p,caches:u,root:"/",extname:".art",ignore:[],imports:i};r.prototype=d,e.exports=new r},function(e,t){},function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(r){"object"==typeof window&&(n=window)}e.exports=n},function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=/((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyu]{1,5}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g,t.matchToToken=function(e){var t={type:"invalid",value:e[0]};return e[1]?(t.type="string",t.closed=!(!e[3]&&!e[4])):e[5]?t.type="comment":e[6]?(t.type="comment",t.closed=!!e[7]):e[8]?t.type="regex":e[9]?t.type="number":e[10]?t.type="name":e[11]?t.type="punctuator":e[12]&&(t.type="whitespace"),t}},function(e,t,n){"use strict";e.exports=n(2)},function(e,t,n){"use strict";var r=n(1),o=function(e,t,n){return r(e,n)(t)};e.exports=o},function(e,t,n){"use strict";var r={__data:Object.create(null),set:function(e,t){this.__data[e]=t},get:function(e){return this.__data[e]},reset:function(){this.__data={}}};e.exports=r},function(e,t,n){"use strict";var r=Object.prototype.toString,o=function(e){return null===e?"Null":r.call(e).slice(8,-1)},i=function s(e,t){var n=void 0,r=o(e);if("Object"===r?n=Object.create(t||{}):"Array"===r&&(n=[].concat(t||[])),n){for(var i in e)e.hasOwnProperty(i)&&(n[i]=s(e[i],n[i]));return n}return e};e.exports=i},function(e,t,n){"use strict";var r=n(0),o=function(e,t){if(r){var o,i=n(23).minify,s=t.htmlMinifierOptions,a=t.rules.map(function(e){return e.test});(o=s.ignoreCustomFragments).push.apply(o,a),e=i(e,s)}return e};e.exports=o},function(e,t,n){"use strict";var r=function(e,t,r,o){var i=n(1);return o=o.$extend({filename:o.resolveFilename(e,o),bail:!0,source:null}),i(o)(t,r)};e.exports=r},function(e,t,n){"use strict";var r=n(0),o=function(e){if(r){return n(3).readFileSync(e,"utf8")}var t=document.getElementById(e);return t.value||t.innerHTML};e.exports=o},function(e,t,n){"use strict";var r=function(e){console.error(e.name,e.message)};e.exports=r},function(e,t,n){"use strict";var r=n(0),o=/^\.+\//,i=function(e,t){if(r){var i=n(3),s=t.root,a=t.extname;if(o.test(e)){var c=t.filename,u=!c||e===c,p=u?s:i.dirname(c);e=i.resolve(p,e)}else e=i.resolve(s,e);i.extname(e)||(e+=a)}return e};e.exports=i},function(e,t,n){"use strict";var r={test:/{{[ \t]*([@#]?)(\/?)([\w\W]*?)[ \t]*}}/,use:function(e,t,n,o){var i=this,s=i.options,a=i.getEsTokens(o.trim()),c=a.map(function(e){return e.value}),u={},p=void 0,l=!!t&&"raw",f=n+c.shift(),h=function(e,t){console.warn("Template upgrade:","{{"+e+"}}","->","{{"+t+"}}","\n",s.filename||"")};switch("#"===t&&h("#value","@value"),f){case"set":o="var "+c.join("");break;case"if":o="if("+c.join("")+"){";break;case"else":var m=c.indexOf("if");m>-1?(c.splice(0,m+1),o="}else if("+c.join("")+"){"):o="}else{";break;case"/if":o="}";break;case"each":p=r._split(a),p.shift(),"as"===p[1]&&(h("each object as value index","each object value index"),p.splice(1,1));var d=p[0]||"$data",v=p[1]||"$value",g=p[2]||"$index";o="$each("+d+",function("+v+","+g+"){";break;case"/each":o="})";break;case"echo":f="print",h("echo value","value");case"print":case"include":case"extend":p=r._split(a),p.shift(),o=f+"("+p.join(",")+")";break;case"block":o="block("+c.join("")+",function(){";break;case"/block":o="})";break;default:if(-1!==c.indexOf("|")){for(var y=f,b=[],x=c.filter(function(e){return!/^\s+$/.test(e)});"|"!==x[0];)y+=x.shift();x.filter(function(e){return":"!==e}).forEach(function(e){"|"===e?b.push([]):b[b.length-1].push(e)}),b.reduce(function(e,t){var n=t.shift();return t.unshift(e),o="$imports."+n+"("+t.join(",")+")"},y)}else s.imports[f]?(h("filterName value","value | filterName"),p=r._split(a),p.shift(),o=f+"("+p.join(",")+")",l="raw"):o=""+f+c.join("");l||(l="escape")}return u.code=o,u.output=l,u},_split:function(e){for(var t=0,n=e.shift(),r=[[n]];t/,use:function(e,t,n,r){return n={"-":"raw","=":"escape","":!1,"==":"raw","=#":"raw"}[n],t&&(r="/*"+e+"*/",n=!1),{code:r,output:n}}};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=n(19),i=n(21),s="$data",a="$imports",c="print",u="include",p="extend",l="block",f="$$out",h="$$line",m="$$blocks",d="$$from",v="$$options",g=function(e,t){return e.hasOwnProperty(t)},y=JSON.stringify,b=function(){function e(t){var n,o,g=this;r(this,e);var y=t.source,b=t.minimize,x=t.htmlMinifier;if(this.options=t,this.stacks=[],this.context=[],this.scripts=[],this.CONTEXT_MAP={},this.ignore=[s,a,v].concat(t.ignore),this.internal=(n={},n[f]="''",n[h]="[0,0]",n[m]="arguments[1]||{}",n[d]="null",n[c]="function(){"+f+"+=''.concat.apply('',arguments)}",n[u]="function(src,data){"+f+"+="+v+".include(src,data||"+s+",arguments[2]||"+m+","+v+")}",n[p]="function(from){"+d+"=from}",n[l]="function(name,callback){if("+d+"){"+f+"='';callback();"+m+"[name]="+f+"}else{if(typeof "+m+"[name]==='string'){"+f+"+="+m+"[name]}else{callback()}}}",n),this.dependencies=(o={},o[c]=[f],o[u]=[f,v,s,m],o[p]=[d,u],o[l]=[d,f,m],o),this.importContext(f),t.compileDebug&&this.importContext(h),b)try{y=x(y,t)}catch(w){}this.source=y,this.getTplTokens(y,t.rules,this).forEach(function(e){e.type===i.TYPE_STRING?g.parseString(e):g.parseExpression(e)})}return e.prototype.getTplTokens=function(){return i.apply(undefined,arguments)},e.prototype.getEsTokens=function(e){return o(e)},e.prototype.getVariables=function(e){var t=!1;return e.filter(function(e){return"whitespace"!==e.type&&"comment"!==e.type}).filter(function(e){return"name"===e.type&&!t||(t="punctuator"===e.type&&"."===e.value,!1)}).map(function(e){return e.value})},e.prototype.importContext=function(e){var t=this,n="",r=this.internal,o=this.dependencies,i=this.ignore,c=this.context,u=this.options,p=u.imports,l=this.CONTEXT_MAP;g(l,e)||-1!==i.indexOf(e)||(g(r,e)?(n=r[e],g(o,e)&&o[e].forEach(function(e){return t.importContext(e)})):n="$escape"===e||"$each"===e||g(p,e)?a+"."+e:s+"."+e,l[e]=n,c.push({name:e,value:n}))},e.prototype.parseString=function(e){var t=e.value;if(t){var n=f+"+="+y(t);this.scripts.push({source:t,tplToken:e,code:n})}},e.prototype.parseExpression=function(e){var t=this,n=e.value,r=e.script,o=r.output,s=r.code;o&&(s=!1===escape||o===i.TYPE_RAW?f+"+="+r.code:f+"+=$escape("+r.code+")");var a=this.getEsTokens(s);this.getVariables(a).forEach(function(e){return t.importContext(e)}),this.scripts.push({source:n,tplToken:e,code:s})},e.prototype.checkExpression=function(e){for(var t=[[/^\s*}[\w\W]*?{?[\s;]*$/,""],[/(^[\w\W]*?\([\w\W]*?(?:=>|\([\w\W]*?\))\s*{[\s;]*$)/,"$1})"],[/(^[\w\W]*?\([\w\W]*?\)\s*{[\s;]*$)/,"$1}"]],n=0;n> ":" ")+n+"| "+e}).join("\n");return(n||"anonymous")+":"+r+":"+o+"\n"+c+"\n\n"+e.message},t}(Error);s.debugTypes={RuntimeError:!0,CompileError:!0},e.exports=s},function(e,t,n){"use strict";var r=n(24),o=n(5)["default"],i=n(5).matchToToken,s=function(e){return e.match(o).map(function(e){return o.lastIndex=0,i(o.exec(e))}).map(function(e){return"name"===e.type&&r(e.value)&&(e.type="keyword"),e})};e.exports=s},function(e,t,n){"use strict";(function(t){/*! art-template@runtime | https://github.com/aui/art-template */ -var r=n(0),o=Object.create(r?t:window),i=function p(e){return"string"!=typeof e&&(e=e===undefined||null===e?"":"function"==typeof e?p(e.call(e)):JSON.stringify(e)),e},s=/["&'<>]/,a=function(e){var t=""+e,n=s.exec(t);if(!n)return e;var r="",o=void 0,i=void 0,a=void 0;for(o=n.index,i=0;o1&&arguments[1]!==undefined?arguments[1]:{};"string"!=typeof e?t=e:t.source=e,t=o.$extend(t),e=t.source,t.debug&&(t.cache=!1,t.bail=!1,t.minimize=!1,t.compileDebug=!0),t.compileDebug&&(t.minimize=!1),t.filename&&(t.filename=t.resolveFilename(t.filename,t));var n=t.filename,a=t.cache,u=t.caches;if(a&&n){var p=u.get(n);if(p)return p}if(!e)try{e=t.loader(n,t),t.source=e}catch(d){var f=new i({name:"CompileError",path:n,message:"template not found: "+d.message,stack:d.stack});if(t.bail)throw f;return s(f,t)}var l=void 0,h=new r(t);try{l=h.build()}catch(f){if(f=new i(f),t.bail)throw f;return s(f,t)}var m=function(e,n){try{return l(e,n)}catch(f){if(!t.compileDebug)return t.cache=!1,t.compileDebug=!0,c(t)(e,n);if(f=new i(f),t.bail)throw f;return s(f,t)()}};return m.mappings=l.mappings,m.sourcesContent=l.sourcesContent,m.toString=function(){return l.toString()},a&&n&&u.set(n,m),m};a.Compiler=r,e.exports=a},function(e,t,n){"use strict";function r(){this.$extend=function(e){return e=e||{},s(e,e instanceof r?e:this)}}var o=n(0),i=n(20),s=n(9),a=n(11),c=n(13),u=n(8),p=n(12),f=n(15),l=n(16),h=n(10),m=n(14),d={source:null,filename:null,rules:[l,f],escape:!0,debug:!!o&&"production"!==process.env.NODE_ENV,bail:!1,cache:!0,minimize:!0,compileDebug:!1,resolveFilename:m,include:a,htmlMinifier:h,htmlMinifierOptions:{collapseWhitespace:!0,minifyCSS:!0,minifyJS:!0,ignoreCustomFragments:[]},onerror:c,loader:p,caches:u,root:"/",extname:".art",ignore:[],imports:i};r.prototype=d,e.exports=new r},function(e,t){},function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(r){"object"==typeof window&&(n=window)}e.exports=n},function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=/((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyu]{1,5}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g,t.matchToToken=function(e){var t={type:"invalid",value:e[0]};return e[1]?(t.type="string",t.closed=!(!e[3]&&!e[4])):e[5]?t.type="comment":e[6]?(t.type="comment",t.closed=!!e[7]):e[8]?t.type="regex":e[9]?t.type="number":e[10]?t.type="name":e[11]?t.type="punctuator":e[12]&&(t.type="whitespace"),t}},function(e,t,n){"use strict";e.exports=n(2)},function(e,t,n){"use strict";var r=n(1),o=function(e,t,n){return r(e,n)(t)};e.exports=o},function(e,t,n){"use strict";var r={__data:Object.create(null),set:function(e,t){this.__data[e]=t},get:function(e){return this.__data[e]},reset:function(){this.__data={}}};e.exports=r},function(e,t,n){"use strict";var r=Object.prototype.toString,o=function(e){return null===e?"Null":r.call(e).slice(8,-1)},i=function s(e,t){var n=void 0,r=o(e);if("Object"===r?n=Object.create(t||{}):"Array"===r&&(n=[].concat(t||[])),n){for(var i in e)e.hasOwnProperty(i)&&(n[i]=s(e[i],n[i]));return n}return e};e.exports=i},function(e,t,n){"use strict";var r=n(0),o=function(e,t){if(r){var o,i=n(23).minify,s=t.htmlMinifierOptions,a=t.rules.map(function(e){return e.test});(o=s.ignoreCustomFragments).push.apply(o,a),e=i(e,s)}return e};e.exports=o},function(e,t,n){"use strict";var r=function(e,t,r,o){var i=n(1);return o=o.$extend({filename:o.resolveFilename(e,o),bail:!0,source:null}),i(o)(t,r)};e.exports=r},function(e,t,n){"use strict";var r=n(0),o=function(e){if(r){return n(3).readFileSync(e,"utf8")}var t=document.getElementById(e);return t.value||t.innerHTML};e.exports=o},function(e,t,n){"use strict";var r=function(e){console.error(e.name,e.message)};e.exports=r},function(e,t,n){"use strict";var r=n(0),o=/^\.+\//,i=function(e,t){if(r){var i=n(3),s=t.root,a=t.extname;if(o.test(e)){var c=t.filename,u=!c||e===c,p=u?s:i.dirname(c);e=i.resolve(p,e)}else e=i.resolve(s,e);i.extname(e)||(e+=a)}return e};e.exports=i},function(e,t,n){"use strict";var r={test:/{{[ \t]*([@#]?)(\/?)([\w\W]*?)[ \t]*}}/,use:function(e,t,n,o){var i=this,s=i.options,a=i.getEsTokens(o.trim()),c=a.map(function(e){return e.value}),u={},p=void 0,f=!!t&&"raw",l=n+c.shift(),h=function(t,n){console.warn((s.filename||"anonymous")+":"+(e.line+1)+":"+(e.start+1)+"\nTemplate upgrade: {{"+t+"}} -> {{"+n+"}}")};switch("#"===t&&h("#value","@value"),l){case"set":o="var "+c.join("");break;case"if":o="if("+c.join("")+"){";break;case"else":var m=c.indexOf("if");m>-1?(c.splice(0,m+1),o="}else if("+c.join("")+"){"):o="}else{";break;case"/if":o="}";break;case"each":p=r._split(a),p.shift(),"as"===p[1]&&(h("each object as value index","each object value index"),p.splice(1,1));var d=p[0]||"$data",v=p[1]||"$value",g=p[2]||"$index";o="$each("+d+",function("+v+","+g+"){";break;case"/each":o="})";break;case"echo":l="print",h("echo value","value");case"print":case"include":case"extend":p=r._split(a),p.shift(),o=l+"("+p.join(",")+")";break;case"block":o="block("+c.join("")+",function(){";break;case"/block":o="})";break;default:if(-1!==c.indexOf("|")){for(var y=l,b=[],x=c.filter(function(e){return!/^\s+$/.test(e)});"|"!==x[0];)y+=x.shift();x.filter(function(e){return":"!==e}).forEach(function(e){"|"===e?b.push([]):b[b.length-1].push(e)}),b.reduce(function(e,t){var n=t.shift();return t.unshift(e),o="$imports."+n+"("+t.join(",")+")"},y)}else o=""+l+c.join("");f||(f="escape")}return u.code=o,u.output=f,u},_split:function(e){for(var t=0,n=e.shift(),r=[[n]];t/,use:function(e,t,n,r){return n={"-":"raw","=":"escape","":!1,"==":"raw","=#":"raw"}[n],t&&(r="/*"+e+"*/",n=!1),{code:r,output:n}}};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=n(19),i=n(21),s="$data",a="$imports",c="print",u="include",p="extend",f="block",l="$$out",h="$$line",m="$$blocks",d="$$slice",v="$$from",g="$$options",y=function(e,t){return e.hasOwnProperty(t)},b=JSON.stringify,x=function(){function e(t){var n,o,y=this;r(this,e);var b=t.source,x=t.minimize,w=t.htmlMinifier;if(this.options=t,this.stacks=[],this.context=[],this.scripts=[],this.CONTEXT_MAP={},this.ignore=[s,a,g].concat(t.ignore),this.internal=(n={},n[l]="''",n[h]="[0,0]",n[m]="arguments[1]||{}",n[v]="null",n[c]="function(){var s=''.concat.apply('',arguments);"+l+"+=s;return s}",n[u]="function(src,data){var s="+g+".include(src,data||"+s+",arguments[2]||"+m+","+g+");"+l+"+=s;return s}",n[p]="function(from){"+v+"=from}",n[d]="function(c,p,s){p="+l+";"+l+"='';c();s="+l+";"+l+"=p+s;return s}",n[f]="function(){var a=arguments,s;if(typeof a[0]==='function'){return "+d+"(a[0])}else if("+v+"){"+m+"[a[0]]="+d+"(a[1])}else{s="+m+"[a[0]];if(typeof s==='string'){"+l+"+=s}else{s="+d+"(a[1])}return s}}",n),this.dependencies=(o={},o[c]=[l],o[u]=[l,g,s,m],o[p]=[v,u],o[f]=[d,v,l,m],o),this.importContext(l),t.compileDebug&&this.importContext(h),x)try{b=w(b,t)}catch(E){}this.source=b,this.getTplTokens(b,t.rules,this).forEach(function(e){e.type===i.TYPE_STRING?y.parseString(e):y.parseExpression(e)})}return e.prototype.getTplTokens=function(){return i.apply(undefined,arguments)},e.prototype.getEsTokens=function(e){return o(e)},e.prototype.getVariables=function(e){var t=!1;return e.filter(function(e){return"whitespace"!==e.type&&"comment"!==e.type}).filter(function(e){return"name"===e.type&&!t||(t="punctuator"===e.type&&"."===e.value,!1)}).map(function(e){return e.value})},e.prototype.importContext=function(e){var t=this,n="",r=this.internal,o=this.dependencies,i=this.ignore,c=this.context,u=this.options,p=u.imports,f=this.CONTEXT_MAP;y(f,e)||-1!==i.indexOf(e)||(y(r,e)?(n=r[e],y(o,e)&&o[e].forEach(function(e){return t.importContext(e)})):n="$escape"===e||"$each"===e||y(p,e)?a+"."+e:s+"."+e,f[e]=n,c.push({name:e,value:n}))},e.prototype.parseString=function(e){var t=e.value;if(t){var n=l+"+="+b(t);this.scripts.push({source:t,tplToken:e,code:n})}},e.prototype.parseExpression=function(e){var t=this,n=e.value,r=e.script,o=r.output,s=r.code;o&&(s=!1===escape||o===i.TYPE_RAW?l+"+="+r.code:l+"+=$escape("+r.code+")");var a=this.getEsTokens(s);this.getVariables(a).forEach(function(e){return t.importContext(e)}),this.scripts.push({source:n,tplToken:e,code:s})},e.prototype.checkExpression=function(e){for(var t=[[/^\s*}[\w\W]*?{?[\s;]*$/,""],[/(^[\w\W]*?\([\w\W]*?(?:=>|\([\w\W]*?\))\s*{[\s;]*$)/,"$1})"],[/(^[\w\W]*?\([\w\W]*?\)\s*{[\s;]*$)/,"$1}"]],n=0;n> ":" ")+n+"| "+e}).join("\n");return(r||"anonymous")+":"+o+":"+i+"\n"+p+"\n\n"+t+": "+s}var a=function(e){function t(n){r(this,t);var i=o(this,e.call(this,n.message));return i.name="TemplateError",i.message=s(n),Error.captureStackTrace&&Error.captureStackTrace(i,i.constructor),i}return i(t,e),t}(Error);e.exports=a},function(e,t,n){"use strict";var r=n(24),o=n(5)["default"],i=n(5).matchToToken,s=function(e){return e.match(o).map(function(e){return o.lastIndex=0,i(o.exec(e))}).map(function(e){return"name"===e.type&&r(e.value)&&(e.type="keyword"),e})};e.exports=s},function(e,t,n){"use strict";(function(t){function r(e){return"string"!=typeof e&&(e=e===undefined||null===e?"":"function"==typeof e?r(e.call(e)):JSON.stringify(e)),e}function o(e){var t=""+e,n=a.exec(t);if(!n)return e;var r="",o=void 0,i=void 0,s=void 0;for(o=n.index,i=0;o]/;s.$escape=function(e){return o(r(e))},s.$each=function(e,t){if(Array.isArray(e))for(var n=0,r=e.length;n2&&arguments[2]!==undefined?arguments[2]:{},o=[{type:"string",value:e,line:0,start:0,end:e.length}],i=0;i { - console.warn('Template upgrade:', - `{{${oldSyntax}}}`, `->`, `{{${newSyntax}}}`, - `\n`, options.filename || ''); + console.warn(`${options.filename || 'anonymous'}:${match.line + 1}:${match.start + 1}\n` + + `Template upgrade: {{${oldSyntax}}} -> {{${newSyntax}}}`); }; @@ -147,17 +146,6 @@ const artRule = { }, target); - } else if (options.imports[key]) { - - // ... v3 compat ... - warn('filterName value', 'value | filterName'); - - group = artRule._split(esTokens); - group.shift(); - - code = `${key}(${group.join(',')})`; - output = 'raw'; - } else { code = `${key}${values.join('')}`; } diff --git a/src/compile/adapter/rule.native.js b/src/compile/adapter/rule.native.js index dc56754c..728ab02b 100644 --- a/src/compile/adapter/rule.native.js +++ b/src/compile/adapter/rule.native.js @@ -3,7 +3,7 @@ */ const nativeRule = { test: /<%(#?)((?:==|=#|[=-])?)([\w\W]*?)(-?)%>/, - use: (match, comment, output, code) => { + use: (match, comment, output, code/*, trimMode*/) => { output = ({ '-': 'raw', @@ -21,7 +21,7 @@ const nativeRule = { } // ejs compat: trims following newline - // if (trtimMode) {} + // if (trimMode) {} return { code, diff --git a/src/compile/compiler.js b/src/compile/compiler.js index 06183aa2..cbef2942 100644 --- a/src/compile/compiler.js +++ b/src/compile/compiler.js @@ -35,6 +35,9 @@ const LINE = `$$line`; /** 所有“模板块”变量 */ const BLOCKS = `$$blocks`; +/** 截取模版输出“流”的函数 */ +const SLICE = `$$slice`; + /** 继承的布局模板的文件地址变量 */ const FROM = `$$from`; @@ -83,10 +86,11 @@ class Compiler { [LINE]: `[0,0]`, [BLOCKS]: `arguments[1]||{}`, [FROM]: `null`, - [PRINT]: `function(){${OUT}+=''.concat.apply('',arguments)}`, - [INCLUDE]: `function(src,data){${OUT}+=${OPTIONS}.include(src,data||${DATA},arguments[2]||${BLOCKS},${OPTIONS})}`, + [PRINT]: `function(){var s=''.concat.apply('',arguments);${OUT}+=s;return s}`, + [INCLUDE]: `function(src,data){var s=${OPTIONS}.include(src,data||${DATA},arguments[2]||${BLOCKS},${OPTIONS});${OUT}+=s;return s}`, [EXTEND]: `function(from){${FROM}=from}`, - [BLOCK]: `function(name,callback){if(${FROM}){${OUT}='';callback();${BLOCKS}[name]=${OUT}}else{if(typeof ${BLOCKS}[name]==='string'){${OUT}+=${BLOCKS}[name]}else{callback()}}}` + [SLICE]: `function(c,p,s){p=${OUT};${OUT}='';c();s=${OUT};${OUT}=p+s;return s}`, + [BLOCK]: `function(){var a=arguments,s;if(typeof a[0]==='function'){return ${SLICE}(a[0])}else if(${FROM}){${BLOCKS}[a[0]]=${SLICE}(a[1])}else{s=${BLOCKS}[a[0]];if(typeof s==='string'){${OUT}+=s}else{s=${SLICE}(a[1])}return s}}` }; // 内置函数依赖关系声明 @@ -94,7 +98,7 @@ class Compiler { [PRINT]: [OUT], [INCLUDE]: [OUT, OPTIONS, DATA, BLOCKS], [EXTEND]: [FROM, /*[*/ INCLUDE /*]*/ ], - [BLOCK]: [FROM, OUT, BLOCKS] + [BLOCK]: [SLICE, FROM, OUT, BLOCKS] }; @@ -471,6 +475,7 @@ Compiler.CONSTS = { OUT, LINE, BLOCKS, + SLICE, FROM, ESCAPE, EACH diff --git a/src/compile/error.js b/src/compile/error.js index eaa565a9..c53f1519 100644 --- a/src/compile/error.js +++ b/src/compile/error.js @@ -1,56 +1,47 @@ /** * 模板错误处理类 + * @param {Object} options */ class TemplateError extends Error { - - constructor(error) { - super(error); - let message = error.message; - - if (TemplateError.debugTypes[error.name]) { - - if (error.source) { - message = TemplateError.debug(error); - } - - this.path = error.path; - } - + constructor(options) { + super(options.message); this.name = 'TemplateError'; - this.message = message; - } - - static debug(error) { - const { - source, - path, - line, - column - } = error; - - const lines = source.split(/\n/); - const start = Math.max(line - 3, 0) - const end = Math.min(lines.length, line + 3); - - // Error context - const context = lines.slice(start, end).map((code, index) => { - const number = index + start + 1; - const left = number === line ? ' >> ' : ' '; - return `${left}${number}| ${code}`; - }).join('\n'); - - // Alter exception message - return `${path || 'anonymous'}:${line}:${column}\n` + - `${context}\n\n` + - `${error.message}` + this.message = formatMessage(options); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } } }; +function formatMessage({ + name, + source, + path, + line, + column, + message +}) { + + if (!source) { + return message; + } -TemplateError.debugTypes = { - 'RuntimeError': true, - 'CompileError': true -}; + const lines = source.split(/\n/); + const start = Math.max(line - 3, 0) + const end = Math.min(lines.length, line + 3); + + // Error context + const context = lines.slice(start, end).map((code, index) => { + const number = index + start + 1; + const left = number === line ? ' >> ' : ' '; + return `${left}${number}| ${code}`; + }).join('\n'); + + // Alter exception message + return `${path || 'anonymous'}:${line}:${column}\n` + + `${context}\n\n` + + `${name}: ${message}` +} module.exports = TemplateError; \ No newline at end of file diff --git a/src/compile/runtime.js b/src/compile/runtime.js index d520d94f..9508b587 100644 --- a/src/compile/runtime.js +++ b/src/compile/runtime.js @@ -2,10 +2,40 @@ const detectNode = require('detect-node'); const runtime = Object.create(detectNode ? global : window); +const ESCAPE_REG = /["&'<>]/; + + + +/** + * 编码模板输出的内容 + * @param {any} content + * @return {string} + */ +runtime.$escape = content => xmlEscape(toString(content)); + + + +/** + * 迭代器,支持数组与对象 + * @param {array|Object} data + * @param {function} callback + */ +runtime.$each = (data, callback) => { + if (Array.isArray(data)) { + for (let i = 0, len = data.length; i < len; i++) { + callback(data[i], i); + } + } else { + for (let i in data) { + callback(data[i], i); + } + } +}; + // 将目标转成字符 -const toString = value => { +function toString(value) { if (typeof value !== 'string') { if (value === undefined || value === null) { value = ''; @@ -21,8 +51,7 @@ const toString = value => { // 编码 HTML 内容 -const ESCAPE_REG = /["&'<>]/; -const xmlEscape = content => { +function xmlEscape(content) { const html = '' + content; const regexResult = ESCAPE_REG.exec(html); if (!regexResult) { @@ -69,32 +98,4 @@ const xmlEscape = content => { }; -/** - * 编码模板输出的内容 - * @param {any} content - * @return {string} - */ -const escape = content => xmlEscape(toString(content)); - - -/** - * 迭代器,支持数组与对象 - * @param {array|Object} data - * @param {function} callback - */ -const each = (data, callback) => { - if (Array.isArray(data)) { - for (let i = 0, len = data.length; i < len; i++) { - callback(data[i], i, data); - } - } else { - for (let i in data) { - callback(data[i], i); - } - } -}; - -runtime.$each = each; -runtime.$escape = escape; - module.exports = runtime; \ No newline at end of file diff --git a/src/compile/tpl-tokenizer.js b/src/compile/tpl-tokenizer.js index 2f3ee222..f6a0699d 100644 --- a/src/compile/tpl-tokenizer.js +++ b/src/compile/tpl-tokenizer.js @@ -3,6 +3,16 @@ const TYPE_EXPRESSION = 'expression'; const TYPE_RAW = 'raw'; const TYPE_ESCAPE = 'escape'; +function Match(content, line, start, end) { + this.content = content; + this.line = line; + this.start = start; + this.end = end; +}; + +Match.prototype.toString = function(){ + return this.content; +}; /** @@ -12,7 +22,7 @@ const TYPE_ESCAPE = 'escape'; * @param {Object} context * @return {Object[]} */ -const tplTokenizer = (source, rules, context) => { +const tplTokenizer = (source, rules, context = {}) => { const tokens = [{ type: TYPE_STRING, @@ -82,6 +92,7 @@ const tplTokenizer = (source, rules, context) => { } else { + values[0] = new Match(values[0], line, start, end); const script = rule.use.apply(context, values); token.script = script; substitute.push(token); diff --git a/test/compile/index.js b/test/compile/index.js index 76d8d759..4b59caa1 100644 --- a/test/compile/index.js +++ b/test/compile/index.js @@ -61,6 +61,19 @@ module.exports = { // todo empty }, + 'block': () => { + + render = compile('<% block(function(){ %>hello <%= value %>.<% }) %>', { + bail: true + }); + data = { + value: 'aui' + }; + result = render(data); + assert.deepEqual('hello aui.', result); + + }, + 'syntax compat: art-template@v3': () => { @@ -241,9 +254,6 @@ module.exports = { test(`{{time | dateFormat:'yyyy-MM-dd'}}`, { time: 1491566794863 }, `2017-04-07`, options); // ... v3 compat ... - test(`{{brackets value}}`, { - value: '糖饼' - }, '『糖饼』', options); // ... v3 compat ... },