赖同学


  • 首页

  • 标签

  • 分类

  • 归档

  • 站点地图

  • 留言

  • 搜索

Nodejs实战 —— Web 程序的模板

发表于 December 17, 2018|分类于 NodeJS|阅读次数: –
字数统计: 6596|阅读时间: 33 min

读 《node.js 实战 2.0》,进行学习记录总结。

当当网购买链接

豆瓣网 1.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&gt;

如果用在模板中的是可信数据,不想做转义处理,可以用 <%- 代替 <%= :

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 用空格表示标签的嵌套关系'
NodeJS
前端面试题目汇总摘录(JS 编程篇)
Nodejs实战 —— 深入了解Connect 和 Express
  • 文章目录
  • 站点概览
  1. 1.Web 程序的模板
    1. 1.用模板保持代码的整洁性
      1. 1.<strong>模板实战</strong>
        1. 1.<strong>不用模板</strong>
        2. 2.**用模块渲染 HTML **
    2. 2.Embedded JavaScript 的模板
      1. 1.<strong>创建模板</strong>
        1. 1.<strong>字符转义</strong>
      2. 2.<strong>将 EJS 集成到你的程序中</strong>
        1. 1.<strong>缓存 EJS 模板</strong>
      3. 3.**在客户端程序中使用 EJS **
    3. 3.使用 Mustache 模板语言与 Hogan
      1. 1.<strong>创建模板</strong>
      2. 2.<strong>Mustache 标签</strong>
        1. 1.<strong>1. 显示简单的值</strong>
        2. 2.<strong>2. 区块:多个值的循环遍历</strong>
        3. 3.<strong>3. 反向区块:值不存在时的默认 HTML</strong>
        4. 4.<strong>4. 区块 lambda: 区块内的定制功能</strong>
        5. 5.5**. 子模板:在其他模板中重用模板**
      3. 3.**微调 Hogan **
    4. 4.用 Pug 做模板
      1. 1.<strong>Pug 基础知识</strong>
        1. 1.<strong>1. 指定标签的属性</strong>
        2. 2.<strong>2. 用块扩展把他组织好</strong>
        3. 3.<strong>3. 将数据纳入到 Pug 模板中</strong>
      2. 2.<strong>Pug 模板中的逻辑</strong>
        1. 1.**1. 在 Pug 模板中使用 JavaScript **
        2. 2.<strong>2. 循环遍历对象和数组</strong>
        3. 3.<strong>3. 条件化渲染的模板代码</strong>
        4. 4.<strong>4. 在 Pug 中使用 case 语句</strong>
      3. 3.<strong>组织 Pug 模板</strong>
        1. 1.<strong>1. 用模板继承组织多个模板文件</strong>
        2. 2.<strong>2. 用块前缀/块追加实现布局</strong>
        3. 3.<strong>3. 模板包含</strong>
        4. 4.<strong>4. 借助 mixin 重用模板逻辑</strong>
    5. 5.总结
© 2018 — 2023赖彬鸿
1.6k
载入天数...载入时分秒...
0%