- 前端
【免费下载链接】beautiful-web-type
In-depth guide to the best open-source typefaces: https://www.beautifulwebtype.com
Beautiful Web Type 是一个聚焦"高质量开源字体"的深度指南站点(本项目为它的开源实现),它不只列出字体,而是为每款字体提供完整的字重与风格目录、全量字形集、kerning 质量、OpenType 特性、推荐搭配(pairings)等维度的全面审视。本文将以仓库根目录的 README.md 为骨架,结合源码与内容目录,完整讲解站点的本地开发流程、内容创作规范(新增字体、新增搭配、共享文案),并深入剖析这些约定背后的数据加载与渲染实现,帮助你直接上手维护或扩展这样一个字体评测站点。
项目定位与内容模型
2018 年 10 月,Beautiful Web Type 发布了完全重构与重新设计的版本。新版本的目标是完整呈现每一款字体:收录所有可用的字重与风格(weights and styles)、完整字形集(glyph set)、kerning 质量、OpenType 特性、推荐搭配等。站方只收录达到高质量水准的字体,且所有入选字体均基于 SIL Open Font License 协议发布。早期版本被归档在v1/目录(v1/index.html),当前仓库中的 app 目录则是基于 Next.js 的全新实现。
从代码结构看,站点的核心数据流非常清晰:
- 内容层(content):以 YAML + HTML 片段的形式存放所有可编辑的站点内容,包括字体元数据、搭配、共享 specimen 文案。
- 加载层(lib/content/source.ts):构建时读取
content/目录,解析并强校验所有字段,生成类型化的内容对象。 - 查询层(lib/content/queries.ts):向页面组件提供按 slug、分类、名称等维度查询的 API。
- 页面层(app):通过 Next.js 静态生成(
generateStaticParams)产出/alegreya/、/alegreya/glyphs/等页面,路由定义见 lib/routes.ts。
其中lib/content/types.ts定义了核心 TypeScript 类型:Typeface(字体)、GlyphPage(字形页)、Pairing(搭配)、Sample(样例)、TextData(共享文案)、ContentGraph(整个内容图)。文章的后续章节将逐一对应这些类型展开。
本地开发:从安装到测试
README 给出了完整的本地开发命令,全部基于pnpm(packageManager指定为pnpm@10.10.0,Node 版本要求>=22.0.0 <23,见 package.json):
pnpm install # 安装依赖 pnpm dev # 启动 Next.js 开发服务器常用命令:
pnpm build # 生产构建(next build) pnpm test # 依次运行单元测试与端到端测试pnpm test实际上是两条命令的串联(见 package.json 中的test脚本):
pnpm test:unit # vitest run —— 运行 lib 层的单元测试 pnpm test:e2e # pnpm build && playwright test —— 先构建再跑 Playwright 端到端测试仓库还提供两条运维辅助脚本:
pnpm audit:typefaces:运行 scripts/audit-typefaces.ts,对每一款字体发起真实 HTTP 请求检查 Google Fonts 链接与项目主页的可达性,并用git ls-remote对照上游 GitHub 稳定 tag 检查是否已有更新版本,最终把结果写入 reports/typeface-audit.md。pnpm audit:security:使用osv-scanner基于pnpm-lock.yaml做依赖漏洞扫描。
单测集中在 tests/content-validation.test.ts 与 tests/glyph-inspector-font-loader.test.ts,前者直接验证内容加载的强校验逻辑(详见后文),后者验证字形检查器的字体加载管线。端到端测试位于 tests/e2e/site.spec.ts,覆盖关键页面路径。
内容创作总览
站点的所有可编辑内容都存放在content/目录,其结构为:
content/ ├── typefaces/<slug>/ # 每款字体一个目录 │ ├── meta.yml # 字体元数据 │ ├── detail.html # 详情页正文 HTML 片段 │ ├── sample.html # 标本源内容(specimen source) │ └── glyphs.yml # 字形检查器所需的字体文件路径 ├── pairings/<slug>/ # 每对字体搭配一个目录 │ ├── meta.yml │ └── sample.html └── site/text.yml # 共享 specimen 文案与复用文本数组这一布局与 lib/content/source.ts 中的loadTypefaceBundles、loadPairings、loadTextData三个加载函数一一对应:加载器会遍历content/typefaces/、content/pairings/下的子目录,并强制读取content/site/text.yml。
新增一款字体:四文件 Bundle
必需文件与目录规范
为站点添加一款新字体,需要创建content/typefaces/<slug>/目录并放入四个文件(README.md 的 "Add a typeface" 一节):
| 文件 | 作用 |
|---|---|
meta.yml | 字体元数据,驱动应用内的展示逻辑 |
detail.html | 详情页渲染的受信任 HTML 片段 |
sample.html | 保留的标本源内容 |
glyphs.yml | 字形检查器使用的字体文件路径 |
从 lib/content/source.ts 可以看到,这四个文件都是强制存在的:readYamlFile与readRequiredTextFile会在文件缺失时直接抛出Expected file to exist: <path>。这一点也被 tests/content-validation.test.ts 的 "requires all files in a typeface bundle" 用例覆盖。
meta.yml 字段详解
meta.yml承载字体元数据(README.md 与 lib/content/types.ts 共同定义),以下字段为必填:
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 字体显示名,非空字符串 |
dateAdded | string/Date | 收录日期,会被解析为YYYY-MM-DD(toDateString) |
category | string | 分类名,必须属于Display、Monospaced、Sans-Serif、Serif之一(assertCategory强校验,非法值直接抛错) |
styles | string/number | 风格数量(如Variable或数字2) |
weights | array | 字重列表,每项必须包含数值型weight与字符串name |
latestRelease | object | 最新发布信息,含version与date两个字符串字段 |
projectUrl | string | 项目主页(下载入口) |
creator | object | 设计师信息,含name与url |
description | string | 字体描述 |
以下字段为可选(maybeString/maybeBoolean处理,缺省时为undefined):
| 字段 | 类型 | 说明 |
|---|---|---|
gFontsUrl | string | Google Fonts 页面地址,存在时详情页头部会出现 "Embed using Google Fonts" 链接 |
italic | boolean | 是否提供斜体,影响详情页第二组头条样例的呈现方式(斜体/大写切换) |
smallcap | boolean | 是否提供小型大写(small caps),为 true 时详情页渲染 Smallcap headlines 区块 |
sampleText | string | 覆盖共享标本文案的个性化 specimen 文本 |
sampleClasses | string | 按字体定制的 Tailwind 覆盖类,作用于 specimen 舞台 |
familyFaces | string[] | 同族其他字体名,用于"Other faces in family"导航 |
comparisonFaces | string[] | 对比字体名列表,触发详情页底部的字型对比区块 |
updatedAt | string/Date | 更新时间(maybeDateString可选解析) |
以仓库实际文件 content/typefaces/inter/meta.yml 为例:
name: Inter dateAdded: '2020-11-21' category: Sans-Serif styles: Variable italic: true weights: - weight: 100 name: Thin - weight: 400 name: Regular - weight: 900 name: Black latestRelease: version: 'v4.1' date: 16 Nov 2024 projectUrl: https://rsms.me/inter/ gFontsUrl: https://fonts.google.com/specimen/Inter creator: name: Rasmus Andersson url: https://rsms.me/ description: Inter is a versatile typeface designed especially for screens... comparisonFaces: - Cooper Hewitt - Libre Franklin sampleText: 'Hahnenkammrennen' sampleClasses: 'font-[500]'注意:weights里的name会直接渲染在详情页"字重列表"中,weight数值则拼成w400之类的类名来控制实际字重,参见 components/typeface-detail.tsx。
sampleText 与 sampleClasses:标本定制
README 特别强调:列表卡片与详情页顶部标本默认使用 content/site/text.yml 中的共享 specimen 文案,只有当字体在meta.yml里设置了sampleText时才会覆盖。这一逻辑落在 components/sample-card.tsx:const sampleText = typeface.sampleText ?? specimen.primary;。
sampleClasses用于按字体对 specimen 舞台做 Tailwind 覆盖。README 给出的示例展示了响应式变体与任意属性的用法:
sampleText: "Sharp & Quiet" sampleClasses: "[--sample-fit-width:0.78] tracking-tight text-[#b42318] md:[--sample-fit-width:0.82]"这里--sample-fit-width是 specimen 文本的自适应缩放自定义属性(由 components/fitted-specimen-text.tsx 消费),md:前缀让不同断点下的缩放比例不同。
OpenType 特性:使用 Tailwind 任意属性
README 明确了一条重要约定:启用 OpenType 特性时,应使用 Tailwind arbitrary properties 而非font-features-*工具类。例如小型大写应写成:
sampleClasses: "[font-feature-settings:'smcp']"这与详情页中 smallcap 区块的实现一致——components/typeface-detail.tsx 会在smallcap: true时渲染font-feature-settings: 'smcp';的代码提示与样例组。
detail.html 与 sample.html 的角色
detail.html是渲染在字体详情页正文区域的受信任 HTML 片段,最终通过dangerouslySetInnerHTML注入到页面中(components/typeface-detail.tsx)。"受信任"意味着这些文件被视为站点作者自己维护的内容,因此 HTML 会被原样渲染。sample.html被保留为标本源内容,加载时读取进Sample.bodyHtml(lib/content/source.ts),供getSampleForTypeface等查询使用。当前展示层的 specimen 卡片主要依赖text.yml与sampleText覆盖,sample.html更多承担内容溯源的角色。
glyphs.yml:字形检查器的字体引用
glyphs.yml目前只需一个字段:
fontFile: /assets/fonts/example/example.woff该路径会被assertPublicAssetPath强校验:加载器会拼接content/public/或仓库根的public/目录,确认字体文件真实存在,否则抛出Expected "fontFile" asset to exist(lib/content/source.ts)。仓库真实示例见 content/typefaces/inter/glyphs.yml,对应的字体文件位于 public/assets/fonts/inter/Inter-roman.var.woff2。
该路径同时驱动字形检查器页面/<slug>/glyphs/:路由把fontFile传给 components/glyph-inspector.tsx,加载管线(lib/glyph-inspector-font-loader.ts)会在浏览器端按需加载opentype.min.js与wawoff2-decompress.js,将 WOFF2 解压后再用opentype.parse解析出全部字形,网格化渲染并可点击查看每个字形的 HTML 码、CSS 码与字形名。
新增一对字体搭配
创建content/pairings/<slug>/目录并放入两个文件:
| 文件 | 作用 |
|---|---|
meta.yml | 搭配元数据 |
sample.html | 渲染在搭配卡片上的受信任 HTML 片段 |
meta.yml必填字段为name、dateAdded、typefaces(字符串数组,引用已收录字体名),可选字段为sampleShade(仅允许light或dark,用于卡片底色明暗)。仓库中的完整示例 content/pairings/fraunces-and-libre-franklin/meta.yml:
name: Fraunces and Libre Franklin dateAdded: '2020-11-22' typefaces: - Fraunces - Libre Franklin加载逻辑在 lib/content/source.ts:sampleShade经过assertSampleShade强校验,传入neon这类非法值会在构建时直接报错(对应 tests/content-validation.test.ts 的 "rejects invalid pairing sampleShade values" 用例)。搭配卡片组件见 components/pairing-card.tsx,详情页通过getPairingsForTypeface(typeface.name)查询当前字体的推荐搭配并渲染(components/typeface-detail.tsx)。
共享站点文案:content/site/text.yml
content/site/text.yml 保存两类内容:
specimen.primary:共享标本主文案,例如当前仓库中的Signal & Shape;- 三个可复用数组:
words(单词)、headlines(头条标题)、paragraphs(段落),分别供详情页的 Words、Headlines、Paragraphs 三个展示区块使用。
从源码可以看到这些数组的实际消费方式(components/typeface-detail.tsx):
- Words 区块取
text.words前 6 条; - Headlines 区块按
weights数量逐条取text.headlines[index]; - Paragraphs 区块(仅非 Display 分类渲染)取
text.paragraphs[typeface.italic ? 0 : 1],并分别以ms2、ms1、ms0、ms-1、ms-2 col-2五种字号/栏宽组合展示。
这些 headline 文案精心选择了覆盖多语种、多字母形态的 pangram,用于充分暴露字体的字形与排版特性。加载函数loadTextData对四类字段都做非空字符串数组校验(lib/content/source.ts)。
数据加载与强校验:content 层的底层机制
所有内容最终通过 lib/content/graph.ts 的buildContentGraphFromContentDir汇总为ContentGraph,其结构(lib/content/types.ts)包括:
typefaces、typefaceBySlug、typefaceByName:字体全列表与两种索引;glyphPages、glyphPageBySlug、glyphPageByTypefaceName:字形页索引;samples、sampleByTypefaceName:样例索引;pairings、pairingsByTypefaceName:搭配列表与按字体名索引;latestTypefaces、latestPairings:最新收录的字体与搭配(首页使用);text:共享文案。
值得说明的是,这套加载器对内容质量有很高的强制力:必填文件缺失、分类非法、字段类型不符、字体资源不存在、日期无法解析等任何问题都会让构建直接失败,而不是静默跳过。这意味着"新增一款字体"的正确流程是:写好四件套 → 本地运行pnpm dev(或pnpm test:unit)验证 → 确认页面与字形检查器正常 → 提交。
页面层使用 Next.js App Router 的静态生成:app/[typefaceSlug]/page.tsx通过generateStaticParams枚举所有字体 slug 预渲染详情页,dynamicParams = false确保未知 slug 返回 404;app/[typefaceSlug]/glyphs/page.tsx同理生成字形检查器页。分类页(app/sans-serif/page.tsx等)由 components/category-page.tsx 渲染为SampleCard网格。
结论与内容维护建议
综合 README 与源码,Beautiful Web Type 的维护工作可以归纳为三条主流程:
- 新增字体:在
content/typefaces/<slug>/写齐meta.yml、detail.html、sample.html、glyphs.yml,其中meta.yml的必填字段与glyphs.yml的fontFile资源路径是构建成功的关键; - 新增搭配:在
content/pairings/<slug>/提供meta.yml(含name、dateAdded、typefaces)与sample.html; - 微调 specimen 与特性展示:通过
sampleText、sampleClasses(含[font-feature-settings:'smcp']这类 arbitrary properties)按字体定制,全局基线文案则维护在content/site/text.yml。
每次改动后运行pnpm test(单元 + 端到端)即可获得完整反馈;若要周期性核查各字体链接与上游版本,可运行pnpm audit:typefaces查看 reports/typeface-audit.md。这套"YAML 元数据 + HTML 片段 + 强校验加载器 + 静态生成"的内容管线,既保证了站点展示的丰富度,也通过构建期校验把内容错误暴露在发布之前,是同类字体指南站点一个值得参考的实现范式。
- 前端
【免费下载链接】beautiful-web-type
In-depth guide to the best open-source typefaces: https://www.beautifulwebtype.com
相关推荐
Luminus-template 数据库迁移指南:用 luminus-migrations 优雅管理 SQL 版本
Luminus template 数据库迁移指南:用 luminus migrations 优雅管理 SQL 版本 Luminus template 是 Clo
从文本到动作:MoMask如何用生成式掩码建模重塑3D人体动画创作
从文本到动作:MoMask如何用生成式掩码建模重塑3D人体动画创作 在数字内容创作领域,3D人体动作生成一直是一个充满挑战的课题。传统的动作捕捉技术成本高昂、流
人工智能深度学习计算机视觉媒体生成GetQzonehistory:一条命令导出历史说说,完成QQ空间数据恢复
GetQzonehistory:一条命令导出历史说说,完成QQ空间数据恢复 GetQzonehistory 是一个QQ空间数据恢复工具,批量导出历史说说成 Ex
网页爬虫数据分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考