☰
用 Metalsmith 复刻 Jekyll 静态博客:examples/jekyll 完整实战指南
2026/10/14 3:05:19 网站建设 项目流程
  • 开发工具
  • CLI
  • 前端

【免费下载链接】metalsmith

An extremely simple, pluggable static site generator for Node.js

项目地址:https://gitcode.com/gh_mirrors/me/metalsmith
点击查看免费下载

本指南以仓库中的 examples/jekyll 示例为蓝本,带你用 Metalsmith 的插件化流水线完整复刻一个 Jekyll 风格静态博客:从 Frontmatter 驱动的文章、Swig 布局模板,到集合(collections)、摘要(excerpts)与永久链接(permalinks)的生成,再到本地构建与浏览器预览。读完本文,你将掌握metalsmith.json声明式配置的核心字段与插件编排顺序,并能直接复制这套示例跑出一个可用的博客站点。

一、示例概览:用插件模拟 Jekyll

Metalsmith 本身只是一个"极简、可插拔"的构建核心(仓库根目录package.json中的描述为An extremely simple, pluggable static site generator),所有功能都通过插件堆栈完成。examples/jekyll正是这一理念的体现:它不依赖 Jekyll(Ruby 生态),而是用一组 Metalsmith 官方与社区插件,模拟出 Jekyll 博客的典型工作流——文章目录、YAML Frontmatter、站点元数据、模板布局、集合列表、永久链接。

该示例的目录结构如下:

examples/jekyll/ ├── _layouts/ # Swig 布局模板(index.swig 首页、post.swig 文章页) ├── _posts/ # 源内容:4 篇博客文章 + 1 个首页 index.md ├── _site/ # 构建产物(已提交,可直接查看生成效果) ├── metalsmith.json # 声明式构建配置(CLI 入口) ├── package.json # 依赖与 npm scripts └── README.md # 官方给出的运行说明

二、三步跑起来:安装、构建、预览

examples/jekyll/README.md给出的运行方式极简,共两条命令:

npm install npm start
  • npm install安装 package.json 中声明的依赖,核心包括metalsmith(^2.5.0)以及@metalsmith/drafts、@metalsmith/markdown、@metalsmith/permalinks、@metalsmith/collections、@metalsmith/excerpts、@metalsmith/layouts等插件,外加模板转换器jstransformer-swig;
  • npm start实际执行"start": "metalsmith",即调用 Metalsmith 官方 CLI(bin/metalsmith)。CLI 会在当前目录查找metalsmith.json,解析其中的 source、destination、metadata 与 plugins,并依次执行构建,将结果写入_site目录。

构建完成后用浏览器预览:

npm run serve

该命令对应"serve": "serve _site",借助serve包(^13.0.2,devDependencies)在当前_site目录起一个本地静态服务器,默认地址为http://localhost:3000。

_site目录中的产物(index.html、first-post/index.html 等)已随仓库提交,你可以直接对比源码与产物来验证每一步插件的效果。

三、metalsmith.json 逐项拆解:声明式构建配置

整个示例的灵魂是 metalsmith.json,它用纯 JSON 声明了源目录、目标目录、全局元数据与插件流水线:

{ "source": "./_posts", "destination": "./_site", "metadata": { "site_title": "My Jekyll-Powered Blog", "site_description": "My second, super-cool, Jekyll-powered blog." }, "plugins": [ { "@metalsmith/drafts": {} }, { "@metalsmith/markdown": {} }, { "metalsmith-excerpts": true }, { "metalsmith-collections": { "posts": { "pattern": "*[!index].html" } } }, { "@metalsmith/permalinks": { "pattern": ":title" } }, { "metalsmith-layouts": { "directory": "_layouts" } } ] }

1. source 与 destination

  • source指向源内容目录./_posts,Metalsmith 会递归读取其中的所有文件,并解析 YAML Frontmatter;
  • destination指向构建产物目录./_site。

在 lib/index.js 中,构造函数默认值为source('src')与destination('build'),本示例通过配置覆盖为 Jekyll 风格的_posts/_site命名。构建时若clean为 true(默认开启,见 lib/index.js),会先清空目标目录再写入,保证产物干净。

2. metadata:全站元数据

metadata中的site_title与site_description会成为全局元数据,在布局模板中直接以{{ site_title }}、{{ site_description }}引用(见下文布局节)。这与 Jekyll 的_config.yml中站点级配置的定位一致。

3. 插件流水线(按顺序执行)

Metalsmith 的插件按数组顺序串联执行,每个插件接收上一步产出的files对象(键为文件路径,值为文件元数据与内容),这正是"流水线"式静态站点生成器的核心机制(lib/index.js 通过ware中间件依次运行插件栈)。本例中的插件顺序是经过设计的:

①@metalsmith/drafts:过滤草稿

{ "@metalsmith/drafts": {} }

作用是从文件集合中剔除带有draft标记的文章。其底层逻辑极其简单——仓库中 examples/drafts-plugin/index.js 展示了一个最小实现:遍历files,凡是files[file].draft为真值就删除该文件。官方@metalsmith/drafts插件在此基础上还支持drafts元数据集合等配置,但核心行为一致。

