静态网站生成利器 Eleventy

什么是 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.jsonteam.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 的:
  • 将本地 JSON 数据迁移到 Google Sheets:让非技术人员也能轻松编辑内容,无需访问代码仓库。
  • 编写自定义短代码(Shortcodes):创建可复用的富媒体组件,比如嵌入视频、图表或引用框。

Eleventy 的强大之处在于,它既足够简单,让你快速上手;又足够开放,支持深度定制。随着你对它的了解加深,你会发现越来越多提升效率的方式。

真正的自由,来自于对工具的掌控。而 Eleventy,正赋予你这种自由。

参考

相关推荐
阿聪_7 小时前
React.ComponentType 类型使用
前端
aiwery7 小时前
理解 JavaScript 中的 Iterator、Generator、Promise 与 async/await
前端·面试
K仔7 小时前
什么是DOM事件模型
前端
熊猫片沃子7 小时前
新手必避的 Vue 基础坑:从数据绑定到事件处理的常见错误与解决方案
前端·vue.js
lichenyang4537 小时前
UniApp 实现搜索页逻辑详解
前端
怪可爱的地球人7 小时前
处理“文本搜索和替换” 的工具-RegExp
前端
公众号:重生之成为赛博女保安7 小时前
一款基于selenium的前端验证码绕过爆破工具
前端·selenium·测试工具
漫 漫,8 小时前
Vue2存量项目国际化改造踩坑
前端·javascript·vue.js
喜葵8 小时前
前端测试深度实践:从单元测试到E2E测试的完整测试解决方案
前端·单元测试