最近有个朋友问<%=...%> 插值语法的一些问题,突然想起以前使用<%=...%>语法进行模板编译的日子,似乎已经很久远了,刚好有点时间所以写篇文章重新温故下模板编译的处理逻辑。

关键 正则表达式eval函数字符串拼接

1
2
3
4
5
<ul>
<% for(var i = 0; i< data.list.length;i++) {%>
<li><%= data.list[i] %></li>
<% } %>
</ul>

给定上面的模板字符串,我们希望能够提供一个模板编译函数,传递数据(data:["a","b","c"])给该函数以编译出下面的文本标签。

1
2
3
4
5
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>

思路 对于上面的字符串模板,如果要编译得到目标字符串,那么关键点在于for循环的处理,这里最简单的办法就是直接通过eval函数来执行 for循环部分的代码,而<% 和 %>等部分则需要通过正则来进行替换。假设,我们在这里提供一个 echo函数 用于做字符串的拼接,那么可以考虑先把模板字符串处理为下面的样式,再行处理。

1
2
3
4
5
6
7
echo('<ul>');
for(var i = 0; i< data.list.length;i++) {
echo('<li>');
echo(data.list[i]);
echo('</li>');
}
echo('</ul>');

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

/* 1.模板字符串 */
let template = `
<ul>
<% for(var i = 0; i< data.list.length;i++) {%>
<li><%= data.list[i] %></li>
<% } %>
</ul>
`

/* 2.封装编译函数 */
function compile(template) {

/* 2.1 编写正则规则 */
/* 匹配<% %>部分 */
let expr = /<%([\s\S]+?)%>/g;
/* 匹配 <%= data.list[i] %> 部分*/
let evalExpr = /<%=(.+?)%>/g;

/* 2.2 字符串拼接和正则匹配:$1表示匹配到的原文内容 */
template = template
.replace(evalExpr, '`); \n echo($1); \n echo(`')
.replace(expr, '`); \n $1 \n echo(`');

/* 2.3 拼接最外层的 echo 函数 */
template = 'echo(`' + template + '`)';

/* 2.4 组装解析函数 */
let script = `(function parse(data){
let output = "";
function echo(html){
output += html.trimEnd();
}
${template}
return output;
})`;
console.log('script', script);

return script;
}

/* 3.生成解析函数 */
let parse = eval(compile(template));

/* 4.注入数据 */
let html = parse({ list: ["a", "b", "c"] });
console.log(html);

/* 输出内容: */
/*
script (function parse(data){
let output = "";
function echo(html){
output += html.trimEnd();
}
echo(`
<ul>
`);
for(var i = 0; i< data.list.length;i++) {
echo(`
<li>`);
echo( data.list[i] );
echo(`</li>
`);
}
echo(`
</ul>
`)
return output;
})
**********************************
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
*/