②@metalsmith/markdown:Markdown 转 HTML

{ "@metalsmith/markdown": {} }

将.md文件的正文(contents)渲染为 HTML。默认会将.md输出为.html文件,这也是后续 collections 匹配*.html的前提。

③metalsmith-excerpts:提取文章摘要

{ "metalsmith-excerpts": true }

为每篇文章生成excerpt字段(通常取正文第一段),供首页列表展示。注意它必须放在markdown之后,因为摘要要从渲染后的 HTML 中提取。true表示使用默认行为;也可以传对象配置分隔符。仓库测试 test/index.js 验证了通过excerpt_separator(如~~~)自定义摘要边界的能力;此外,核心库本身也支持在读取阶段用frontmatter({ excerpt: true })配合 gray-matter 直接提取 Frontmatter 摘要(见 lib/index.js 与 test/index.js)。

④metalsmith-collections:按模式收集文章集合

{ "metalsmith-collections": { "posts": { "pattern": "*[!index].html" } } }

将符合模式的 HTML 文件收集为posts集合,供首页模板循环渲染。这里的模式*[!index].html是一个否定字符类 glob:匹配任意非index.html的 HTML 文件,从而把首页文件本身排除在文章列表之外。仓库核心库对 glob 匹配有完整支持——Metalsmith#match基于micromatch(lib/index.js),且测试明确验证了"否定与或模式"的匹配行为(test/index.js,例如!index.md会匹配除该文件外的其余文件)。

⑤@metalsmith/permalinks:生成永久链接

{ "@metalsmith/permalinks": { "pattern": ":title" } }

将文章由first-post.html重写为first-post/index.html(默认还会带上index.html尾部),并给文件补充path字段供模板生成链接。这里的:title即文章标题的 slug 化形式。产物 first-post/index.html 与首页中的href="first-post"正是该插件的工作结果。

⑥metalsmith-layouts:套用布局模板

{ "metalsmith-layouts": { "directory": "_layouts" } }

指定布局目录为_layouts。每个源文件通过 Frontmatter 中的layout字段(如layout: post.swig)选择模板,模板引擎由jstransformer-swig提供(见 package.json 的jstransformer-swig依赖),输出最终 HTML 到_site。

4. 关于插件命名的一个注意点

