js模板引擎3--编译模板

编译模板的时候,我们可以使用Function构造函数构建出可执行的js代码,但关键点是如何把模板数据和构建的js代码关联起来。

比如前面的模板字符串例子:

<script id="test" type="text/template">
    <% for(var i = 0; i < list.length; i++) { %>
        <li><%=list[i].title %></li>
    <% } %>
</script>

这个例子中,我们关心的是变量list的值如何确定。

有两种思路可以实现,先介绍第一种,比较复杂的做法。

解析js语句

用到的工具

要确定list的值,首先要从模板语句中识别出list

模板语句中我们直接是使用了js的语法,要识别出js语句中的变量,就要对js语句进行解析。

简单起见,这里使用js-tokens包(v3.0.2)解析js语句。

js-tokens使用正则表达式解析js语句,全部代码只有20多行,牛!当然也可以使用babel等解析。

js-tokens的代码稍微改造一下:

var jsTokens = {
    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,
    matchToToken: function(match) {
        var token = {type: "invalid", value: match[0]}
        if (match[ 1]) token.type = "string" , token.closed = !!(match[3] || match[4])
        else if (match[ 5]) token.type = "comment"
        else if (match[ 6]) token.type = "comment", token.closed = !!match[7]
        else if (match[ 8]) token.type = "regex"
        else if (match[ 9]) token.type = "number"
        else if (match[10]) token.type = "name"
        else if (match[11]) token.type = "punctuator"
        else if (match[12]) token.type = "whitespace"
        return token
    }
};

测试一下上面的代码:

var test = 'for(var i = 0; i < list.length; i++) { console.log(list[i]); }';
var res = test.match(jsTokens.default).map(function(item, index) {
    jsTokens.default.lastIndex = 0;
    return jsTokens.matchToToken(jsTokens.default.exec(item));
});
console.log(res);

输出结果:

[
  { type: 'name', value: 'for' },
  { type: 'punctuator', value: '(' },
  { type: 'name', value: 'var' },
  { type: 'whitespace', value: ' ' },
  { type: 'name', value: 'i' },
  { type: 'whitespace', value: ' ' },
  { type: 'punctuator', value: '=' },
  { type: 'whitespace', value: ' ' },
  { type: 'number', value: '0' },
  { type: 'punctuator', value: ';' },
  { type: 'whitespace', value: ' ' },
  { type: 'name', value: 'i' },
  { type: 'whitespace', value: ' ' },
  { type: 'punctuator', value: '<' },
  { type: 'whitespace', value: ' ' },
  { type: 'name', value: 'list' },
  { type: 'punctuator', value: '.' },
  { type: 'name', value: 'length' },
  { type: 'punctuator', value: ';' },
  { type: 'whitespace', value: ' ' },
  { type: 'name', value: 'i' },
  { type: 'punctuator', value: '++' },
  { type: 'punctuator', value: ')' },
  { type: 'whitespace', value: ' ' },
  { type: 'punctuator', value: '{' },
  { type: 'whitespace', value: ' ' },
  { type: 'name', value: 'console' },
  { type: 'punctuator', value: '.' },
  { type: 'name', value: 'log' },
  { type: 'punctuator', value: '(' },
  { type: 'name', value: 'list' },
  { type: 'punctuator', value: '[' },
  { type: 'name', value: 'i' },
  { type: 'punctuator', value: ']' },
  { type: 'punctuator', value: ')' },
  { type: 'punctuator', value: ';' },
  { type: 'whitespace', value: ' ' },
  { type: 'punctuator', value: '}' }
]

从上面的结果来看,变量名和关键字的type属性均是name还是没法获取到变量名,那么就要用到is-keyword-js包来区分。

is-keyword-js 是将mdn网站上的js的关键字作为对象的key,使用 hasOwnProperty 判断是否是关键字,具体可以看源码,很简单。

下面是稍加改造的is-keyword-js包的源码:

