- 前端
【免费下载链接】marked
A markdown parser and compiler. Built for speed.
导读:本文以 Marked 官方贡献指南(docs/CONTRIBUTING.md)为核心,系统讲解向这个以速度著称的 Markdown 解析器提交代码的完整工作流,包括仓库源码目录的职责划分、基于 SOLID 的设计原则、五级优先级标签体系、五大测试规格目录与 front-matter 配置技巧,以及全部 NPM 脚本的真实执行链路。读完本文,你将能够独立完成一次从 Fork、改码、测试到提交 Pull Request 的高质量贡献,并理解 Marked 内部测试引擎与构建管道的运作原理。
一、贡献前的准备:仓库结构与「为什么只改 src 不碰 lib」
Marked 的贡献流程(详见 docs/CONTRIBUTING.md)要求贡献者按以下步骤操作:
- Fork
markedjs/marked到自己的账号下; - 使用 GitHub Desktop 或命令行将仓库克隆到本地;
- 确保当前处于
master分支; - 运行
npm install或npm update安装依赖; - 创建一个独立的功能分支;
- 在
src文件夹中更新代码(lib文件夹是自动编译生成的代码,禁止手工修改); - 运行
npm test修复所有问题(对于 lint 问题,可运行npm run lint让 linter 自动修复); - 运行
npm run build:reset清除对编译产物的改动; - 提交 Pull Request。
其中第 6 步是理解整个仓库的关键。从 package.json 的脚本定义可以看到:
"build:reset": "rimraf ./lib ./public", "build:esbuild": "node esbuild.config.js", "build:types": "tsc && dts-bundle-generator --export-referenced-types --project tsconfig.json -o lib/marked.d.ts src/marked.ts",而 esbuild.config.js 明确写着:lib下的产物全部由./src/生成,构建横幅(banner)甚至直接声明了 "DO NOT EDIT THIS FILE / The code in this file is generated from files in ./src/"。因此贡献者只需专注于 src 目录,lib目录(esm、umd 与 d.ts 类型声明)会在构建时由 src/marked.ts 统一产出。npm run build:reset之所以出现在提交前的工作流中,正是为了避免把构建产物中的无关 diff 带进 Pull Request。
二、设计原则:SOLID 在 Marked 源码中的落地
贡献指南指出,Marked 倾向于遵循 SOLID 软件设计原则,尤其是其中的单一职责原则(Single Responsibility)与开闭原则(Open/Closed):
- 单一职责:Marked 以及它的各个组成部分,唯一的职责就是把 Markdown 字符串转换成 HTML;
- 开闭:Marked 更倾向于让开发者能够轻松地扩展库及其组件,而不是通过不断堆叠配置项来改变行为。
这两条原则可以从 src 目录的模块划分中直观印证:
| 模块文件 | 职责 |
|---|---|
| Lexer.ts | 词法分析,将 Markdown 源文本切分为 token 流,并持有全部编译规则Lexer.rules |
| Tokenizer.ts | 具体的 token 匹配逻辑,按块级/行内规则逐一识别 |
| Parser.ts | 语法解析,将 token 流渲染为 HTML 字符串 |
| Renderer.ts | 各 token 类型的 HTML 输出实现,是自定义渲染的首选扩展点 |
| TextRenderer.ts | 纯文本(无 HTML 标签)输出,用于摘要等场景 |
| Hooks.ts | 钩子机制,允许在不改动解析核心的前提下介入处理流程 |
| MarkedOptions.ts | 选项类型定义与解析 |
| defaults.ts | 默认配置项 |
| rules.ts | 正则规则的定义来源 |
| marked.ts | 入口与 API 封装,聚合以上模块 |
这种「解析器 / 渲染器 / 扩展点」分离的架构,正是开闭原则的体现:新增语法能力时,优先通过扩展(如自定义 Renderer、Hooks)实现,而不是在核心里塞入更多开关配置。贡献者在修改时也应遵循同样的取向,尽量让改动落在可扩展的边界内。
三、优先级标签体系:Issue 与 PR 的「工作量排序表」
贡献指南认为优先级已经为「构建质量」做好了排序,并用一组票证类型标签(Ticket type label)来标注 Issue 或 PR 的工作性质,按优先级从高到低排列:
| 票证类型标签 | 描述 |
|---|---|
| L0 - security | 在 Marked 库中发现安全漏洞 |
| L1 - broken | 合法用法得到与支持规范相比不正确的输出,或导致 marked 崩溃,且该问题没有已知的绕过方案 |
| L2 - annoying | 与 L1(broken)类似,但该问题存在已知的绕过方案 |
| RR - refactor and re-engineer | 能够给 Marked 的开发者(更好的可读性)或最终用户(更快的性能)或两者带来改进 |
| NFS - new feature (spec related) | Marked 目前不具备、但属于所支持规范之内的能力 |
| NFU - new feature (user requested) | Marked 目前不具备、但用户已经提出需求的能力 |
| NFE - new feature (should be an extension) | Marked 目前不具备、且不属于任何规范的能力(应作为扩展实现) |
这套标签直接指导贡献者判断自己提交的内容属于哪个等级、应该如何被对待。例如修复一个会崩溃的解析问题应标注 L1,而引入规范之外的全新语法则应标记为 NFE(因为它更适合做成扩展而非并入核心)。
四、测试体系:Test early, often, and everything
贡献指南强调:项目会为「验证输出」(依据支持规范编写测试)和「最小化回归」(为已修复的 issue 编写测试)两种目的编写用例。因此,了解测试装置(test harness)是参与贡献的前提。五大测试规格目录如下:
| 位置 | 描述 |
|---|---|
| test/specs/commonmark | 针对 CommonMark 规范的合规性测试 |
| test/specs/gfm | 针对 GFM(GitHub Flavored Markdown)规范的合规性测试 |
| test/specs/new | 与原始markdown.pl无关的测试 |
| test/specs/original | 对照原始markdown.pl的验证测试 |
| test/specs/redos | 针对 ReDoS(正则表达式拒绝服务)漏洞的测试 |
这五个目录的运行逻辑可以在 test/run-spec-tests.js 中找到源码级印证:测试引擎从这五个目录批量读取用例(getTests),并对不同目录施加不同的默认选项——CommonMark 目录使用{ gfm: false, pedantic: false },GFM 目录使用{ gfm: true, pedantic: false },original 目录使用{ gfm: false, pedantic: true },而 redos 目录使用{ silent: false }。也就是说,同一个 Markdown 用例在不同的规格目录下,会按该目录对应的方言选项被解析。
每个.md测试用例文件(如foo.md)都对应一个同名的.html期望输出文件(如foo.html)。如果你的测试需要指定选项——比如假设gfm被设置为false——可以在.md文件顶部添加 front-matter(YAML 头)来覆盖选项,例如:
--- gfm: false ---仓库中已有大量真实用例采用这种写法,例如 test/specs/new/nogfm_hashtag.md 同时声明了gfm: false与pedantic: true,以测试在非 GFM、pedantic 模式下#header是否被当作标题;test/specs/new/breaks.md 则声明breaks: true与gfm: true来验证换行行为。front-matter 中的键名直接对应 Marked 的选项(如gfm、pedantic、breaks等),运行时会被解析进 Marked 实例的配置中。
对于 redos 目录,除常规的.md/.html配对用例外,还存在以.cjs结尾的动态用例(见 test/specs/redos),例如 quadratic_underscores.cjs 以module.exports导出一个包含 101 个下划线字符的markdown输入及其期望html输出,用于在自动化检查中探测正则表达式的二次方/指数级回溯风险。此外,package.json 中还有专门的test:redos脚本(node test/recheck.ts > vuln.js),配合 recheck 依赖对规则进行 ReDoS 静态扫描。
五、提交 PR 与 Issue:模板与检查清单
Marked 为 Pull Request 和 Issue 都提供了提交模板。当你开始新建 PR 或 Issue 时,会看到使用模板的指引说明。PR 模板中同时包含提交者与审查者两套检查清单,在大多数情况下这两者并不是同一个人——提交者负责确认代码质量、测试与文档,审查者负责从维护者的视角复核正确性与合入条件。请务必逐项勾选并如实回答,这能显著加快审查流程。
六、NPM 脚本全解:每一条命令背后实际发生了什么
在 NPM 命令方面,Marked 尽量使用 NPM 框架自带的原生脚本。下面结合 package.json 的真实定义逐条拆解。
6.1 运行测试:npm test
npm test这条命令并非只跑一遍测试,而是build:reset→build:docs→test:specs→test:unit→test:umd→test:cjs→test:types→test:lint的完整流水线:先清空并重建文档,再依次运行规格测试(test/run-spec-tests.js)、单元测试(test/unit)、UMD 产物验证(test/umd-test.js)、CommonJS 产物验证(test/cjs-test.cjs)、类型声明验证(test/types)与 ESLint 校验。对于日常快速验证,可以使用npm run test:only(仅构建后跑 specs 与 unit),或用npm run test:specs:only/npm run test:unit:only单独运行某一类测试;npm run test:update则用于在确认行为正确后批量更新期望输出。
6.2 语法规范检查:npm run test:lint
npm run test:lint用于检测你是否使用了项目标准的语法规则(即 ESLint 规则集)。它对应eslint命令(不做自动修复),而npm run lint则对应eslint --fix,会自动修复可修复的格式问题——这正是贡献指南中「让 linter 帮你修复」的由来。
6.3 性能对比:npm run bench
npm run bench用于查看 Marked 与其他主流 Markdown 库之间的耗时对比。它在 package.json 中定义为npm run build && node test/bench.js,即先构建最新产物,再执行 test/bench.js 基准脚本。注意运行前提是本地已安装基准对比所需的依赖(如 commonmark、markdown-it 等 devDependencies)。
6.4 查看编译后的规则:npm run rules
npm run rules用于查看从src/rules.js编译出的全部正则规则。它的实现位于 test/rules.js:从Lexer.rules读取规则对象,将其中的正则toString()后按 JSON 格式打印。你也可以指定一个或多个「规则路径」来只看特定规则:
npm run rules -- block.gfm.item inline.pedantic.br { block: { gfm: { item: /^( *)((?:[*+-]|\d{1,9}\.)) ?[^\n]*(?:\n(?!\1(?:[*+-]|\d{1,9}\.) ?)[^\n]*)*/gm } }, inline: { pedantic: { br: /^( {2,}|\\)\n(?!\s*$)/ } } }点号分隔的路径对应规则对象的嵌套层级(如block.gfm.item即块级规则中 GFM 方言下的列表项规则),未指定的分支会被省略。该脚本对规则对象做了递归序列化,并把noopTest等无意义规则过滤为null/undefined,方便直观审阅某条规则的实际正则。
6.5 构建产物:npm run build
npm run build用于构建你自己的 es5、esm 和 minified 版本(注:构建目标与配置以当前仓库为准,实际产物为 esm 与 umd 两个 bundle 及类型声明)。它展开为三步:build:esbuild(node esbuild.config.js,以 src/marked.ts 为入口,产出 lib/marked.esm.js 与 lib/marked.umd.js,UMD 通过esbuild-plugin-umd-wrapper包装为全局marked)、build:types(tsc配合 dts-bundle-generator 生成 lib/marked.d.ts)、build:man(用marked-man从 man/marked.1.md 生成 man 手册)。构建横幅会注入当前版本号,并保留 MarkedJS 与原作者 Christopher Jeffrey 的 MIT 版权声明。
七、适用前提与限制说明
- Node 版本:根据 package.json 的
engines字段,本仓库要求Node >= 20,本地开发前请先确认环境满足要求; - 产物目录:
lib为构建生成目录,手工修改会在npm run build:reset或重新构建时被覆盖,请始终修改 src 下的 TypeScript 源码; - 测试期望更新:
npm run test:update会直接改写.html期望文件,仅应在确认新行为正确时使用,切勿用它掩盖未修复的回归。
遵循上述流程,你就能以与维护者一致的节奏完成一次干净的贡献:改src、跑测试、让 linter 自修、build:reset清理产物、最后提交带齐检查清单的 Pull Request。
- 前端
【免费下载链接】marked
A markdown parser and compiler. Built for speed.
相关推荐
ESPectre开发者指南:从代码结构到贡献流程全解析
ESPectre开发者指南:从代码结构到贡献流程全解析 ESPectre是一个基于Wi Fi频谱分析(CSI)的运动检测系统,具有原生的Home Assista
人工智能机器学习物联网嵌入式智能硬件边缘计算rainfrog开发指南:从源码构建到贡献代码全流程
rainfrog开发指南:从源码构建到贡献代码全流程 作为一款轻量级终端数据库管理工具(Terminal User Interface, TUI),rainfr
数据库CLI开发工具React/Vue项目集成指南:js-file-download在前端框架中的最佳实践
React/Vue项目集成指南:js file download在前端框架中的最佳实践 js file download 是一款轻量级JavaScript库,能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考