细心的读者会发现:metalsmith.json中部分插件使用非作用域命名(metalsmith-excerpts、metalsmith-collections、metalsmith-layouts),而 package.json 中安装的对应依赖却是@metalsmith/*作用域包(@metalsmith/excerpts、@metalsmith/collections、@metalsmith/layouts)。这与 Metalsmith 生态的迁移历史有关:仓库根目录的 metalsmith-migrated-plugins.js 记录了旧核心插件整体迁往@metalsmith组织的过程(markdown、layouts、collections、drafts、excerpts、permalinks等均在迁移清单中),并会在安装时打印metalsmith-xxx -> @metalsmith/xxx的迁移提示。编写新配置时,推荐统一使用@metalsmith/*作用域命名,与 package.json 中的依赖保持完全一致。

四、源内容:Frontmatter 驱动文章与首页

_posts目录下的每篇文章都是一个带 YAML Frontmatter 的 Markdown 文件,例如 first-post.md:

--- title: First Post date: 2012-08-20 layout: post.swig --- An interesting post about how it's going to be different this time around. I'm going write a lot more nowadays and use this blog to improve my writing.
  • title:文章标题,注入模板的{{ title }};
  • date:文章日期,模板中用{{ date | date('Y-m-d') }}格式化输出;
  • layout:指定布局文件,这里是post.swig。

其余三篇文章(second-post.md、third-post.md、fourth-post.md)结构完全一致,仅内容不同——这也是"内容与表现分离"的典型 Jekyll 组织方式。

首页则是一个只有 Frontmatter、没有正文的文件 index.md:

--- layout: index.swig ---

它仅声明使用index.swig布局,正文为空。由于配置中 source 为_posts,首页与文章放在同一目录下;collections 的*[!index].html模式恰好将其排除在文章列表外。

Frontmatter 的解析由核心库在读取阶段完成:Metalsmith 默认开启 frontmatter 解析(frontmatter(true)),lib/index.js 中每个文件在读取时会先经matter.parse解析 YAML 头,将title、date、layout等键合并进文件对象,再把剩余正文作为contents(Buffer)保留。若 Frontmatter 非法,会抛出invalid_frontmatter错误(仓库 test/fixtures/read-invalid-frontmatter 提供对应测试用例)。

五、布局模板:Swig 语法驱动的双模板体系

1. 首页模板 index.swig

examples/jekyll/_layouts/index.swig 负责渲染文章列表页:

<html> <head> <title>{{ site_title }}</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="{{ site_description }}"> <meta name="generator" content="metalsmith"> </head> <body> <h1>{{ site_title | safe }}</h1> <p>{{ site_description | safe }}</p> <hr> <h2>Posts</h2> {% for post in posts %} <h3><a href="{{ post.path }}">{{ post.title | safe }}</a></h3> <p>{{ post.excerpt | striptags | safe }}</p> <hr> {% endfor %} </body> </html>

关键点:

  • {{ site_title }}、{{ site_description }}来自metalsmith.json的metadata;
  • {% for post in posts %}遍历 collections 插件生成的posts集合;
  • {{ post.path }}是 permalinks 插件为每篇文章生成的访问路径(如first-post);
  • {{ post.excerpt | striptags | safe }}展示 excerpts 插件提取的摘要,先striptags去掉 HTML 标签再安全输出。

对照产物 examples/jekyll/_site/index.html,可以看到循环被渲染为四条文章条目,摘要中的&apos;等 HTML 实体是striptags过滤后的正常表现。

2. 文章页模板 post.swig

examples/jekyll/_layouts/post.swig 负责渲染单篇文章:

<html> <head> <title>{{ title }} | {{ site_title }}</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="{{ site_description }}"> <meta name="generator" content="metalsmith"> </head> <body> <h1>{{ title }}</h1> <time>{{ date | date('Y-m-d') }}</time> {{ contents | safe }} </body> </html>
  • {{ title }}、{{ date | date('Y-m-d') }}来自文章 Frontmatter;
  • {{ contents | safe }}输出 markdown 插件渲染后的正文(此时contents已是 HTML 字符串)。

对照产物 examples/jekyll/_site/first-post/index.html:<title>First Post | My Jekyll-Powered Blog</title>、<time>2012-08-20</time>,正文<p>...</p>均为渲染结果,与模板完全对应。

需要说明的是,metalsmith-layouts的directory: "_layouts"指定了布局目录,每个文件通过自身 Frontmatter 的layout字段选择模板。仓库中其他示例展示了不同模板引擎的等价用法,例如 examples/static-site/index.js 以编程 API 使用 Handlebars 布局并注入formattedDate辅助函数;而本示例选择 Swig,主要通过jstransformer-swig转换器接入。

六、产物验证:从 Frontmatter 到静态 HTML 的完整链路

构建完成后,_site中的目录结构与源内容一一对应(Markdown 文件被渲染为 HTML、permalinks 重写为目录形式):

_site/ ├── index.html # 首页(文章列表) ├── first-post/index.html # 单篇文章(永久链接形式) ├── second-post/index.html ├── third-post/index.html └── fourth-post/index.html

对照验证要点:

  1. Frontmatter 解析:title: First Post、date: 2012-08-20原样进入文件对象并被模板引用;
  2. markdown 渲染:正文 Markdown 段落变为<p>...</p>;
  3. excerpts 提取:首页列表展示每篇文章的第一段摘要;
  4. collections 过滤:*[!index].html将index.html排除,列表只有 4 篇文章;
  5. permalinks 重写:first-post.html→first-post/index.html,path字段供链接使用;
  6. layouts 套版:layout: post.swig/layout: index.swig决定最终 HTML 骨架。

整条链路的执行顺序由 bin/metalsmith 的resolveConfig保证:CLI 读取metalsmith.json后,将plugins数组中的每个条目通过metalsmith.imports(name)动态加载模块,再调用plugin(options)注册进实例,最终metalsmith.build()按序执行(bin/metalsmith)。构建过程在 lib/index.js 的build方法中完成:读取源目录 → 依次运行插件 → 清空并写入目标目录。

七、扩展:从示例到自己的博客

基于本示例,你可以从三个方向扩展:

  • 新增文章:在_posts下添加带 Frontmatter(title、date、layout: post.swig)的 Markdown 文件,重新npm start即自动出现在列表与永久链接中;
  • 调整模板:修改 index.swig 与 post.swig,或新增布局文件并在 Frontmatter 中切换;
  • 切换配置方式:本示例使用声明式metalsmith.json(CLI 默认读取);仓库 examples/static-site/index.js 展示了等价的编程式 API——Metalsmith(__dirname).metadata(...).source(...).destination(...).use(...).build(...),两种方式可互相转换。

另外,CLI 还提供了若干调试选项(bin/metalsmith):--config <path>指定配置文件、--env KEY=VALUE覆盖环境变量、--debug <namespace>开启调试日志、--dry-run只处理不写出,以及metalsmith init、metalsmith clean等辅助命令,均可在自己的博客项目中按需使用。


综上,examples/jekyll用不到 30 行的 JSON 配置串联起六个插件,完整复刻了 Jekyll 博客的核心体验。它既是理解 Metalsmith 插件流水线的最佳入门样例,也是一份可以直接复制、运行、扩展的博客脚手架——这正是"极简、可插拔"设计哲学的最佳注脚。

  • 开发工具
  • CLI
  • 前端

【免费下载链接】metalsmith

An extremely simple, pluggable static site generator for Node.js

项目地址:https://gitcode.com/gh_mirrors/me/metalsmith
点击查看免费下载
上一篇:OpenFGA Check 实现深度解析:关系重写、图遍历与并发控制的完整指南
下一篇:抖音视频无水印下载三步跑通:单条、主页、合集批量备份

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询