var isJsKeywords = {
    reservedKeywords: {
        'abstract': true,
        'await': true,
        'boolean': true,
        'break': true,
        'byte': true,
        'case': true,
        'catch': true,
        'char': true,
        'class': true,
        'const': true,
        'continue': true,
        'debugger': true,
        'default': true,
        'delete': true,
        'do': true,
        'double': true,
        'else': true,
        'enum': true,
        'export': true,
        'extends': true,
        'false': true,
        'final': true,
        'finally': true,
        'float': true,
        'for': true,
        'function': true,
        'goto': true,
        'if': true,
        'implements': true,
        'import': true,
        'in': true,
        'instanceof': true,
        'int': true,
        'interface': true,
        'let': true,
        'long': true,
        'native': true,
        'new': true,
        'null': true,
        'package': true,
        'private': true,
        'protected': true,
        'public': true,
        'return': true,
        'short': true,
        'static': true,
        'super': true,
        'switch': true,
        'synchronized': true,
        'this': true,
        'throw': true,
        'transient': true,
        'true': true,
        'try': true,
        'typeof': true,
        'var': true,
        'void': true,
        'volatile': true,
        'while': true,
        'with': true,
        'yield': true
    },
    test: function(str) {
        return this.reservedKeywords.hasOwnProperty(str);
    }
};

介绍完工具,下面开始完善代码。

解析流程

接着上一篇中模板的解析结果开始。

模板的解析结果有两中类型: stringexpress

最终模板填充完数据之后本质是一个字符串,对于string类型无需过多处理,只要拼接起来就行;express类型本质是完成数据填充,然后将填充完数据的字符串拼接,所以需要一个变量保存拼接字符串的结果:

// 预设变量,保存模板最终的处理结果
var OUT_CODE = '$RES_OUT';

function getScriptTokens(tplTokens) {
    var scripts = []; // 保存js代码语句,最终用于构造渲染函数
    for(var i = 0; i < tplTokens.length; i++) {
        var source = tplTokens[i].value.replace(/\n/g,'');
        var code = '';
        if(tplTokens[i].type === 'string') {
            code = OUT_CODE + ' += "' + source + '"';
        } else if(tplTokens[i].type === 'express') {
            if(tplTokens[i].script.output) { // 直接输出变量值
                code = OUT_CODE + ' += ' + tplTokens[i].script.code;
            } else {
                // js逻辑语句
                code = tplTokens[i].script.code;
            }
            // 解析js语句 -- 提取变量,确定变量的值
            getVariableContext(code);
        }

        scripts.push({
            source: source,
            tplToken: tplTokens[i],
            code: code
        });
    }

    return scripts;
}

暂停一下,上面代码中用到了常量stringexpress和上一篇中tplToToken函数中的一样,提取出这两个常量:

var TPL_TOKEN_TYPE_STRING = 'string';
var TPL_TOKEN_TYPE_EXPRESSION = 'express';

接着实现getVariableContext函数的逻辑。

首先解析js语句,标记出关键字

function getVariableContext(code) {
    var jsExpressTokens = code.match(jsTokens.default).map(function(item) {
        jsTokens.default.lastIndex = 0;
        return jsTokens.matchToToken(jsTokens.default.exec(item));
    }).map(function(item) {
        if(item.type === 'name' && isJsKeywords.test(item.value)) {
            item.type = 'keywords';
        }
        return item;
    });
}

然后提取出变量名,变量名的typename。注意,还要跳过对象的属性名:

function getVariableContext(code) {
    var jsExpressTokens = code.match(jsTokens.default).map(function(item) {
        jsTokens.default.lastIndex = 0;
        return jsTokens.matchToToken(jsTokens.default.exec(item));
    }).map(function(item) {
        if(item.type === 'name' && isJsKeywords.test(item.value)) {
            item.type = 'keywords';
        }
        return item;
    });

    var ignore = false; // 跳过对象的属性
    var variableTokens = jsExpressTokens.filter(function(item) {
        if(item.type === 'name' && !ignore) {
            return true;
        }
        // 如果ignore是true,说明下一个是对象的属性
        ignore = item.type === 'punctuator' && item.value === '.';
        return false;
    });

    console.log(variableTokens);
}

