手把手带逛11ty第二集:模板

Eleventy

先忘掉文档转换,我们来说一说静态站点生成器要解决的问题

HelloWorld中我似乎给了你一种感觉:eleventy这个工具就是用来把“文档”转化成“html”网页的。

这话对也不对,准确的来说,eleventy只是内置了一个把“文档”转化为“html代码段”的功能,如果你看过HelloWorld项目中,dist目录中生成的index.html的话,就明白我在说什么:

<h1>二流程序员的核心品质</h1>
<p>复制粘贴一把梭,增删改查小能手。</p>
<p>会写<code>Hello World</code>就敢说自己精通某个语言、框架</p>

那么我们现在回想一下流行的博客网站,就那种特别简单的博客站点,整个网站大概有以下几个比较突出的特点:

  1. 有一个首页,首页一般有三个重要区域:
    1. 导航栏,常见的有这么几个链接:
      • 首页
      • 所有博文列表
      • About Me
    2. 主内容区,一般展示最近三~五篇博文的部分内容,或者最近一两篇博文的全文
    3. 外链与版权声明区,这里一般贴上博主的一些其它链接,比如github啊什么的,再写一个版权声明copyright之类的
  2. 点击链接进入任何文章页面,一般只是页面主内容区的内容被替换成博文内容,而导航栏与“外链与版权声明区”一般是共享的

关键就在这个“共享的导航栏和外链与版权声明区”里:这一段页面元素几乎是整个站点所有页面都共享的。

如果你把这部分内容想象成C语言里的头文件,那它就是一段几乎要被项目中所有代码文件都要引用起来的stdio.h

所以我们先来看一看,怎么写这个“头文件”

template与Nunjucks

这种静态站点里的“头文件”一般把它们叫做"template",不同的web框架中多多少少都支持这种内容,比如传统的Java世界里的JSP或者ASP .NET世界里的razor,就是这种东西,它们一般有两部分组成:

  1. 首先是完全兼容HTML+CSS
  2. 其二是有自己独创的一部分特殊语法,我喜欢把这类东西叫“占位符”

eleventy支持好几种template类型,不过比较常用的是一种叫Nunjucks的格式,这个文档格式挺简单,但要完全学习它还是要费一些时间的,不过不急,我们讲到哪里解释到哪里。

从最简单做起,我们现在重新再建个项目,就叫HelloTemplate吧,有了上一讲HelloWorld的经验,新建项目的步骤这里就不再赘述了。这个项目我们做两个页面,但:

  1. 新建一个公共的头文件
  2. 两个页面都引用这个头文件

新建项目的配置文件里,我们新增加一个配置项:

 module.exports = function(config) {
     return {
         dir: {
+            includes: "_templates",
             input: "src",
             output: "dist"
         }
     };
 }

${config.dir.includes}配置的是头文件的搜索路径,它的值是一个相对于${config.dir.input}的相对路径。也就是说,上面的配置项会告诉eleventy框架:请去./src/_templates目录下去搜索头文件。

然后我们再新建一个头文件./src/_templates/global.njk,文件扩展名.njk是nunjucks模板文件的扩展名,内容如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{title}}</title>
</head>
<body>
    <header>
        <ul>
            <li> <a href="/">index<a> </li>
            <li> <a href="/page1">page1<a> </li>
            <li> <a href="/page2">page2<a> </li>
        </ul>
    </header>

    {{ content | safe }}
</body>
</html>

这个模板里有两个占位符:

  • {{ title }} : 引擎会把一个名为title的变量的值填充在这里
  • {{ content | safe }} : 引擎会找到一个名为content的变量内容,然后再把这个变量的的值传给一个名为safe的特殊函数,然后再把safe函数返回的值填充在这里

也就是说,其它文件如果使用这个template的话,必须提供这两个变量:titlecontent。而至于safe这个函数,是随eleventy框架自带的,我们后面再解释它有什么作用。

我们在框架里还提供了一个简单的“导航”:其实就是两个分别指向/page1/page2的链接

接下来,我们创建两个文件src/page1.mdsrc/page2.md,内容随你,我是如下写的

# page1

这是页面一
# page2

这是页面二

再创建一个src/index.md作为首页,内容如下:

# index

这是首页

因为三个页面内容基本一致,所以下面我只以page1.md的改动为例。以下描述的改动都要同时应用在三个文件上。

现在问题是:怎么在page1.md中引用头文件?我们上面也说了,引用头文件就需要提供两个必要的变量titlecontent,这两个变量怎么定义?

  • content变量是一个引擎自动定义的变量:在引用头文件时,引擎会把当前文件的渲染(转化)结果放在一个名为content的变量中。也就是说我们不需要手动定义content变量
  • title这个变量就需要我们手动定义它的值了

还有一个问题:如何在page1.md中声明对src/_templates/global.njk的引用?

答案如下:

+---
+layout: global.njk
+title: 页面一
+---

 # page1

 这是页面一
  • 通过在文档头部指定layout变量的值,来说明我们需要引用的头文件是global.njk。这样引擎就会去${config.dir.includes},也就是src/_templates目录下去找对应的文件,找到了,bingo!

  • 通过在文档头部指定title变量的值,引擎就知道了在渲染时,如何填充global.njk中的<title>{{title}}</title>

  • 文档本身的渲染内容,会被引擎定义在content变量中,然后填充在global.njk中的{{ content | safe }}位置处

  • safe函数有什么用呢?简单来说就是page1.md转化后的内容是HTML代码段。如果用safe函数进行处理的话,引擎出于安全考虑,会把所有内容进行转义。比如# page1会先被引擎转化成<h1>page1</h1>,然后默认会被再处理成&lt;h1&gt;page1&lt;/h1&gt;。而如果使用safe函数进行处理的话,就不会被二次转义,而会保持HTML标签的样子。

    所以说,safe的意思是“这段内容可以被安全的渲染,请不要做额外的转义”,而不是说“请对这段内容做转义,以确保它安全”。。这怎么说呢,稍微有点反常识。

模板,template,头文件的基本概念通过这样一个简单的例子我们就说清楚了。template最迷人的地方在于:它是可以互相嵌套的!即template可以引用其它template,发挥无限想象力!