Nodejs实战 —— Web 程序的模板
读 《node.js 实战 2.0》,进行学习记录总结。
这章会涉及三个热门的模板引擎,以及如何用模板把显示层标记从逻辑代码中分离出来,保持 Web 程序代码的整洁性。
Web 程序的模板
用模板保持代码的整洁性
在 Node 中 可以像其他 Web 技术一样,用 MVC 模式开发传统的 Web 程序。主要的思想是将逻辑、数据和展示层分离。在遵循 MVC 模式的 Web 程序中,一般是用户向服务器请求资源,然后控制器向模型请求数据,得到数据后传到视图,再由视图以特定格式将数据呈现给用户。MVC 中的视图部分一般会用到某种模板语言。在使用模板时,视图会将模型返回的数据传递给模板引擎,并制定用那个模板文件展示这些数据。
![MVC 程序的流程以及它跟模板层的交互](/images/posts/2018-12-17-NodeJs2-Part2-MVC 程序的流程以及它跟模板层的交互.png)
模板文件中通常包含数据的占位符、HTML、CSS,有时还会用一些客户端 JavaScript 来做第三方小部分显示,比如点赞按钮、或者触发界面行为,隐藏显示页面等等操作。因为模板文件的工作重点是展示而不是处理逻辑,所以前后端人员可以一起工作,有利于项目任务的分工。
模板实战
博客文章是从文本中读取的,格式如下。 ---
表明一篇文章结束,另一篇文章开始。
title: It is someone birthday!
data: January 09,2098
I am getting old, but thankfully i am not in jail!
---
title: 'Movies are pretty good'
data: January 2,2078
I have been watching a lot of movies lately.It is relaxing,except when they have clowns in them.
博客程序代码,读取博客文章
// index.js
const fs = require('fs');
const http = require('http');
// 读取和解析博客文章的数据
function getEntries() {
const entries = [];
// 从文件中读取博客文章的数据
let entriesRaw = fs.readFileSync('./entries.txt', 'utf8');
entriesRaw = entriesRaw.split('---');
entriesRaw(entryRaw => {
const entry = {};
const lines = entryRaw.split('\n');
lines.map(line => {
if (line.indexOf('title: ') === 0) {
entry.title = line.replace('title: ', '');
} else if (line.indexOf('date: '
') === 0) {'
entry.date = line.replace('date: '
', '
');'
}
else {
entry.body = entry.body || '';
entry.body += line;
}
})
entries.push(entry);
});
return entries;
}
const entries = getEntries(); console.log(entries);
const server = http.createServer((req, res) => {
const output = blogPage(entries);
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.end(output);
}); server.listen(8000);
页面用函数 blogPage 函数渲染的,用它把文章渲染到 HTML 页面中,以便发送浏览器,有两种不同的尝试方式, 用模板和不用模板。
不用模板
博客程序可以直接输出 HTML,但杂处理逻辑中引入 HTML 会让代码变得很乱
function blogPage(entries) {
let output = `
<html>
<head>
<style type="text/css">
.entry_title { font-weight: bold; }
.entry_date { font-style: italic; }
.entry_body { margin-bottom: 1em; }
</style>
</head>
<body>
</html>
`;
entries.map(entry => {
output += `
<div class="entry_title">${entry.title}</div>
<div class="entry_date">${entry.date}</div>
<div class="entry_body">${entry.body}</div>
`;
});
output += `</body></html>`;
return output;
}
可以看出来,逻辑中有很多 HTML 会显得很乱。
**用模块渲染 HTML **
模板可以把 HTML 从处理逻辑中挪走,大幅度提高代码的整洁性。
安装 Embedded JavaScript 模块演示:
npm install ejs
加载模板
const ejs = require('ejs');
const template = fs.readFileSync('./template/blog_page.ejs', 'utf8');
function blogPage() {
const values = {
entries
};
return ejs.render(template, values);
}
EJS 模板文件是由 HTML 标记和数据占位符构成的。
<html>
<head>
<style>
.entry_title {
font-weight: bold;
}
.entry_date {
font-style: italic;
}
.entry_body {
margin-bottom: 1em;
}
</style>
<body>
<% entries.map(entry =>{ %>
<div class="entry_title"><%= entry.title %></div>
<div class="entry_date"><%= entry.date %></div>
<div class="entry_body"><%= entry.body %></div>
<% }) %>
</body>
</head>
</html>
Node 社区创建了很多模板引擎,HTML 需要闭合标签,而 CSS 需要左右大括号,会有特殊的语言,例如 Pug 语言可以更简洁地表示 HTML 或者 CSS 。
本节会介绍三种模板引擎,以及在 Node 中如何取使用它们:
- 'Embedded JavaScript (EJS)引擎'
- '极简的 Hogan 引擎'
- 'Pug 模板引擎'
Embedded JavaScript 的模板
Embedded JavaScript 处理模板的方式简单直接,可以把 EJS 标签当做数据准备的占位符嵌入到 HTML 中,还可以在模板中执行纯 JavaScript 代码,就像 PHP 那样完成条件分支和循环之类的工作。
创建模板
在模板的世界中,发送给模板引擎做渲染的数据有时被称为上下文,而下面是 EJS 用一个简单的模板渲染上下文的例子:
const ejs = require('ejs');
const template = '<%= message %>';
const context = {
message: 'Hello template!'
};
console.log(ejs.render(template, context));
注意发送给 render 第二个参数中的 locals 的用法。第二个参数可以包含 EJS 选项以及上下文数据,而 locals 可以确保上下文数据不会被当做 EJS 选项。但大多数情况下你都可以把上下文本身当做第二个参数,例如下面 render:
console.log(ejs.render(template, context));
如果把上下文直接当做 render 的第二个参数,一定不要给上下文中的值用这些名称:cache/client/close/compileDebug/debug/filename/open/scope 。它们是为修改模板引擎设定的保留字。
字符转义
在渲染时,EJS 会转义上下文值中的所有特殊字符,将它们替换为 HTML 实体码,这是为了防止跨站脚本(xss)攻击,恶意用户会将 JavaScript 作为数据提交给 Web 程序,希望其他用户访问包含这些数据 的页面时能在他们的浏览器中执行。下面的代码展示了 EJS 的转义处理。
const ejs = require('ejs');
const template = '<%= message %>';
const context = { message: "<script> alert('XSS attack);<script/>" };
console.log(ejs.render(template, context));
这段代码在显示时会输出下面这种代码:
& lt;
script & gt;
alert('XSS attack'); & lt;
/script>
如果用在模板中的是可信数据,不想做转义处理,可以用 <%-
代替 <%=
:
const ejs = require('ejs');
const template = '<%- '
message % > ''
const context = {
message: "<script>alert('Trusted JavaScript!');</script>"
};
console.log(ejs.render(template, context));
注意!指明 EJS 标签的字符是可修改的,比如像这样:
const ejs = require('ejs');
ejs.delimiter = '$';
const template = '<$= message $>'
const context = { message: 'Hello template!'
console.log(ejs.render(template, context));
将 EJS 集成到你的程序中
把模板和代码放到同一个文件里很别扭,并且会显得代码很乱。接下来使用 Node API 从单独的文件中读取模板。
进入工作目录,创建 app.js 文件,把下面的代码放在里面。
// app.js
const ejs = require('ejs');
const fs = require('fs');
const http = require('http');
const filename = './template/students.ejs';
const students = [
{
name: 'Rick LaRue',
age: 23
},
{
name: 'Sarah Cathands',
age: 25
},
{
name: 'Bob Dobbs',
age: 37
}
];
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
if (req.url === '/') {
// 从文件中读取模板
fs.readFile(filename, (err, data) => {
const template = data.toString();
const context = {
students: students
};
// 渲染模板
const output = ejs.render(template, context);
res.setHeader('Content-Type', 'text/html');
// 发送响应
res.end(output);
});
} else {
res.statusCode = 404;
res.end('Not Found');
}
});
server.listen(8000);
接下来创建存放模板文件的 students.ejs 文件。
<% if (students.length) { %>
<ul>
<% students.forEach(student =>{ %>
<li><%= student.name %> (<%= student.age %>)</li>
<% }) %>
</ul>
<% } %>
缓存 EJS 模板
如果有必要,可以让 EJS 把模板函数缓存在内存中。也就是说在解析完模板文件后,EJS 可以把解析得到的函数存下来。这样以后需要渲染这个模板时不用再次解析,所以渲染速度会快。
如果正在开发过程当中,想即时看到模板文件修改后的效果,则不要启用缓存。但在把程序 部署到生产环境中时,启用缓存是一种简单快捷的性能优化办法。可以通过环境变量 NODE_ENV 判定是否启用缓存
const cache = process.env.NODE_ENV === 'production';
const output = ejs.render(template, {
students,
cache,
filename
});
第二个参数的 filename 选项不仅限于文件,可以用要渲染的模板的唯一标识。接下来看看怎么在客户端使用 EJS。
**在客户端程序中使用 EJS **
要在客户端使用 EJS ,首先要先把 EJS 搜索引擎下载到工作目录下面
curl -O https://raw.githubusercontent.com/tj/ejs/master/lib/ejs.js
下载完就可以在客户端使用 EJS ,下面代码存为 index.html 并试着在浏览器打开运行
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<script src="./ejs.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.js"></script>
<title>EJS example</title>
</head>
<body>
<div id="output"></div>
</body>
<script>
const template = '<%= message %>';
const context = {
message: 'Hello template!'
};
$(document).ready(() => {
$('#output').html(ejs.render(template, context));
});
</script>
</html>
Hogan 模板引擎,特意限制了模板代码中可用的功能。
使用 Mustache 模板语言与 Hogan
Hogan.js 是 Twitter 为满足自己的需求而创建的引擎模板。Hogan 是 Mustache 模板语言标准的具体实现。
跟 EJS 不用的是,Mustache 遵循极简主义,特意去掉了条件逻辑。在内容过滤上,Mustache 只为防止 XSS 的攻击而保留了转义处理功能,主张模板代码应该尽可能地简单。
本节将介绍:
- '如何在程序中创建和实现 Mustache 模板'
- 'Mustache 标准中的各种模板标签'
- '如何用子模板组织模板'
- '如何用定制的分隔符和其他选项对 Hogan 进行微调'
创建模板
要使用 Hogan ,同样先安装
npm i -save hogan.js
下面是用 Hogan 使用简单模板渲染上下文的例子。运行后输出 ’Hello template‘
const hogan = require('hogan.js');
const templateSource = '{{message}}';
const context = {
message: 'Hello Template'
};
const template = hogan.compile(templateSource);
console.log(template.render(context));
node index.js
了解如何用 Hogan 处理 Mustache 模板后,下面看看它支持哪些标签
Mustache 标签
Mustache 标签在概念上跟 EJS 的标签类似,也有变量值的占位符,指明哪里需要循环。可以增强 Mustache 的功能,在模板里添加注释。
1. 显示简单的值
在 Mustache 模板中,要把想要显示的上下文的名称放在大括号中。大括号在 Mustache 社区里被称为胡须。比如说,要显示上下文项 name 的值,应该使用 Hogan 标签
{{name}}
跟大多数模板一样,Hogan 默认也会对内容进行转义以防止 XSS 攻击,如果想要在 Hogan 中显示未转义的值,既可以把上下文的名称放在第三条胡须中,也可以在前面添加一个 &
符号。
例如
{{&name}}
如果想在模板中加注释,可以这样
{{! This is a comment}}
2. 区块:多个值的循环遍历
尽管 Hogan 不允许在模板中使用逻辑,但是它确实引入了一种更加优雅的方法,用 Mustache 区块对上下文项中多个值做循环遍历:
const context = {
students:[
{ name: 'J', age:'23'},
{ name: 'L', age:'53'}
]
}
如果要创建一个模板,让每个学生都显示在单独的 HTML 段落中,可以像下面这样轻松实现:
<p>Name: J,Age: 23 years old</p>
<p>Name: L,Age: 53 years old</p>
下面这个模板能生成上面的 HTML :
{{# students}}
<p>Name: {{name}}, Age: {{age}} years old </p>
{{/students}}
3. 反向区块:值不存在时的默认 HTML
如果上下文数据中的 students 不是数组会怎么样?比如说,如果只是单个对象,那么模板互显示这个对象,但是如果是 undefined 或 false , 空数组,则什么都不显示。
如果想输出消息指明该区块的值不存在,那么可以用 Mustache 的反向区块。把下面的快模板代码加到前面那个显示学生消息的模板中,上下文中没有数据时就会显示这条消息:
{{^ students}}
<p>No students found</p>
{{/ students}}
4. 区块 lambda: 区块内的定制功能
如果 Mustache 现有功能无法满足,可以按照它的标准定制区块标签。让它调用函数处理模块内容,不用循环遍历数组。这被称为 区块 lambda 。
下面的例子用到了 github-flavored markdown 模块:
npm install github-flavored-markdown --dev
在下面这段代码中,模板中的Name传给由区块 lambda 调用的 Markdown 解析器,生成 了Name。
const hogan = require('hogan.js');
// 引入 Markdown 解析器
const md = require('github-flavored-markdown');
// Mustache 模板也包含 Markdown 格式的内容
const templateSource = `{{#markdown}}**Name**:{{name}}{{/markdown}}`;
// 模板的上下文包含了一个解析 Markdown 的区块 lambda
const context = {
name: 'Rick LaRue',
markdown: () => text => md.parse(text)
};
const template = hogan.compile(templateSource);
console.log(template.render(context));
使用区块 lambda 可以在模板中轻松实现缓存和转换机制等功能。
5**. 子模板:在其他模板中重用模板**
为了避免多个模板中复制粘贴相同的代码,可以将这些通用的代码做成子模板(partial)。 子模板是放在其他模板内的构件,可以把复杂的模板分解成简单模板。
比如下面这个例子,将显示学生数据的代码从主模板中分离出来做成了子模板。
const hogan = require('hogan.js');
// 用于子模板的代码
const studentTemplate = `
<p>
Name: {{ name }},
Age: {{ age }} years old
</p>
`;
// 主模板
const mainTemplate = `
{{# students}}
{{>student}}
{{/ students}}
`;
const context = {
students: [
{
name: 'Jane Narwhal',
age: 21
},
{
name: 'Rick LaRue',
age: 26
}
]
};
// 编译主模板和子模板
const template = hogan.compile(mainTemplate);
const partial = hogan.compile(studentTemplate);
// 渲染主模板和子模板
const html = template.render(context, {
student: partial
});
console.log(html);
**微调 Hogan **
Hogan 用起来相当简单,掌握它的标签汇总表就够了。在使用时可能只有一两个地方需要调整一下.
如果你不喜欢 Mustache 风格的大括号,可以给 compile 方法传入一个参数覆盖 Hogan 所用的分隔符。下面的例子把 EJS 风格的分隔符编译在 Hogan 中:
hogan.compile(text, {
delimiters: '<% %>'
});
除了 Mustache,还有其他模板语言。比如想把 HTML 的噪声都去掉的 Pug。
用 Pug 做模板
Pug, 以前叫做 Jade, 它用另一种方式来表示 HTML,是 Express 的默认模板引擎。Pug 和其他主流模板系统的差别主要在于它对空格的使用。Pug 模板用缩进表示 HTML 标签的嵌入关系。HTML 标签也不必明确给出关闭标签,从而避免了因为过早关闭,或根本就不关闭标签所产生的问题。由于有严格的缩进规则,因此 Pug 模板看起来很简洁,更易于维护。
<html>
<head>
<title>Welcome</title>
</head>
<body>
<div id="main" class="content">
<strong>"Hello world!"</strong>
</div>
</body>
</html>
这段 HTML 对应的 Pug 模板如下:
html
head
title Welcome
body
div.content#main
strong "Hello world!"
Pug 像 EJS 一样,可以嵌入 JavaScript ,可以用在服务端和客户端。但 Pug 还有其他特性,比如模板继承和 mixins。用 mixins 可以定义易于重用的小型模板,用来表示常用的视觉元素的 HTML ,比如条目列表和盒子。Mixins 很像上一节介绍的 Hogan 的自末班。有了模板继承,那些把一个 HTML 页面渲染到多个文件中 Pug 模板组织起来就更容易了。先安装:
npm install pug -S
本节介绍:
- 'Pug 的基础知识,比如说类名、属性和块扩展'
- '如何用内置的关键字往 Pug 模板里添加逻辑'
- '如何用继承、块和 mixins 组织模板'
Pug 基础知识
Pug 的标签和 HTML 一样,但是抛弃了前后的字符,并用缩进表示标签的嵌套关系。标签可以用 .<classname>
关联一个或者是多个 CSS 类。比如应用了 content 和 sidebar 类的 div 元素表示为:
div.content.sidebar
在标签上添加 #<ID>
可以赋予它 CSS 的 ID 。比如上面那个再加上一个 ID
div.content.sidebar#featured_content
1. 指定标签的属性
将标签的属性放在括号中,每个属性之间用括号分开,下面的 Pug 表示一个 会在新的浏览器打开的标签:
a(href='http://laibh.top',target='_blank')
因为带属性的标签可能会很长,所以 Pug 在处理这样的标签的时候会比较灵活。比如下面的这种表示跟那个效果是一样的:
a(href='http://laibh.top',
target='_blank')
也可以指定不需要值的属性,下面的这段 Pug 实例是一个 HTML 表单,其中包含一个 select 元素,也有预定的 option :
strong Select your favorite food:
form
select
option(value='Cheese') Cheese
option(value='Tofu') Tofu
Specifying tag content
在前面那段代码中还有标签内容的示例:strong 标签后面的 Select your favorite food:;第一个 option 后面的 Cheese;第二个 option 后面的 Tofu。
这是 Pug 中指定标签内容的常用方法,但不是唯一的。尽管这种风格在指定的比较短的内容时很好用,但如果标签的内容很长,则会导致 Pug 模板中出现超长的代码行。不过。就想下面这个例子,可以用 | 指定标签的内容
textarea
| This is a some default text
| that the user should be
| provided with.
如果 HTML 标签只接受文本(即不能嵌入 HTML 元素),比如 style 会让 script 。则可以去掉 | 字符,像下面这样:
style.
h1{
font-size: 6em;
color: #9d9;
}
用这两种方法别分可以表示长短两种内容,可以让 Pug 模板保持有呀,Pug 还有一种表示嵌套关系的办法,叫做扩展。
2. 用块扩展把他组织好
Pug 一般用 缩进表示嵌套,但有时缩进形成的空格太多了。比如下面的这个用缩进定义链接列表的 Pug 模板:
ul
li
a(href='http://nodejs.org/') Node.js homepage
li
a(href='http://npmjs.org/') NPM homepage
li
a(href='http://nodebits.org/') Nodebits blog
如果用 Pug 块扩展,这个例子会更加紧凑。块扩展可以在标签后面用冒号表示嵌套。例如下面这段代码:
ul
li: a a(href='http://nodejs.org/') Node.js homepage
li: a(href='http://npmjs.org/') NPM homepage
li: a(href='http://nodebits.org/') Nodebits blog
3. 将数据纳入到 Pug 模板中
数据传入到 Pug 引擎的方式跟 EJS 一样,模板先被编译成函数,然后带着上下文调用它,以便于渲染输出 HTML 。如下例:
const pug = require('pug');
const template = 'strong #{message}'
const context = { message: 'Hello template' };
const fn = pug.compile(template);
console.log(fn(context));
这个模板中的 #{message}是要被上下文值替换掉的占位符。
上下文值也可以作为属性的值。这个例子会渲染出来
<a href="http://laibh.top"> </a>
const pug = require('pug');
const template = 'a(herf=url)'
const context = { url: 'http://laibh.top' };
const fn = pug.compile(template);
console.log(fn(context));
Pug 模板中的逻辑
把数据交给模板后,还需要定义处理数据的逻辑,可以直接在模板中嵌入 JavaScript 代码来定义数据处理逻辑。例如 if 、for 、var 声明等等。下面有个例子
h3.contact-header My Contacts
if contacts.length
each contact in contacts
- 'var fullName = contact.firstName + ' ' + contact.lastName'
.contact-box
p fullName
if contact.isEditable
p: a(href='/edit/+contact.id) Edit Record
p
case contact.status
when 'Active'
strong User is active in the system
when 'Inactive'
em User is inactive
when 'Pending'
| User has a pending invitation
else
p You currently do not have any contacts
下面来看一下 Pug 模板中嵌入 javascript 代码如何处理输出
**1. 在 Pug 模板中使用 JavaScript **
带有 -
前缀的 JavaScript 代码的返回结果不会出现在渲染结果中。带有 =
前缀的 JavaScript 代码的执行结果会出现。但为了防止 XSS 攻击做了转义处理。如果 JavaScript 代码生成的内容不应该转义。那么可以使用前缀 !=
.
在 Pug 中,有些常用的 条件判断和循环语句可以不带前缀:if/else/case/when/default/until/each 和 unless
Pug 中还可以定义变量,下面两种赋值方式效果是一样的:
-count = 0
count = 0
没有前缀的语句没有输出。
2. 循环遍历对象和数组
Pug 中的 JavaScript 可以访问上下文中的值。在下面的例子中,我们会读取一个 Pug 模板,并让它显示一个包含两个消息的上下文的数组:
const pug = require('pug');
const fs = require('fs');
const template = fs.readFileSync('./template.pug');
const context = {
messages: ['You have logged in successfully.', 'Welcome back!']
};
const fn = pug.compile(template);
console.log(fn(context));
Pug 模板的内容
- 'messages.forEach( message =>{'
p= message
- '})'
运行代码:
node index.js
输出这样的结果:
<p>You have logged in successfully.</p>
<p>Welcome back!</p>
Pug 中还有一种非 JavaScript 形式的循环:each 语句。用 each 语句很容易实现数组和对象属性的循环遍历.
each message in messages
p= message
对象属性的循环遍历可以稍有不同,例如这样:
each value, key in post
div
strong #{key}
p value
3. 条件化渲染的模板代码
有时候要根据数据的取值来决定显示那些模板,下面是一个条件判断的例子。几乎有一半的可能会输出 script 标签:
-n = Math.round(Math.random() * 1) + 1;
-if (n === 1){
script
alert('You win');
- '}'
条件判断在 Pug 中还有中更简洁的写法:
-n = Math.round(Math.random()* 1) + 1 ;
if n === 1
script
alert('You win!');
如果条件判断是取反的,比如 if (n!=1),可以用 Pug 的 unless 关键字:
-n Math.round(Math.random() *1) + 1;
unless n === 1
script
alert('You win!');
4. 在 Pug 中使用 case 语句
Pug 中还有类似 switch 的 非 JavaScript 条件判断:case 语句。case 语句可以根据模板的场景指定输出。
在下面的例子中,我们用 case 语句以三种不同的方式显示博客的搜索结果。如果没有结果,则显示一条提示消息。如果找到一篇文章,则显示它的详细内容。如果很多篇则用 each 语句循环遍历所有文章,显示它们的标题:
case results.length
when 0
p No results found
when 1
p= results[0].content
default
each result in results
p= result.title
组织 Pug 模板
模板定义好了之后,要知道该如何组织。跟程序逻辑一样,你肯定也不想让模板文件过大,一个模板应该对应一个构件:一个页面,一个边栏或者是一篇博客文章的内容;
下面会介绍几种机制,让几个不同模板文件一起渲染内容:
- '用模板继承组织多个模板文件'
- '用块前缀/追加实现布局'
- '模板包含'
- '借助 mixins 重用模板逻辑'
1. 用模板继承组织多个模板文件
模板继承是多个模板文件的结构化处理方法之一。从概念上讲,模板就像面向对象编程中的类。一个模板可以有扩展另一个,然后这个再扩展另一个,只要合理,使用多少层都可以。
下面有个例子,用模板继承提供一个简单的 HTML 包装器,可以用来包装 页面内容。创建存放 Pug 文件的 templates 目录。在其中创建模板文件 layout.pug
html
head
block title
body
block content
layout.pug 中有 HTML 页面的基本定义和两个模板块。模板块可以由后裔模板提供内容的占位符。layout.pug 的后裔模板可以在 title 模板块的位置设置页面标题,在 content 模板块的位置设定页面的内容要显示什么。
接下来创建 page.pug。这个模板会提供 title 和 content 块的具体内容:
html
head
block title
body
block content
继承的实际用法:
const pug = require('pug');
const fs = require('fs');
const templateFile = './templates/page.pug';
const iterTemplate = fs.readFileSync(templateFile);
const context = {
messages: ['You have logged in successfully.', 'Welcome back!']
};
const iterFn = pug.compile(iterTemplate, {
filename: templateFile
});
console.log(iterFn(context));
2. 用块前缀/块追加实现布局
在前面那个例子中,layout.pug 中的模板没有内容,因此在 page.pug 模板中设定内容简答直接。如果被继承的模板中没有内容,也可以用块前缀和块追加,在原有内容基础上构建新内容而不是替换它。
下面的 layout.pug 模板中加了一个模板块 scripts ,其中有加载 jq 的 script 标签
html
head
- 'const baseUrl = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/" '
block title
block style
block scripts
body
block content
extends layout.pug
block title
title Messages
block style
link(rel="stylesheet", href=baseUrl+"themes/flick/jquery-ui.css")
block scripts
script(src=baseUrl+"jquery-ui.js")
block content
- 'count = 0'
each message in messages
- 'count = count + 1'
script.
$(() => {
$("#message_#{count}").dialog({
height: 140,
modal: true
});
});
!= '<div id="message_' + count + '">' + message + '</div>'
但模板继承不是唯一一种集成多个模板的办法,还可以使用 include Pug 命令。
3. 模板包含
Pug 中的 include 命令式另一个组织模板的工具。这个命令会引入另一个模板中内容。
如果在前面那个 layout.pug 中加入一个 include footer 就会引入这个模板
html
head
- 'const baseUrl = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/" '
block title
block style
block scripts
body
block content
include footer
可以 用 include 往 layout.pug 中添加关于网站的消息或设计元素。也可以指定文件的扩展名,包含 非 Pug 文件(比如 include xxx.html)
4. 借助 mixin 重用模板逻辑
尽管 Pug 的 include 命令能帮我们引入之前创建的 代码块,但要构建在程序和不同页面之间共享的可重用功能库时,它就帮不上忙了。Pug 为此专门提供了 mixin 命令,可以用来定制可重用的 Pug 代码块。
mixin 模拟的是 JavaScript 函数。它跟函数一样,可以带参数,并且这些参数可以用来生成 Pug 代码。
比如说要处理下面这种数据结构:
const students = [
{ name: 'Rick LaRue', age: 23 },
{ name: 'Sarah Cathands', age: 25 },
{ name: 'Bob Dobbs', age: 37 }
]
如果要把对象中提取出来的属性输出到 HTML 列表中,那么可以像下面这样定义一个 mixin:
mixin list_object_property(objects, property)
ul
each object in objects
li = object[property]
然后就可以像下面这样借助 mixin 显示这些数据了
mixin list_object_property(students, 'name');
借助模板继承、include 语句和 mixin,你可以轻松地重用展示标记,防止模板文件变得过于冗长
总结
- '模板引擎可以把程序逻辑和展示组织好'
- 'EJS、Hogan.js 和 Pug 都是 Node 中比较流行的模板引擎。'
- 'EJS 支持简单的流程控制,以及转义或非转义插值。'
- 'Hogan.js 是简单的模板引擎,不支持流程控制,但支持 Mustache 标准'
- 'Pug 是比较复杂的模板语言,可以输出 HTML,但不用尖括号。'
- 'Pug 用空格表示标签的嵌套关系'