用一个例子测试一下:

var tplStr = `
 <% for (var i = 0; i < list.length; i ++) { %>
    <p>索引 <%= i + 1 %> :<%= list[i] %></p>
<% } %>
<% var a = list.name; console.log(a); %>
`;
var tplTokens = tplToToken(tplStr,reg);
getScriptTokens(tplTokens);

输出结果:

[
  { type: 'name', value: 'i' },
  { type: 'name', value: 'i' },
  { type: 'name', value: 'list' },
  { type: 'name', value: 'i' }
]
[ 
  { type: 'name', value: '$RES_OUT' }, 
  { type: 'name', value: 'i' } ]
[
  { type: 'name', value: '$RES_OUT' },
  { type: 'name', value: 'list' },
  { type: 'name', value: 'i' }
]
[]
[
  { type: 'name', value: 'a' },
  { type: 'name', value: 'list' },
  { type: 'name', value: 'console' },
  { type: 'name', value: 'a' }
]

观察上面的测试结果,注意到几个问题:

  1. 同一个变量会多次提取;
  2. 提取到的变量包含了预设变量$RES_OUT,顶级变量下的console,模板变量list,模板内声明的变量ia,这些变量的值要怎么确定;

在变量赋值逻辑中会解决上述两个问题。

确定好变量的值后,需要保存变量名和对应的值,以便在构造渲染函数时使用,所以声明一个变量variableContext保存。

对于变量名重复问题,利用对象的键的唯一性解决,需要另外声明一个变量variableContextMap

对于顶级变量下的变量名,需要通过顶级变量globalThis赋值,注入到渲染函数中的顶级变量保存在$GLOBAL_DATA中。

对于注入到模板中的数据保存在$DATA中,模板变量做为$DATA的一个属性赋值。

预设变量$RES_OUT的初始值为空字符''

确定提取出变量的值:

var DATA = '$DATA'; // 模板数据变量名
var GLOBAL_DATA = '$GLOBAL_DATA'; // 全局变量名

var globalThis = typeof self !== 'undefined' ? self
    : typeof window !== 'undefined' ? window
    : typeof global !== 'undefined' ? global : {};
var variableContext = [];
var variableContextMap = {};
function getVariableContext(code) {
    var jsExpressTokens = code.match(jsTokens.default).map(function(item) {
        jsTokens.default.lastIndex = 0;
        return jsTokens.matchToToken(jsTokens.default.exec(item));
    }).map(function(item) {
        if(item.type === 'name' && isJsKeywords.test(item.value)) {
            item.type = 'keywords';
        }
        return item;
    });

    var ignore = false; // 跳过对象的属性
    var variableTokens = jsExpressTokens.filter(function(item) {
        if(item.type === 'name' && !ignore) {
            return true;
        }
        // 如果ignore是true,说明下一个是对象的属性
        ignore = item.type === 'punctuator' && item.value === '.';
        return false;
    });
    variableTokens.forEach(function(item) {
        var val;
        if(!Object.prototype.hasOwnProperty.call(variableContextMap, item.value)) {
            if(item.value === OUT_CODE) { // 预设变量
                val = '""';
            } else if(Object.prototype.hasOwnProperty.call(globalThis, item.value)) {
                val = GLOBAL_DATA + '.' + item.value;
            } else { // 模板变量
                // 这里有个问题:模板内声明的变量也会被作为$DATA的一个属性,先记下这个问题。
                val = DATA + '.' + item.value;
            }
            variableContextMap[item.value] = val;
            variableContext.push({
                name: item.value,
                value: val
            });
        }
   
    });
}

完整的代码附在最后,下面测试一下:

