什么是 Eleventy?
Eleventy (或 11ty )是一个 静态网站生成器(Static Site Generator),用 JavaScript 编写,具有以下特点:
- 简单易用,零配置即可运行
- 支持多种模板语言(如 HTML、Markdown、Nunjucks、Handlebars、Pug 等)
- 无数据库、无 CMS,适合构建博客、文档站、作品集等
- 构建速度快,输出纯静态文件,便于部署
官网:www.11ty.dev(中文文档也已提供)
生成网站
从最基本的功能来看,Eleventy 和其他静态网站生成器类似:它会读取一个包含源文件的文件夹,处理这些输入内容,然后输出一个文件夹,其中的内容就是最终的网站。
假设你有一个包含 HTML 和 Markdown 文件的目录,结构如下所示。只要完成配置并运行 11ty(我推荐使用命令 npx @11ty/eleventy --serve
,它会启动一个支持热重载的本地服务器------建议你将这条命令添加到 package.json
的 scripts 中),Eleventy 就会将这些文件逐一转换成最终的网站,默认输出到名为 _site
的文件夹中。
注意,非 index
的页面文件会被自动转换为文件夹,并在其中生成一个 index.html
。这样做可以让 URL 更加简洁美观:例如,访问 /team
而不是 /team.html
,网址看起来更干净、更专业。
这基本上就是大多数静态网站生成器的工作方式。但 11ty 的特别之处在于,它被设计得极具可定制性。你可以自由设置它从哪里读取输入文件、处理哪些类型的文件、忽略哪些内容,以及最终网站的输出目录。你还可以混合使用多种模板引擎,添加"过滤器"来处理模板中的内容------比如将纯文本中的链接自动转换为可点击的超链接。此外,你可以通过 JSON 文件或 JavaScript 函数为页面提供数据(甚至可以在构建时从外部 API 获取数据),从而动态生成页面内容。功能远不止这些,而我只用了几个小时,就已经意识到还有大量功能是我尚未发现的。
官方文档一开始让我有些困惑,因为它对"如何搭建你的网站"几乎不持任何立场,也没有引导你思考如何根据自身需求进行定制,尽管它本身确实内置了一些默认行为。接下来,我会按照时间顺序,一步步带你了解我是如何发现这些默认行为,并根据自己的需要进行调整的。
使用 .eleventyignore
忽略文件
我遇到的第一个问题,是 GitHub 仓库中的 README.md
被自动处理并生成到了最终网站中,变成了 makespace.fun/readme
这样的页面。其实,Eleventy 支持像 .gitignore
那样忽略特定文件和文件夹。你只需创建一个名为 .eleventyignore
的文件,然后把想要忽略的文件或目录名逐行写入其中即可。例如:
README.md
_drafts/
secret.txt
这样,这些文件就不会被包含在生成的网站中了。这个机制简单却非常实用,能帮助你保持输出内容的整洁。
配置 Eleventy:.eleventy.js
使用"直通文件复制"保留原始资源
接着,我发现了一个问题:Eleventy 并没有把我的样式文件和静态资源复制到最终生成的网站中。默认情况下,它只处理 .html
和 .md
文件,像 /css
或 /assets
这样的重要文件夹并不会被自动包含进来。这是我遇到的第一个障碍!
经过一番摸索,我发现了 .eleventy.js
配置文件的存在。这个文件很容易创建,也是你进行大部分自定义配置的核心所在。
js
// 项目根目录下的 .eleventy.js
module.exports = function(eleventyConfig) {
// 自定义配置写在这里
}
为了让 /css
和 /assets
文件夹顺利进入最终输出目录,我需要添加如下代码,启用"直通复制"(Passthrough Copy)功能:
js
module.exports = function(eleventyConfig) {
// 这些文件夹将原封不动地复制到输出目录
eleventyConfig.addPassthroughCopy("assets");
eleventyConfig.addPassthroughCopy("css");
}
你甚至可以更灵活一些,在复制过程中重命名或更改目标路径。
除了资源文件的复制,你还可以在配置文件中自定义模板引擎、模板存放位置、添加自定义过滤器等。下面我会结合具体概念进一步介绍这些功能。
模板系统(Templating)
到目前为止,Eleventy 给我的感受还不算特别惊艳。虽然看到资源文件能顺利复制过去让我挺高兴,也觉得"挑选部分文件生成简洁 URL"这个想法不错------但整体流程还是有些繁琐。好在接下来我接触到了模板功能,这才真正体会到它的价值。
当时我有两个页面:首页和团队页。它们都包含一些重复的结构,比如 <head>
中的 <meta>
标签、导航栏和页脚。为了减少重复代码,我决定使用模板来统一管理这些共用部分。
自定义模板引擎
下面的例子中,我会继续使用 Eleventy 默认的 LiquidJS 模板引擎,因为我之前用过 Jekyll。但你完全可以在 .eleventy.js
配置文件中更换默认引擎,比如改用 Nunjucks 或 Pug。
js
module.exports = function(eleventyConfig) {
return {
markdownTemplateEngine: "njk", // 将 Markdown 文件使用 Nunjucks 引擎处理
htmlTemplateEngine: "pug" // 将 HTML 文件使用 Pug 引擎处理
}
}
注意:当你使用
return
语法时,可以覆盖默认的模板引擎设置。
博客文章示例:布局与组件复用
由于我之前用过 Jekyll,对"布局模板"和"包含组件"的概念并不陌生。假设你有一篇用 Markdown 写的博客文章,你想让它套用一个统一的页面模板,而这个模板又需要包含页脚这类可复用的组件。
hello-09.02.md(文章内容)
md
---
layout: blogpost.liquid
title: 你好!这是我的第一篇博客
---
# 哇,这是一篇超酷的博客!
是的,这是用 Markdown 写的!太棒了......
文件顶部 ---
之间的部分叫做 Front Matter(前置数据),它是一种为文件附加元数据的方式,供模板引擎读取。在这个例子中,我告诉 Eleventy:这篇博客要使用 blogpost.liquid
作为布局模板,并且把 title
变量传给模板使用。
blogpost.liquid(布局模板)
html
---
title: 默认标题
---
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
{{ content }}
{% include footer %}
</body>
</html>
注意,这个布局文件本身也有 Front Matter!它提供了一个默认标题,以防某个页面没有在 Front Matter 中指定标题。
{{ }}
双花括号用于插入变量内容。{% %}
花括号加百分号则用于执行模板逻辑,比如include
(包含)、if
条件判断,甚至for
循环。
这样一来,你就可以轻松实现页面结构的复用和动态内容的注入,大大提升了开发效率和维护性。
高级模板功能:布局链与包含变量
Eleventy 的模板系统不仅支持基础的复用,还允许你进行更复杂的组合操作,比如布局嵌套(链式布局)和对组件传递参数。
布局链(Layout Chaining)
你可以让一个布局模板本身也使用另一个布局作为其"父级"。例如,blogpost.liquid
使用 base.liquid
作为外层结构,而 base.liquid
负责定义 HTML 的基本骨架。这样就能实现多层结构的复用,避免重复代码。
向包含组件传递变量
更强大的是,你可以在 include
时传入变量,从而动态控制组件的行为。比如,为当前激活的导航项添加高亮样式:
html
<!-- index.html -->
{% include nav.liquid, current: 'index' %}
<!-- team.html -->
{% include nav.liquid, current: 'team' %}
html
<!-- _includes/nav.liquid -->
<ul>
<li class="{% if current == 'index' %}current{% endif %}">
<a href="/">首页</a>
</li>
<li class="{% if current == 'team' %}current{% endif %}">
<a href="/team">团队</a>
</li>
</ul>
通过传入 current
参数,导航组件能自动判断哪个页面是当前页,并为其添加 current
类,实现视觉上的选中状态。这种模式非常适合构建可复用、智能化的 UI 组件。
利用 Front Matter 控制资源加载
你还可以在页面的 Front Matter 中指定特定资源,比如加载不同的 CSS 文件:
yaml
---
css: team.css
---
然后在布局模板中引用:
html
<link rel="stylesheet" href="/css/{{ css }}">
这样一来,每个页面都可以灵活地加载自己所需的样式表,而无需在每个 HTML 文件中手动写死路径。
自定义包含文件目录
默认情况下,Eleventy 会在项目中查找名为 _includes
的文件夹来存放可复用的模板片段。但你完全可以自定义这个目录名。在 .eleventy.js
配置文件中使用 dir.includes
选项即可:
js
module.exports = function(eleventyConfig) {
return {
dir: {
includes: "components" // 现在会从 /components 文件夹中查找 include 文件
}
};
};
这让你可以根据项目结构自由组织模板组件,提升可读性和维护性。
数据管理(Data)
从静态 HTML 到结构化数据
当我逐步将页面模板化后,我发现很多信息是直接写死在 HTML 中的------比如团队页面上列出的几十位合作者姓名。每次增删成员都要手动修改 HTML,非常容易出错且难以维护。这时我意识到:这些内容完全可以提取成结构化的数据文件,比如 JSON,甚至未来还能从 API 动态获取!
Eleventy 的数据体系
Eleventy 提供了强大而灵活的数据管理机制,支持多种方式定义数据:
- Front Matter:在单个文件中定义页面专属数据。
- 文件夹级数据文件 (如
team.json
或team.11tydata.js
):为某一组页面提供共享数据。 - 全局数据文件 :放在
_data
文件夹中,所有模板都可以访问。
数据级联(Data Cascading)
Eleventy 的核心概念之一是 数据级联(Data Cascading)。它定义了数据的查找顺序和合并规则。简单来说:
数据优先级:页面 Front Matter > 文件夹数据 > 全局数据
如果有同名变量,优先级高的会覆盖低的。同时,对象类型的值会自动深度合并,而不是简单替换。例如:
- 全局
_data/site.json
定义了网站标题和默认描述。 - 某个页面的 Front Matter 可以只覆盖标题,描述则沿用全局值。
这种机制让你既能统一管理通用信息,又能灵活定制个别页面。
使用目录数据文件存储页面专属数据
以上面提到的"团队页"为例,我决定将团队成员的信息从 HTML 中抽离出来,单独存放到一个 JSON 文件中。为此,我把原来的 team.html
重命名为 team/index.html
(这样可以让 HTML 与数据文件共存于同一目录),然后在 team/
文件夹中创建了一个名为 team.11tydata.json
的文件。
只需这么做,Eleventy 就会自动识别这个文件,并将其内容作为该目录下所有模板的可用数据------无需任何额外配置!
json
{
"coconspirators": ["张三", "李四", "王五", "..."]
}
接着,在模板中就可以直接使用这些数据了:
html
<ul>
{% for person in coconspirators %}
<li>{{ person }}</li>
{% endfor %}
</ul>
这样一来,每次增减成员时,只需修改 JSON 文件,无需触碰 HTML 结构,维护起来既安全又高效。
小结数据管理
通过高级模板功能 和结构化数据管理,Eleventy 不再只是一个静态文件处理器,而是一个真正强大的内容生成引擎。你可以:
- 用 布局链 构建复杂页面结构
- 用 带参数的 include 实现智能组件
- 用 自定义 include 目录 组织项目结构
- 用 JSON 或 JS 数据文件 替代硬编码内容
- 利用 数据级联 实现灵活而一致的配置管理
这些特性共同构成了一个高效、可维护的现代静态网站开发体验。
过滤器(Filters):让数据处理更智能
当我把静态 HTML 内容逐步迁移到 JSON 数据时,遇到了一个问题:有些内容包含 HTML 标签,比如团队成员简介中的链接,而且这些链接需要在新标签页中打开(target="_blank"
)。
我当然可以把原始 HTML 直接写进 JSON:
json
{
"team": [
{
"bio": "此人曾在 <a href=\"https://makespacefoundation.org\" target=\"_blank\">MakeSpace</a> 工作。"
}
]
}
但这不仅让 JSON 显得臃肿混乱,还容易出错,尤其是每个链接都要手动加 target="_blank"
,非常繁琐。 更好的做法是:用 Markdown 写内容,再通过过滤器自动处理链接行为。
这时,Eleventy 的 过滤器(Filter) 功能就派上用场了!过滤器允许你在模板中对数据进行加工,语法简洁明了:
html
{{ bio | markdownify | externalLinks }}
它会先将 Markdown 转为 HTML,再为所有外部链接自动添加 target="_blank"
和 rel="noopener"
等安全属性。
创建自定义过滤器
在 .eleventy.js
配置文件中添加过滤器非常简单:
js
module.exports = function(eleventyConfig) {
// 配置 markdown-it 解析器
const options = {
html: true,
breaks: true,
linkify: true,
};
const md = require("markdown-it")(options);
// 添加 markdownify 过滤器
eleventyConfig.addFilter("markdownify", function(value) {
return md.render(value || "");
});
// 可以继续添加其他过滤器......
};
现在你就可以在任何模板中使用 {{ content | markdownify }}
,将 Markdown 文本转换为 HTML。
如果还想让所有链接在新窗口打开,可以进一步扩展 markdown-it
的渲染规则,或使用现成的插件(如 markdown-it-for-inline
),为每个链接自动注入 target="_blank"
属性。配置方式与上面类似,只需在 md
实例初始化时添加相应插件即可。
更多值得探索的功能
希望这篇指南能帮你理解 Eleventy 的核心魅力所在------它不只是一个静态站点生成器,更是一个灵活、可编程的内容构建系统。
接下来,我计划继续完善网站:
- 添加更多静态页面,并逐步建立统一的设计系统。
- 搭建博客功能 ,尝试使用 Eleventy 的:
- 分页(Pagination):自动将文章列表分页显示。
- 集合(Collections):按标签、分类等组织内容。
- 插件生态:例如面包屑导航、SEO 优化、RSS 生成等。
- 将本地 JSON 数据迁移到 Google Sheets:让非技术人员也能轻松编辑内容,无需访问代码仓库。
- 编写自定义短代码(Shortcodes):创建可复用的富媒体组件,比如嵌入视频、图表或引用框。
Eleventy 的强大之处在于,它既足够简单,让你快速上手;又足够开放,支持深度定制。随着你对它的了解加深,你会发现越来越多提升效率的方式。
真正的自由,来自于对工具的掌控。而 Eleventy,正赋予你这种自由。