- 开发工具
- CLI
- 前端
【免费下载链接】metalsmith
An extremely simple, pluggable static site generator for Node.js
本指南以仓库中的 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 startnpm 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,可以看到循环被渲染为四条文章条目,摘要中的'等 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对照验证要点:
- Frontmatter 解析:
title: First Post、date: 2012-08-20原样进入文件对象并被模板引用; - markdown 渲染:正文 Markdown 段落变为
<p>...</p>; - excerpts 提取:首页列表展示每篇文章的第一段摘要;
- collections 过滤:
*[!index].html将index.html排除,列表只有 4 篇文章; - permalinks 重写:
first-post.html→first-post/index.html,path字段供链接使用; - 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
相关推荐
终极Nintendo Switch主题化HOME菜单:uLaunch完全指南
终极Nintendo Switch主题化HOME菜单:uLaunch完全指南 想要彻底改变你的Nintendo Switch主界面体验吗?uLaunch就是答案
使用 Devbox 搭建可复现的 Jekyll 博客开发环境(基于 examples/stacks/jekyll 模板)
使用 Devbox 搭建可复现的 Jekyll 博客开发环境(基于 examples/stacks/jekyll 模板) 本指南以本仓库 examples/st
开发工具CLIWeChatMsg:微信聊天记录导出的终极指南与数据永久保存方案
WeChatMsg:微信聊天记录导出的终极指南与数据永久保存方案 微信聊天记录导出已成为数字时代个人数据管理的重要需求,WeChatMsg作为一款开源微信备份工
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考