var tplStr = `
<% if (isAdmin) { %>
    <!--fhj-->
    <h1><%=title%></h1>
    <ul>
        <% for (var i = 0; i < list.length; i ++) { %>
            <li>索引 <%= i + 1 %> :<%= list[i] %></li>
        <% } %>
    </ul>
    <% var a = list.name; console.log(a); %>
<% } %>
`;
var tplTokens = tplToToken(tplStr,reg);
var scripts = getScriptTokens(tplTokens);
console.log(variableContext);
console.log(scripts);

提取的变量及其值:

[
  { name: 'isAdmin', value: '$DATA.isAdmin' },
  { name: '$RES_OUT', value: '' },
  { name: 'title', value: '$DATA.title' },
  { name: 'i', value: '$DATA.i' },
  { name: 'list', value: '$DATA.list' },
  { name: 'a', value: '$DATA.a' },
  { name: 'console', value: '$GLOBAL_DATA.console' }
]

js代码片段列表:

[
  {
    source: '\n',
    tplToken: Token {
      type: 'string',
      value: '\n',
      script: null,
      start: 0,
      line: 0,
      end: 1
    },
    code: '$RES_OUT += "\n"'
  },
  {
    source: '<% if (isAdmin) { %>',
    tplToken: Token {
      type: 'expression',
      value: '<% if (isAdmin) { %>',
      script: [Object],
      line: 1,
      start: 1,
      end: 21
    },
    code: 'if (isAdmin) {'
  },
  {
    source: '\n    <!--fhj-->\n    <h1>',
    tplToken: Token {
      type: 'string',
      value: '\n    <!--fhj-->\n    <h1>',
      script: null,
      line: 1,
      start: 21,
      end: 45
    },
    code: '$RES_OUT += "\n    <!--fhj-->\n    <h1>"'
  },
  {
    source: '<%=title%>',
    tplToken: Token {
      type: 'expression',
      value: '<%=title%>',
      script: [Object],
      line: 3,
      start: 45,
      end: 55
    },
    code: '$RES_OUT += title'
  },
  {
    source: '</h1>\n    <ul>\n        ',
    tplToken: Token {
      type: 'string',
      value: '</h1>\n    <ul>\n        ',
      script: null,
      line: 3,
      start: 55,
      end: 78
    },
    code: '$RES_OUT += "</h1>\n    <ul>\n        "'
  },
  {
    source: '<% for (var i = 0; i < list.length; i ++) { %>',
    tplToken: Token {
      type: 'expression',
      value: '<% for (var i = 0; i < list.length; i ++) { %>',
      script: [Object],
      line: 5,
      start: 78,
      end: 124
    },
    code: 'for (var i = 0; i < list.length; i ++) {'
  },
  {
    source: '\n            <li>索引 ',
    tplToken: Token {
      type: 'string',
      value: '\n            <li>索引 ',
      script: null,
      line: 5,
      start: 124,
      end: 144
    },
    code: '$RES_OUT += "\n            <li>索引 "'
  },
  {
    source: '<%= i + 1 %>',
    tplToken: Token {
      type: 'expression',
      value: '<%= i + 1 %>',
      script: [Object],
      line: 6,
      start: 144,
      end: 156
    },
    code: '$RES_OUT += i + 1'
  },
  {
    source: ' :',
    tplToken: Token {
      type: 'string',
      value: ' :',
      script: null,
      line: 6,
      start: 156,
      end: 158
    },
    code: '$RES_OUT += " :"'
  },
  {
    source: '<%= list[i] %>',
    tplToken: Token {
      type: 'expression',
      value: '<%= list[i] %>',
      script: [Object],
      line: 6,
      start: 158,
      end: 172
    },
    code: '$RES_OUT += list[i]'
  },
  {
    source: '</li>\n        ',
    tplToken: Token {
      type: 'string',
      value: '</li>\n        ',
      script: null,
      line: 6,
      start: 172,
      end: 186
    },
    code: '$RES_OUT += "</li>\n        "'
  },
  {
    source: '<% } %>',
    tplToken: Token {
      type: 'expression',
      value: '<% } %>',
      script: [Object],
      line: 7,
      start: 186,
      end: 193
    },
    code: '}'
  },
  {
    source: '\n    </ul>\n    ',
    tplToken: Token {
      type: 'string',
      value: '\n    </ul>\n    ',
      script: null,
      line: 7,
      start: 193,
      end: 208
    },
    code: '$RES_OUT += "\n    </ul>\n    "'
  },
  {
    source: '<% var a = list.name; console.log(a); %>',
    tplToken: Token {
      type: 'expression',
      value: '<% var a = list.name; console.log(a); %>',
      script: [Object],
      line: 9,
      start: 208,
      end: 248
    },
    code: 'var a = list.name; console.log(a);'
  },
  {
    source: '\n',
    tplToken: Token {
      type: 'string',
      value: '\n',
      script: null,
      line: 9,
      start: 248,
      end: 249
    },
    code: '$RES_OUT += "\n"'
  },
  {
    source: '<% } %>',
    tplToken: Token {
      type: 'expression',
      value: '<% } %>',
      script: [Object],
      line: 10,
      start: 249,
      end: 256
    },
    code: '}'
  }
]

