详解Node.js模板引擎制作
什么是模板引擎
模板引擎是一种将数据和模板文本结合起来产生新文本的工具。模板引擎允许我们使用模板文本生成我们需要的HTML、XML、JSON等格式的文本。互联网浏览器解析HTML是一件非常耗费性能的事情,而且HTML中可以嵌入静态资源、样式、脚本等,模板引擎可以将大量的相同或类似的内容进行复用,让前端渲染部分变得更加灵活和高效。
模板引擎的运作方式
由于模板引擎是将数据和模板文本结合起来生成新文本,所以在使用模板引擎的时候,也就涉及了模板的编写和数据的填充。通常情况下,模板文本会在服务器端预处理后返回给客户端,而相应的数据会以某种格式进行传输,如JSON、XML等。前端JS则可以解析这些数据并使用对应的模板进行渲染。
常用的模板引擎有EJS、Handlebars、Pug等。
制作模板引擎
在开始制作模板引擎之前,需要确定模板文本和数据的表达方式。这里我们使用JSON格式的数据和类似Handlebars的模板语法来实现一个简单的模板引擎。
需求分析
我们需要实现一个可以将模板文本生成HTML标记语言的模板引擎,可以自由定义变量、条件语句、循环语句等。示例模板如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
{{#if body}}
<div class="body">{{body}}</div>
{{else}}
<div class="no-body">No Body Detected</div>
{{/if}}
<ul>
{{#each items}}
<li>{{this}}</li>
{{/each}}
</ul>
</body>
</html>
实现步骤
步骤1:模板文本解析
实现模板文本解析,将类似{{title}}
、{{#if body}}...{{else}}...{{/if}}
、{{#each items}}...{{/each}}
的标记解析出来。具体实现如下:
function parser(source) {
const REGEXP_VARIABLE = /{{(.*?)}}/g; //变量
const REGEXP_IF = /{{#if (.*?)}}([\s\S]*?){{\/if}}/g; //条件语句
const REGEXP_ELSE = /{{#else}}([\s\S]*?){{\/if}}/g; //否定语句
const REGEXP_EACH = /{{#each (.*?)}}([\s\S]*?){{\/each}}/g; //循环语句
let result = source;
result = result.replace(REGEXP_IF, (match, condition, ifTrue) => {
const ifFalse = REGEXP_ELSE.test(ifTrue) ? REGEXP_ELSE.exec(ifTrue)[1] : '';
return `\`;\nif (${condition}) {\ncontent += \`${ifTrue}\`;\n} else {\ncontent += \`${ifFalse}\`;\n}\ncontent += \``;
});
result = result.replace(REGEXP_EACH, (match, items, content) => {
return `\`;\nfor (let i = 0; i < ${items}.length; i++) {\nconst thisData = ${items}[i];\n${content}\n}\ncontent += \``;
});
result = result.replace(REGEXP_VARIABLE, (match, variable) => {
return `\${thisData.${variable}}`;
});
result = 'let content = `\n' + result + '\n`;';
result += '\nreturn content;';
return result;
}
这里定义了三个正则表达式和一个parser
函数。REGEXP_VARIABLE
用于匹配变量,如{{title}}
,(.*?)
表示非贪婪模式匹配。REGEXP_IF
用于匹配条件语句,如{{#if body}}...{{else}}...{{/if}}
,其中([\s\S]*?)
用于匹配包含换行符在内的任意字符,{{\/if}}
用于匹配条件语句的结束标记。REGEXP_ELSE
用于匹配否定语句,如{{#else}}...{{/if}}
。REGEXP_EACH
用于匹配循环语句,如{{#each items}}...{{/each}}
。
parser
函数将以上四个正则表达式结合起来实现了模板文本的解析。解析过程中,通过字符串拼接的方式生成JS代码字符串。
步骤2:模板参数生成
模板函数接收两个参数:数据和选项。数据表示模板所需的数据,选项则表示模板文本和编译之后的函数等信息。在这里,我们将解析后的JS代码字符串保存在选项的render
属性中。
function compile(template) { //返回的是编译后的函数
const render = new Function('data', `
${parser(template)}
`);
return function(data) {
const content = render.call(data);
return content;
};
}
此处使用了Function
构造函数将JS代码字符串转换为函数形式。render
函数的实现依赖于parser
函数生成的JS代码。compile
函数返回的是编译后的函数。
步骤3:制作模板引擎
我们可以将compile
函数封装为一个模板引擎,以便更好地使用。这里我们将模板引擎命名为CETemplate
,封装的方法包括编译模板和渲染数据等。
class CETemplate { //模板引擎
constructor() {
this.templates = {}; //已经编译过的模板列表
}
compile(name, template) { //编译模板
const compiled = compile(template);
this.templates[name] = compiled;
return compiled;
}
render(name, data) { //渲染数据
const template = this.templates[name];
if (!template) {
throw new Error(`Template ${name} not found.`);
} else {
return template(data);
}
}
}
此处使用了Class
关键字定义一个模板引擎类,包含了编译模板和渲染数据两个方法,以及一个保存已编译模板的templates
属性。
示例说明
下面演示如何使用我们制作的模板引擎生成HTML页面。
const ce = new CETemplate();
const template = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
{{#if body}}
<div class="body">{{body}}</div>
{{else}}
<div class="no-body">No Body Detected</div>
{{/if}}
<ul>
{{#each items}}
<li>{{this}}</li>
{{/each}}
</ul>
</body>
</html>
`;
const compiled = ce.compile('template', template);
const data = {
title: 'CE Template Engine',
body: 'Hello, World!',
items: ['Item 1', 'Item 2']
};
const content = ce.render('template', data);
console.log(content);
以上代码中,我们首先创建了一个模板引擎实例ce
。然后,定义了一个HTML模板,使用compile
将其编译,并将编译后的函数保存到ce
实例中。接着,我们定义了一个数据对象并将其传递给render
函数,最后打印输出渲染后的HTML代码。
再看一个更加复杂的示例,包含了不同类型的标记。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<header>
<nav>
<ul>
{{#each menu}}
<li{{#if (isActive this.url)}} class="active"{{/if}}><a href="{{this.url}}">{{this.text}}</a></li>
{{/each}}
</ul>
</nav>
</header>
<h1>{{title}}</h1>
<div>{{content}}</div>
{{#if isShow}}
<div class="notice">{{notice}}</div>
{{/if}}
<footer>
<p>© {{year}} {{owner}}</p>
</footer>
</body>
</html>
const ce = new CETemplate();
const template = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<header>
<nav>
<ul>
{{#each menu}}
<li{{#if (isActive this.url)}} class="active"{{/if}}><a href="{{this.url}}">{{this.text}}</a></li>
{{/each}}
</ul>
</nav>
</header>
<h1>{{title}}</h1>
<div>{{content}}</div>
{{#if isShow}}
<div class="notice">{{notice}}</div>
{{/if}}
<footer>
<p>© {{year}} {{owner}}</p>
</footer>
</body>
</html>
`;
const compiled = ce.compile('template', template);
const data = {
title: 'CE Template Engine',
menu: [
{ url: '/', text: 'Home' },
{ url: '/about', text: 'About' },
{ url: '/contact', text: 'Contact' },
],
content: 'Hello, World!',
isShow: true,
notice: 'This is a notice.',
year: 2021,
owner: 'My Company'
};
const content = ce.render('template', data);
console.log(content);
对比两个示例,我们可以发现,使用模板引擎可以方便地生成HTML页面。虽然本文的示例比较简单,但是模板引擎的应用非常广泛,对于高效开发和优化性能都有很大的帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解nodejs模板引擎制作 - Python技术站