在上一篇文章中,我们学习了社区驱动型网站的规划内容。我们看到了开始接受用户提交需要考虑多少因素,并以我从构建 Style Stage 的经验中吸取的教训为例。
既然我们已经涵盖了规划,那么让我们开始编写代码!我们将一起开发一个 Eleventy 设置,您可以将其用作您自己的社区(或个人)网站的起点。
文章系列
- 为贡献做准备
- 网站构建 (您当前的位置!)
本文将涵盖
- 如何初始化 Eleventy 并创建有用的开发和构建脚本
- 推荐的设置自定义
- 如何定义自定义数据并组合多个数据源
- 使用 Nunjucks 和 Eleventy 布局链接创建布局
- 部署到 Netlify
愿景
假设我们希望让大家提交他们的猫和狗,并让他们在可爱度比赛中互相较量。

我们不会在这篇文章中讨论用户投票。这将非常酷(并且完全可以使用无服务器函数实现),但我们的重点是宠物提交本身。换句话说,用户可以提交他们猫和狗的个人资料详细信息。我们将使用这些提交来创建每周的战斗,在主页上随机将一只猫和一只狗进行对决,争夺谁是最完美(或者如果你愿意,也可以说是最棒)的宠物。
让我们启动 Eleventy
我们将从在您喜欢的任何目录中运行 npm init
初始化新项目开始,然后使用以下命令将 Eleventy 安装到其中
npm install @11ty/eleventy
虽然完全是可选的,但我喜欢打开添加到目录的 package-json
文件,并将 scripts
部分替换为以下内容
"scripts": {
"develop": "eleventy --serve",
"build": "eleventy"
},
这使我们能够在包含 Browsersync 热重载以进行本地开发的开发环境(npm run develop
)中开始开发 Eleventy。它还添加了一个命令来编译和构建我们的工作(npm run build
),以便在生产服务器上进行部署。
如果您在想,“npm 什么?”,我们正在调用 Node(这是 Eleventy 所需的)。这里提到的命令旨在在您首选的终端中运行,该终端可能是额外的程序或内置在您的代码编辑器中,例如在 VS Code 中。
我们还需要另一个 npm 包,fast-glob,稍后在组合数据时它会派上用场。我们现在可以安装它
npm install --save-dev fast-glob.
让我们配置我们的目录
Eleventy 允许自定义输入目录(我们工作的地方)和输出目录(我们的构建工作去的地方),以提供额外的组织。
要配置这一点,我们将在项目目录的根目录中创建 eleventy.js
文件。然后,我们将告诉 Eleventy 我们希望我们的输入和输出目录放在哪里。在本例中,我们将使用 src
目录作为输入,使用 public
目录作为输出。
module.exports = function (eleventyConfig) {
return {
dir: {
input: "src",
output: "public"
},
};
};
接下来,我们将创建一个名为 pets
的目录,用于存储我们从用户提交中获得的宠物数据。我们甚至可以进一步细分该目录,以减少合并冲突并通过 cat
和 dog
子目录清晰地区分猫数据和狗数据
pets/
cats/
dogs/
数据将是什么样的?用户将发送一个 JSON 文件,该文件遵循以下模式,其中每个属性都是关于宠物的数据点
{
"name": "",
"petColor": "",
"favoriteFood": "",
"favoriteToy": "",
"photoURL": "",
"ownerName": "",
"ownerTwitter": ""
}
为了使提交过程对用户来说非常清楚,我们可以在项目根目录中创建一个 CONTRIBUTING.md
文件,并写出提交指南。GitHub 会获取该文件中的内容并将其显示在存储库中。这样,我们可以提供有关此模式的指南,例如 favoriteFood
、favoriteToy
和 ownerTwitte
是可选字段的说明。
如果您愿意,也可以使用 README.md
文件。只是有一个专门用于贡献的标准文件会很好。
注意 photoURL
是这些属性之一。我们本可以将其设为文件,但为了安全和托管成本的考虑,我们将要求提供 URL 而不是文件。您可能会决定愿意接收实际文件,这完全没问题。
让我们处理数据
接下来,我们需要从各个猫文件和狗文件中创建一个组合的数据数组。这将使我们能够循环遍历它们来创建网站页面,并为每周的战斗选择随机的猫和狗提交。
Eleventy 允许在 _data
目录中使用 node module.exports
。这意味着我们可以创建一个函数来查找所有猫文件,另一个函数来查找所有狗文件,然后从每组文件中创建数组。这就像获取每个猫文件并将它们合并在一起以创建一个单个 JavaScript 文件中的数据集,然后对狗执行相同的操作。
在 _data
中使用的文件名将成为保存该数据集的变量,因此我们将在其中添加用于猫和狗的文件
_data/
cats.js
dogs.js
每个文件中的函数几乎相同 - 我们只是在两个函数之间交换“cat”和“dog”的实例。以下是猫的函数:
const fastglob = require("fast-glob");
const fs = require("fs");
module.exports = async () => {
// Create a "glob" of all cat json files
const catFiles = await fastglob("./src/pets/cats/*.json", {
caseSensitiveMatch: false,
});
// Loop through those files and add their content to our `cats` Set
let cats = new Set();
for (let cat of catFiles) {
const catData = JSON.parse(fs.readFileSync(cat));
cats.add(catData);
}
// Return the cats Set of objects within an array
return [...cats];
};
这看起来很吓人吗?不要害怕!我通常也不写 node,这对于不太复杂的 Eleventy 网站来说不是必需的步骤。如果我们选择让贡献者添加到一个不断增长的单个 JSON 文件中,其中包含 _data
,那么第一步就根本不需要。再说一次,这一步的主要原因是通过允许单独的贡献者文件来减少合并冲突。这也是我们添加 fast-glob 的原因。
让我们输出数据
现在是开始将数据插入到我们 UI 的模板中的好时机。实际上,您可以将一些 JSON 文件放到 pets/cats
和 pets/dogs
目录中,这些文件包含属性数据,这样我们就可以从一开始就拥有可以使用的内容来测试。
我们可以通过在 src 目录中添加一个 index.njk
文件来添加我们的第一个 Eleventy 页面。这将成为主页,并且是一个 Nunjucks 模板文件格式。
Nunjucks 是 Eleventy 用于创建模板的众多选项之一。 查看文档以获取完整的功能列表。
让我们从循环遍历我们的数据并输出一个无序列表开始,用于猫和狗
<ul>
<!-- Loop through cat data -->
{% for cat in cats %}
<li>
<a href="/cats/{{ cat.name | slug }}/">{{ cat.name }}</a>
</li>
{% endfor %}
</ul>
<ul>
<!-- Loop through dog data -->
{% for dog in dogs %}
<li>
<a href="/dogs/{{ dog.name | slug }}/">{{ dog.name }}</a>
</li>
{% endfor %}
</ul>
提醒一下,对 cats
和 dogs
的引用与 _data
中的文件名相匹配。在循环中,我们可以使用点表示法访问 JSON 密钥,如 cat.name
所示,它使用双花括号(例如 {{ cat.name }}
)作为 Nunjucks 模板变量输出。
让我们创建宠物个人资料页面
除了主页(index.njk
)上的猫和狗列表外,我们还想为每只宠物创建单独的个人资料页面。循环中暗示了我们将用于这些页面的结构,它将是 [宠物类型]/[名称-slug]
。
从数据创建页面的推荐方法是通过 Eleventy 的 分页 概念,它允许将数据分块。
我们将在 src
目录的根目录中创建负责分页的文件,但您也可以将它们嵌套在自定义目录中,只要它位于 src 中并且 Eleventy 可以找到它即可。
src/
cats.njk
dogs.njk
然后,我们将添加分页信息作为前置 matter,如猫所示
---
pagination:
data: cats
alias: cat
size: 1
permalink: "/cats/{{ cat.name | slug }}/"
---
data
值是 _data
中的文件名。alias
值是可选的,但用于引用分页数组中的一个项目。size: 1
表示我们每个数据项创建一个页面。
最后,为了成功创建页面输出,我们还需要指定期望的永久链接结构。这就是上面alias
值发挥作用的地方,它访问数据集中的name
键。然后我们使用一个名为slug
的内置过滤器,它将字符串值转换为 URL 友好的字符串(将字母小写并用连字符替换空格等)。
让我们回顾一下到目前为止我们所做的
现在是启动 Eleventy 的时候了,使用npm run develop
。这将启动本地服务器并在终端中显示一个 URL,你可以用它来查看项目。如果存在任何构建错误,它将在终端中显示错误。
只要一切顺利,Eleventy 将创建一个public
目录,其中应包含
public/
cats/
cat1-name/index.html
cat2-name/index.html
dogs/
dog1-name/index.html
dog2-name/index.html
index.html
在浏览器中,索引页面应该显示一个猫名字的链接列表和另一个狗名字的链接列表。
让我们将数据添加到宠物资料页面
目前,为猫和狗生成的每个页面都是空白的。我们有可以用来填充它们的数据,所以让我们开始使用它。
Eleventy 期望一个包含布局文件(“模板”)或包含在布局中的模板片段的_includes
目录。
我们将创建两个布局
src/
_includes/
base.njk
pets.njk
base.njk
的内容将是一个 HTML 模板。其中的<body>
元素将包含一个特殊的模板标签{{ content | safe }}
,传递给模板的内容将在其中渲染,其中“safe”表示它可以渲染任何传递进来的 HTML,而不是对它进行编码。
然后,我们可以通过添加以下内容作为前端数据,将主页index.md
分配为使用base.njk
布局。这应该是index.md
中的第一件事,包括破折号
---
layout: base.njk
---
如果你检查public
目录中编译后的 HTML,你会发现我们创建的猫和狗循环的输出现在位于base.njk
布局的<body>
中。
接下来,我们将添加相同的前端数据到pets.njk
中,以定义它也将使用base.njk
布局来利用 Eleventy 的布局链接概念。这样,我们放在pets.njk
中的内容将被base.njk
中的 HTML 模板包裹起来,所以我们不必每次都写出这些 HTML。
为了使用单个pets.njk
模板来渲染猫和狗的资料数据,我们将使用 Eleventy 的最新功能之一,称为计算数据。这将使我们能够将猫和狗数据的值分配给相同的模板变量,而不是使用if
语句或两个单独的模板(一个用于猫,一个用于狗)。同样,好处是避免冗余。
这是cats.njk
中需要更新的内容,dogs.njk
中也需要进行相同的更新(将cat
替换为dog
)
eleventyComputed:
title: "{{ cat.name }}"
petColor: "{{ cat.petColor }}"
favoriteFood: "{{ cat.favoriteFood }}"
favoriteToy: "{{ cat.favoriteToy }}"
photoURL: "{{ cat.photoURL }}"
ownerName: "{{ cat.ownerName }}"
ownerTwitter: "{{ cat.ownerTwitter }}"
请注意,eleventyComputed
定义了这个前端数据数组键,然后使用别名来访问cats
数据集中 的值。现在,例如,我们可以只使用{{ title }}
来访问猫的名字和狗的名字,因为模板变量现在是相同的。
我们可以先将以下代码放到pets.njk
中,以成功加载猫或狗的资料数据,具体取决于正在查看的页面
<img src="{{ photoURL }}" />
<ul>
<li><strong>Name</strong>: {{ title }}</li>
<li><strong>Color</strong>: {{ petColor }}</li>
<li><strong>Favorite Food</strong>: {{ favoriteFood if favoriteFood else 'N/A' }}</li>
<li><strong>Favorite Toy</strong>: {{ favoriteToy if favoriteToy else 'N/A' }}</li>
{% if ownerTwitter %}
<li><strong>Owner</strong>: <a href="{{ ownerTwitter }}">{{ ownerName }}</a></li>
{% else %}
<li><strong>Owner</strong>: {{ ownerName }}</li>
{% endif %}
</ul>
我们要做的最后一件事是将layout: pets.njk
添加到cats.njk
和dogs.njk
的前端数据中。
在 Eleventy 运行的情况下,你现在可以访问单个宠物页面并查看其资料