下一篇将根据上述结果构造渲染函数。

附本篇完整代码:

var TPL_TOKEN_TYPE_STRING = 'string';
var TPL_TOKEN_TYPE_EXPRESSION = 'expression';

// 预置变量
var OUT_CODE = '$RES_OUT';
var DATA = '$DATA'; // 模板数据变量名
var GLOBAL_DATA = '$GLOBAL_DATA'; // 全局变量名

const globalThis = typeof self !== 'undefined' ? self
    : typeof window !== 'undefined' ? window
    : typeof global !== 'undefined' ? global : {};

var jsTokens = {
    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,
    matchToToken: function(match) {
        var token = {type: "invalid", value: match[0]}
        if (match[ 1]) token.type = "string" , token.closed = !!(match[3] || match[4])
        else if (match[ 5]) token.type = "comment"
        else if (match[ 6]) token.type = "comment", token.closed = !!match[7]
        else if (match[ 8]) token.type = "regex"
        else if (match[ 9]) token.type = "number"
        else if (match[10]) token.type = "name"
        else if (match[11]) token.type = "punctuator"
        else if (match[12]) token.type = "whitespace"
        return token
    }
};
var isJsKeywords = {
    reservedKeywords: {
        'abstract': true,
        'await': true,
        'boolean': true,
        'break': true,
        'byte': true,
        'case': true,
        'catch': true,
        'char': true,
        'class': true,
        'const': true,
        'continue': true,
        'debugger': true,
        'default': true,
        'delete': true,
        'do': true,
        'double': true,
        'else': true,
        'enum': true,
        'export': true,
        'extends': true,
        'false': true,
        'final': true,
        'finally': true,
        'float': true,
        'for': true,
        'function': true,
        'goto': true,
        'if': true,
        'implements': true,
        'import': true,
        'in': true,
        'instanceof': true,
        'int': true,
        'interface': true,
        'let': true,
        'long': true,
        'native': true,
        'new': true,
        'null': true,
        'package': true,
        'private': true,
        'protected': true,
        'public': true,
        'return': true,
        'short': true,
        'static': true,
        'super': true,
        'switch': true,
        'synchronized': true,
        'this': true,
        'throw': true,
        'transient': true,
        'true': true,
        'try': true,
        'typeof': true,
        'var': true,
        'void': true,
        'volatile': true,
        'while': true,
        'with': true,
        'yield': true
    },
    test: function(str) {
        return this.reservedKeywords.hasOwnProperty(str);
    }
};


