现成的模板引擎
开始手写之前,我们先看看模板引擎应该是什么样的,在用koa开发后台服务的时候,我们用过ejs模板引擎,其作用是把模板渲染成html代码。下面是一个具体的使用例子。
安装
代码语言:javascript复制npm install ejs
使用示例
在koa中使用ejs模板引擎。
index.js
代码语言:javascript复制const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const app = new Koa()
// 加载模板引擎
app.use(views(path.join(__dirname, './view'), {
extension: 'ejs'
}))
app.use( async ( ctx ) => {
let title = 'hello koa2'
await ctx.render('index', {
title,
})
})
app.listen(3000)
view/index.ejs
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p>EJS Welcome to <%= title %></p>
</body>
</html>
除了ejs,还有很多其他的模板引擎。
手写简单的模板引擎
那这些模板引擎具体是怎么实现的呢?
下面我们来手写一个简单的类ejs模板引擎。
需求分析
实现模板引擎先要定义模板的语法,这里我们就重新不定义了,直接使用ejs的语法。
我们只实现最简单的几个语法:
<%
'脚本' 标签,用于流程控制,无输出。<%=
输出数据到模板(输出是转义 HTML 标签)%>
一般结束标签
设计思路
先贴一下待编译的模板。
代码语言:javascript复制let template = `
<ul>
<% for(let i=0; i < data.supplies.length; i ) { %>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;
思路是把模板转换为JavaScript代码并执行,输出转码后的html代码。
观察一下上面的代码,我们可以先推出来,转换后的JavaScript代码。
代码语言:javascript复制echo(`<ul>`); //echo用于打印出html代码
for(var i=0; i<data.supplies.length; i ) {
echo(`<li>`);
echo( data.supplies[i] );
echo(`</li>`);
}
echo(`</ul>`);
下面是echo的实现。
代码语言:javascript复制var output = "";
function echo(html){
output = html;
}
我们最后生成的,用于输出html的JavaScript代码是下面这样的。
代码语言:javascript复制(function parse(data){
// stores the parsed output
var output = "";
// appends HTML to the parsed template
function echo(html){
output = html;
}
// contains echos, etc
echo(`<ul>`);
for(var i=0; i<data.supplies.length; i ) {
echo(`<li>`);
echo( data.supplies[i] );
echo(`</li>`);
}
echo(`</ul>`);
return output;
})
具体实现
下面列一下对不同字符串的处理方式。
- 不在插值标签里面的字符,直接输出为html代码。
- 在
<%
%>
里面的字符,保留为js逻辑 - 在
<%=
%>
里面的字符,保留js逻辑,且其值输出为html代码。
对这些处理方式,着手实现。
正则/<%=(. ?)%>/g
匹配第三种类型的字符串,替换为n echo( $1 ); n
。
正则/<%([sS] ?)%>/g
匹配第二种类型的字符串,替换为n $1 n
。
其他不是插值的字符,直接n echo( $1 ); n
,由于正则取反比较复杂,这里巧妙转换一下写法,改为在开头和每个类型二三的结尾加一个 "echo(`",结尾加一个结束符号,也能达到统一的结果。
最终完整的代码如下:
代码语言:javascript复制function compile(template){
const evalExpr = /<%=(. ?)%>/g;
const expr = /<%([sS] ?)%>/g;
template = template
.replace(evalExpr, '`); n echo( $1 ); n echo(`')
.replace(expr, '`); n $1 n echo(`');
template = 'echo(`' template '`);';
let script =
`(function parse(data){
let output = "";
function echo(html){
output = html;
}
${ template }
return output;
})`;
return script;
}
let template = `
<ul>
<% for(let i=0; i < data.supplies.length; i ) { %>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;
let div = document.getElementById("app");
let parse = eval(compile(template));
console.log(parse)
div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] });
运行一下
参考文章
how-to-write-a-template-compiler-in-javascript
实例:模板编译