解剖 freeCodeCamp 课程挑战定义文件:以首个 HTML 挑战为样本的前置元数据、五段式正文与校验链路
freeCodeCamp 的课程(curriculum)以大量 Markdown 挑战文件为核心资产,curriculum/fixtures/combined.md 正是其中最典型的样本——它是「Say Hello to HTML Elements」(向 HTML Elements 说你好)这一 HTML 入门挑战的合并版夹具文件。本文以该文件为主体,逐字段解析其前置元数据(Front Matter)与五段式正文结构,并结合 curriculum 包的源码与测试,说明这类挑战文件如何被校验、如何区分英文源与翻译合并版,读完你可以完整读懂乃至亲手编写一个合法的课程挑战文件。
样本文件在 curriculum 包中的位置:测试夹具约定
curriculum/__fixtures__/ 目录是 curriculum 包测试用的夹具(fixture)根目录,其布局本身就携带了一条重要约定:顶层的 combined*.md 文件是翻译/合并后的挑战版本,english/ 子目录中存放对应的英文源文件。当前目录结构如下:
- 顶层合并版:combined.md、combined-html-comments.md、combined-js-comments.md、combined-jsx-comments.md
- 英文源:english/challenge.md 及 english 目录下其余文件
- 翻译词典夹具:
__fixtures__/dictionaries/与__fixtures__/incomplete-dicts/(含 chinese / english / spanish 三种语言的 comments 词典)
这条约定直接对应 get-challenges.ts 中的 hasEnglishSource 函数:它把英文源根目录解析为 <basePath>/english,再检查目标挑战文件是否存在,返回布尔值。对应的测试 get-challenges.test.js 以 basePath = '../__fixtures__' 验证了两种情形:challenge.md 存在时返回 true,no/challenge.md 缺失时返回 false。也就是说,顶层 combined.md(中文版)与 english/challenge.md(英文版)构成了「非英文挑战文件 + 其英文源」的标准配对样本,供翻译管线相关逻辑做测试。
前置元数据(Front Matter)逐字段解析
combined.md 的 Front Matter 完整内容如下,这是每个挑战文件的身份声明区:
---
id: bd7123c8c441eddfaeb5bdef
originalTitle: Say Hello to HTML Elements
challengeType: 0
videoUrl: 'https://scrimba.com/p/pVMPUv/cE8Gpt2'
forumTopicId: 18276
title: 向HTML Elements说你好
---
各字段含义与约束如下:
| 字段 | 取值 | 说明 |
|---|---|---|
id |
bd7123c8c441eddfaeb5bdef |
挑战的唯一标识(24 位十六进制)。中文版与英文版共用同一个 id,表示二者是同一挑战的不同语言版本 |
originalTitle |
Say Hello to HTML Elements |
英文原标题。该字段仅出现在非英文(翻译/合并)版本中,英文源文件里没有它 |
challengeType |
0 |
挑战类型编号,0 表示 HTML 挑战 |
videoUrl |
Scrimba 视频地址 | 挑战配套视频,可选字段,允许为空字符串 |
forumTopicId |
18276 |
帮助论坛对应主题帖的编号,类型为数字 |
title |
向HTML Elements说你好 |
面向学习者显示的标题,这里已是中文 |
challengeType: 0 这一取值不是随意约定的,它在共享包 challenge-types.ts 中被定义为 const html = 0;,并且由此派生出两条关键行为:
- 视图类型:
viewTypes表中[html]: 'classic',即 HTML 挑战使用经典编辑器视图渲染; - 提交类型:
submitTypes表中[html]: 'tests',即完成提交前必须通过该挑战声明的全部测试。
此外,hasNoSolution(0) 返回 false——HTML 类型不在「无解答」列表中,从源码结构看这正对应了挑战文件中存在的 ## Solution 小节:这类挑战携带参考解答。
这些字段的硬性约束由 challenge-schema.js 中的 Joi 校验表把关,其中与本样本直接相关的条目包括:
id: Joi.objectId().required()——id必须是合法的 ObjectId 格式,这与文件中 24 位十六进制取值的形态一致;challengeType: Joi.number().min(0).max(33).required()——类型编号必须是 0 到 33 的整数;title: Joi.string().required()——标题必填;tests: Joi.array().items(...).required()——公开挑战的每条测试必须是{ id?, text(必填), testString }结构;videoUrl: Joi.string().allow('')、forumTopicId: Joi.number()——分别对应上表中的可选与数字字段。
校验入口为同文件导出的 challengeSchemaValidator,它对单个挑战对象执行 schema.validate(challenge)。
五段式正文:从 Description 到 Solution
Front Matter 之后是固定骨架的五个小节:## Description、## Instructions、## Tests、## Challenge Seed、## Solution,每节用带 id 的 <section> 标签包裹。以下按原文件顺序逐节拆解。
Description:HTML 元素与开闭标签
Description 小节面向学习者讲解本章第一个概念,原文核心内容如下:
欢迎来到 freeCodeCamp 的 HTML 编码挑战。这些挑战将逐步引导您完成 Web 开发。首先,您将使用 HTML 构建一个简单的网页。您可以在嵌入到此网页中的代码编辑器(code editor)编辑代码。您是否在代码编辑器中看到了
<h1>Hello</h1>?这是一个 HTML 元素(element)。大多数 HTML 元素都有一个开始标记(opening tag)和结束标记(closing tag):开始标记形如<h1>,结束标记形如</h1>。两者唯一的区别是结束标记左括号后面的那个正斜杠。每个挑战都有可以随时单击「运行测试」按钮运行的测试;当您通过所有测试时,系统会提示您提交解决方案并进入下一个编码挑战。
注意原文中 <h1>、<code> 等尖括号内容是以 HTML 实体(< / >)转义后写在 <code> 标签里的——挑战正文允许内联 HTML,而尖括号必须转义才能安全通过解析。对比英文源 english/challenge.md,其 Description 使用的是 <h1> 这类十进制实体转义;两个文件转义风格不同但语义等价,从源码结构看这是翻译/合并环节产物与英文原件在文本细节上的正常差异。
Instructions:明确可执行的唯一指令
要通过此挑战的测试,请将
h1元素的文本更改为 "Hello World"。
Instructions 小节给出学习者需要做的唯一修改:把 <h1> 里的文本从 Hello 改成 Hello World。它与 Tests 小节的断言是一一对应的——这是该课程「指令-测试」配对的典型写法。
Tests:断言即文档
Tests 小节以 YAML 代码块声明测试数组,combined.md 原文为:
tests:
- text: 你的<code>h1</code>元素应该有“Hello World”文本。
testString: assert.isTrue((/hello(\s)+world/gi).test($('h1').text()));
这条 testString 值得逐段拆解:
$('h1'):测试运行时提供 jQuery 风格的$选择器,选中渲染结果里的h1元素;.text()取其纯文本内容;/hello(\s)+world/gi:正则表达式要求文本中hello与world之间有一个或多个空白字符(\s+),g为全局匹配、i为忽略大小写,因此Hello World、HELLO world都能通过,而HelloWorld(无空格)会失败;assert.isTrue(...):测试断言 API,断言正则匹配结果为真,断言失败时展示text字段作为提示文案。
text 字段是写给学习者看的中文提示,testString 是机器执行的断言。二者构成 { text, testString } 结构,正好匹配前文 Joi 校验中对公开挑战测试的定义。
Challenge Seed:练习的起点代码
<h1>Hello</h1>
Challenge Seed 小节被 <div id='html-seed'> 包裹,其代码块是编辑器中预置的起始代码:一个写着 Hello 的 h1 元素。学习者需要在此种子代码基础上完成 Instructions 要求的修改。
Solution:参考解答
<h1>Hello World</h1>
Solution 小节给出通过测试的参考解答——仅将文本补全为 Hello World。由于 challengeType: 0 属于「有解答」类型(见上文 hasNoSolution 分析),这个小节在样本中是齐备的。
英文版与合并版的差异:翻译管线留下的痕迹
将 combined.md 与英文源 english/challenge.md 并排对照,可以得到一份清晰的「合并版改造清单」:
| 维度 | 英文源 english/challenge.md |
合并版 combined.md |
|---|---|---|
id / videoUrl |
bd7123c8c441eddfaeb5bdef / 同一 Scrimba 地址 |
完全相同(同一挑战的身份与配套视频不随语言变化) |
originalTitle |
无 | 有,记录英文原标题 |
title |
Say Hello to HTML Elements |
向HTML Elements说你好 |
forumTopicId |
12345 |
18276 |
Description / Instructions / Tests text / Seed / Solution |
英文 | 中文(代码本身保持英文,如 <h1>Hello</h1> 不变) |
| 尖括号转义 | <h1>(十进制实体) |
<h1>(命名实体) |
testString 与代码种子在两版中保持字节级一致——测试逻辑与代码不随语言翻译,只有 text 提示与叙述性正文被替换,这正是「合并版」文件的核心特征:英文代码骨架 + 翻译后的说明文字 + 记录来处的 originalTitle。
姊妹夹具 combined-html-comments.md 还展示了合并版可能携带的另一类内容:其 Challenge Seed 中出现了 <!-- (Chinese) Add your code below this line (Chinese) --> 这类中文专属代码注释标记,说明合并环节会把语言特定的种子代码注释一并合入。
从夹具到课程:构建与校验链路
理解了单个文件之后,再看 curriculum 包如何消费这类文件:
- 语言校验与构建入口:get-challenges.ts 的
getChallengesForLang(lang, filters)先校验语言是否在availableLangs.curriculum中(非法语言直接抛出 "langis not an accepted language" 错误),再调用buildCurriculum构建整份课程。get-challenges.test.js 以getChallengesForLang('notlang')验证了该抛错分支。 - 英文源存在性检查:
hasEnglishSource(同文件)解析<basePath>/english根目录并做文件系统访问检查,是翻译流程判断「是否已有英文原文可对齐」的依据,夹具中的english/子目录即为此服务的测试数据。 - 翻译词典:
build-curriculum提供的createCommentMap用于从词典目录生成「英文注释 → 各语言译文」的映射;其测试 build-curriculum.test.js 正是加载__fixtures__/dictionaries(完整词典)与__fixtures__/incomplete-dicts(残缺词典)验证两种行为——词典齐全时返回各语言译文,词条缺失时回退为未翻译的原文('To be translated two'回退为自身)。这解释了夹具中为什么同时备有两套词典数据。 - 结构校验:任何挑战文件在构建产物中都要通过 challenge-schema.js 的 Joi 校验(
id、challengeType0–33、title、tests数组等硬性要求),challenge-schema.test.mjs 及其快照文件对校验器行为做了回归测试。
小结
combined.md 虽然只是一个小型夹具,却完整呈现了 freeCodeCamp 课程挑战定义文件的三要素:以 id 为跨语言锚点的前置元数据、指令与断言一一配对的五段式正文、以及「英文源 + 合并版 + 翻译词典」三层夹具约定的翻译管线数据面。掌握这套格式后,你可以对照 challenge-schema.js 的 Joi 约束自行书写挑战文件,并用 get-challenges.test.js、build-curriculum.test.js 等测试了解每个环节被验证的具体行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00