var reg = {
    test: /<%(#?)(=)?[ \t]*([\w\W]*?)[ \t]*%>/g,
    // 对识别出的模板语法语句做进一步处理变量
    use: function (match, comment, output, code) {
        if(output === '=') { // 变量 / 表达式
            output = true;
        } else {
            output = false;
        }
        if(comment) { // 注释
            code = '/*' + code + '*/';
            output = false;
        }
    
        return {
            code: code,
            output: output
        }
    }
};

var variableContext = [];
var variableContextMap = {}; // 确保上下文中变量名的唯一性

function Token(type, value, preToken) {
    this.type = type;
    this.value = value;
    this.script = null;
    if(preToken) {
        this.line = preToken.value.split('\n').length - 1 + preToken.line;
        this.start = preToken.end;
    } else {
        this.start = 0;
        this.line = 0;
    }
    this.end = this.start + value.length; // 包含起点start,不包含终点end
}

function tplToToken(str,reg) {
    var tokens = [], 
        match, 
        preToken, 
        index = 0;
    while((match = reg.test.exec(str)) !== null) {
        preToken = tokens[tokens.length - 1];
        // 如果匹配结果的开始下标比上一次匹配结果的结束下标大,说明两个模板语法之间有字符串
        if(match.index > index) {
            preToken = new Token(TPL_TOKEN_TYPE_STRING, str.slice(index,match.index), preToken);
            tokens.push(preToken);
        }
        preToken = new Token(TPL_TOKEN_TYPE_EXPRESSION, match[0], preToken);
        preToken.script = reg.use.apply(reg,match);
        tokens.push(preToken);
        // 保存本次匹配结果的结束下标
        index = match.index + match[0].length;
    }

    return tokens;
}
// 从js表达式中提取出变量,用来构建执行上下文
function getVariableContext(code) {
    var jsExpressTokens = code.match(jsTokens.default).map(function(item) {
        jsTokens.default.lastIndex = 0;
        return jsTokens.matchToToken(jsTokens.default.exec(item));
    }).map(function(item) {
        if(item.type === 'name' && isJsKeywords.test(item.value)) {
            item.type = 'keywords';
        }
        return item;
    });

    var ignore = false; // 跳过变量的属性
    var variableTokens = jsExpressTokens.filter(function(item) {
        if(item.type === 'name' && !ignore) {
            return true;
        }
        ignore = item.type === 'punctuator' && item.value === '.';
        return false;
    });
    // console.log(variableTokens);
    // return;
    variableTokens.forEach(function(item) {
        var val;
        if(!Object.prototype.hasOwnProperty.call(variableContextMap, item.value)) {
            if(item.value === OUT_CODE) {
                val = '';
            } else if(Object.prototype.hasOwnProperty.call(globalThis, item.value)) {
                val = GLOBAL_DATA + '.' + item.value;
            } else {
                val = DATA + '.' + item.value;
            }
            variableContextMap[item.value] = val;
            variableContext.push({
                name: item.value,
                value: val
            });
        }
   
    });
}

function getScriptTokens(tplTokens) {
    var scripts = [];
    for(var i = 0; i < tplTokens.length; i++) {
        // 去掉换行符,js中引号包裹的字符串不能换行
        var source = tplTokens[i].value.replace(/\n/g,'');
        var code = '';
        if(tplTokens[i].type === TPL_TOKEN_TYPE_STRING) {
            code = OUT_CODE + ' += "' + source + '"';
        } else if(tplTokens[i].type === TPL_TOKEN_TYPE_EXPRESSION) {
            if(tplTokens[i].script.output) {
                code = OUT_CODE + ' += ' + tplTokens[i].script.code;
            } else {
                // js表达式
                code = tplTokens[i].script.code;
            }
            getVariableContext(code);
        }
        scripts.push({
            source: source,
            tplToken: tplTokens[i],
            code: code
        });
    }

    return scripts;
}
posted @ 2023-08-30 17:10  Fogwind  阅读(9)  评论(0编辑  收藏  举报