我们不会在这篇文章中讨论样式,但你可以前往示例项目仓库查看 CSS 是如何包含的。
让我们将它部署到生产环境中!
网站现在处于功能状态,可以部署到托管环境中!
正如之前推荐的那样,Netlify 是一个理想的选择,尤其是对于社区驱动的网站,因为它可以在每次提交合并时触发部署,并在提交发送以供审查之前提供提交的预览。
如果你选择 Netlify,你需要将你的网站推送到一个 GitHub 仓库,你可以在将网站添加到你的 Netlify 帐户的过程中选择它。我们将告诉 Netlify 从public
目录提供服务,并在将新的更改合并到主分支时run npm run build
。
示例网站包含一个netlify.toml
文件,其中包含构建详细信息,并且会自动被 Netlify 在仓库中检测到,从而无需在新网站流程中定义这些详细信息。
添加完初始网站后,访问 Netlify 的设置 → 构建 → 部署。在“部署上下文”下,选择“编辑”,并将“部署预览”的选择更新为“针对你的生产分支的任何拉取请求/分支部署分支”。现在,对于任何拉取请求,都会生成一个预览 URL,链接将直接在拉取请求审查屏幕中提供。
让我们开始接受提交!
在我们通过 Go 并获得 100 美元之前,最好重新访问第一篇文章,并确保我们已经准备好开始接受用户提交。例如,我们应该将社区健康文件添加到项目中(如果它们还没有被添加)。也许最重要的事情是确保为主分支设置了分支保护规则。这意味着,在拉取请求被合并之前,需要你的批准。
贡献者需要有一个 GitHub 帐户。虽然这似乎是一个障碍,但它减少了一些匿名性。根据内容的敏感性或目标受众,这实际上可以帮助审查(明白吗?)贡献者。
以下是提交流程
- 分叉网站仓库。
- 将分叉克隆到本地机器或使用 GitHub 网页界面完成剩余步骤。
- 在 src/pets/cats 或 src/pets/dogs 中创建一个包含所需数据的唯一 .json 文件。
- 如果是在克隆上进行的更改,则提交更改,或者如果是在网页界面中进行的编辑,则保存文件。
- 打开一个拉取请求回到主仓库。
- (可选)审查 Netlify 部署预览以验证信息是否按预期显示。
- 合并更改。
- Netlify 将新宠物部署到实时网站。
常见问题解答部分是一个很好的地方,可以通知贡献者如何创建拉取请求。你可以查看 Style Stage 上的一个示例。
让我们总结一下...
我们拥有一个完全功能的网站,它接受用户提交作为对项目仓库的提交。它甚至在提交被合并时自动为我们部署这些提交!
我们可以用 Eleventy 构建的社区驱动的网站做更多的事情。例如
- Markdown 文件可以用于通过 Buttondown 发送的电子邮件通讯的内容。Eleventy 允许将 Markdown 与 Nunjucks 或 Liquid 混在一起。因此,例如,你可以添加一个 Nunjucks for 循环来输出最新的五只宠物作为链接,这些链接以 Markdown 语法输出,并被 Buttondown 接收。
- 自动生成的社交媒体预览图像可以用于社交网络链接预览。
- 可以添加一个评论系统。
- Netlify CMS 开放创作可以用来让人们通过界面进行提交。查看 Chris 关于它工作原理的精彩概述。
我的Meow vs. BowWow 示例可供你在 GitHub 上分叉。你也可以查看实时预览,是的,你真的可以将你的宠物提交到这个愚蠢的网站。🙂
祝你创建健康而繁荣的社区!
文章系列
- 准备接受贡献
- 网站构建 (您当前的位置!)