highlight.js 贡献指南:从语言语法到核心解析引擎的开源参与完整实战
【免费下载链接】highlight.jsJavaScript syntax highlighter with language auto-detection and zero dependencies.项目地址: https://gitcode.com/gh_mirrors/hi/highlight.js
导读
CONTRIBUTING.md 是 highlight.js 项目面向所有潜在贡献者的官方入门文档,它定义了项目的设计哲学、功能与语言请求流程、Bug 报告规范、Pull Request 工作流以及 AI 辅助贡献政策。本篇指南以该文档为主线,结合当前仓库中的 package.json、tools/build.js、test/index.js 与 docs/ai-contributions.md 等源码与文档,逐层展开讲解:非程序员如何参与社区、如何正确提出新语言支持、如何搭建开发环境并完成一次合格的构建与测试,以及如何遵循项目规范提交被维护者认可的 PR。读完本文,你将掌握参与 highlight.js(包括但不限于语言语法贡献、核心解析引擎修复、文档完善)所需的完整实操路径。
欢迎与项目哲学:贡献者先要理解的事
highlight.js 是一个零依赖的 JavaScript 语法高亮引擎,其核心引擎致力于保持"小巧、简单、易用"——目标只是覆盖语法高亮的"happy path"(常见路径),而把边缘情况交给插件体系处理。这一点直接决定了贡献者的工作边界,具体体现为三条设计原则:
- 拥抱插件与扩展,而非堆砌小特性:核心不会为了每个边缘需求新增配置旋钮,而是鼓励通过插件机制扩展能力。一个典型案例就是行号功能——官方文档 docs/line-numbers.rst 明确指出"缺少行号被视为特性",需要行号的场景应交给插件或第三方扩展解决。
- 尽力理解局部上下文,但绝不追求成为完整的语言解析器:highlight.js 做得比"纯关键字着色"更多,会尝试理解局部上下文(如区分字符串、注释、正则字面量),但不会去实现完整的编译原理级语法分析。这意味着贡献语法时应当"够用即可",不必追求面面俱到。
- 语言自动检测"不是魔法":自动检测是尽力而为(best effort)的实现,项目方对此有清醒认识。如果你认为自己能改进自动检测的相关性评分(relevance)算法,那将是非常有价值的贡献方向。
不编程也能贡献
项目明确强调:"你不必是程序员"。贡献路径是多元的,包括但不限于:
- 在社区频道中帮助解答用户问题;
- 报告新 Bug 或参与已有 issue 的讨论;
- 提交解决具体 issue 的 Pull Request;
- 编写扩展核心能力的插件(可参考 docs/plugin-api.rst 与 docs/plugin-recipes.rst);
- 编写第三方语言语法定义以提升语言支持(可参考 docs/language-guide.rst 与 docs/language-contribution.rst);
- 设计新主题(可参考 docs/theme-guide.rst,仓库中的 src/styles 目录即存放全部现成主题);
- 改进文档,让说明更清晰易懂。
各项贡献所需的前提技能
| 贡献方向 | 前提技能要求 |
|---|---|
| 解答 issue 或社区提问 | 只需友善、乐于助人 |
| 语言语法定义 | 通常需要熟悉正则表达式(语法规则的核心载体) |
| 核心解析引擎 | 需要掌握JavaScript |
| 文档工作 | 愿意且擅长把事情写清楚 |
| 语法细节审校 | 对某门被支持语言的专家级知识会非常有帮助 |
仓库中约 200 个语言定义都存放在 src/languages/ 目录下,例如 src/languages/javascript.js、src/languages/python.js。如果你对其中某门语言有专家级认知,审校与修复这些语法文件就是天然的切入点。
功能请求:先讨论归属,再动手实现
功能请求(Feature Request)永远受欢迎,但项目方有一条明确的引导:如果该功能不属于核心库,项目乐于建议你以插件方式开发。因此在动手实现之前,正确姿势是先开一个 issue,讨论该功能应当进入核心还是做成插件。这样做的好处是:
- 能获得实现思路的提示;
- 可以链接到历史上关于该主题的讨论,获得更多上下文;
- 避免实现方向与维护者预期不符而白费功夫。
语言请求:直接请求"支持某语言"通常没有意义
这是新手最容易踩的坑。项目方的立场非常明确(详见 docs/language-requests.rst):
核心团队通常不亲自开发新语言,他们的精力集中在解析器开发、Bug 修复和现有语言的支持上,也没有时间评审、合并和维护额外语言的语法。项目鼓励贡献者自己开发并维护第三方语言语法——无论这门语言多么冷门,项目都乐意在 highlightjs 组织下托管,或者由贡献者自己托管并提供链接。
因此,只提交"请支持语言 Xyz"的 issue 会被直接关闭并附上上述说明链接。如果你希望某门语言被支持,最佳路径是自己动手编写语法,或找到愿意开发的开发者。相关指引见 docs/language-guide.rst(语法开发)与 docs/language-contribution.rst(第三方语言模块打包规范)。
报告问题:高质量 issue 的正确打开方式
发现 Bug 或想到改进点时,可以打开一个新 issue 提交。项目对报告语言高亮问题给出了非常实用的建议:用可复现的最小测试用例来呈现问题,这样维护者与贡献者都能快速定位。
一个高质量的 Bug 报告应包含:
- 清晰的标题,指明受影响的语言或模块;
- 触发问题的输入代码片段;
- 当前的高亮输出(错误行为);
- 期望的高亮输出(正确行为);
- 可复现的最小化示例。
提交 PR:从 fork 到 merge 的完整工作流
如果你具备 前提技能,可以从带有 "good first issue" 标签的入门级 issue 开始,也可以直接参与更复杂 issue 的讨论。对于 GitHub 协作流程不熟悉的开发者,项目建议先了解通用的 fork 协作模型,然后按以下步骤操作:
- 在 GitHub 上 fork 本项目;
- 克隆到本地:
git clone git@github.com:username/highlight.js.git(在当前镜像仓库语境下,可等效使用git clone https://gitcode.com/gh_mirrors/hi/highlight.js获取源码副本用于本地开发与测试); - 创建工作分支:
git checkout -b my-branch; - 提交改动:
git commit -m 'my changes'; - 执行构建与测试;
- 推送分支:
git push origin my-branch; - 从你的 fork 向本仓库打开 Pull Request。
开 PR 前的"Keep in Mind"清单
项目方给出了三条重要的代码纪律:
- 先开 issue 再写代码:请在你提交 PR 之前先开一个新 issue(或加入已有 issue 的讨论),让主题先被探索和讨论。这是对双方时间的尊重——你的时间宝贵,维护者的更宝贵。
- 通常应附带 markup 测试:当你做了显著的语法改动或修复 Bug 时,应当添加对应的 markup 测试;唯一例外是仅添加
keywords这类纯关键字清单的改动。 - 改动最小化:只改需要改的部分;修复小 Bug 时不要顺手重新 lint 或重写整个文件。Lint 或大规模重构必须用独立的 commit提交,与功能改动分开。
构建与测试体系:贡献者的"体检关卡"
highlight.js 的构建测试体系可以从 package.json 的 scripts 字段一窥全貌:
"scripts": { "mocha": "mocha", "lint": "eslint src/*.js src/lib/*.js demo/*.js tools/**/*.js --ignore-pattern vendor", "lint-languages": "eslint --no-eslintrc -c .eslintrc.lang.js src/languages/**/*.js", "build_and_test": "npm run build && npm run test", "build_and_test_browser": "npm run build-browser && npm run test-browser", "build": "node ./tools/build.js -t node", "build-cdn": "node ./tools/build.js -t cdn", "build-browser": "node ./tools/build.js -t browser :common", "test": "mocha test", "test-markup": "mocha test/markup", "test-detect": "mocha test/detect", "test-browser": "mocha test/browser", "test-parser": "mocha test/parser" }可以看到构建与测试被明确区分为Node.js 构建和浏览器构建两条链路(详见 docs/building-testing.rst)。
最小化验证:只跑 Node.js 链路
贡献 PR 时,只要你的改动不涉及浏览器专属特性,通常只需构建并测试 Node.js 构建即可——CI 会保证浏览器构建依然通过:
npm run build npm run test浏览器库需要单独构建与测试:
npm run build-browser npm run test-browser也可以使用组合命令一次完成:npm run build_and_test与npm run build_and_test_browser(对应 package.json)。
环境要求:当前仓库 package.json 声明"node": ">=20.0.0",即需要 Node.js 20 及以上版本;首次开发前需执行npm install安装依赖。对于 Debian 系系统(如 Ubuntu),如果 node 二进制名为nodejs,可能需要创建别名或软链接指向node,因为测试依赖中引用了 "node"。
构建工具与常用参数
构建工具位于 tools/build.js,它基于commander解析命令行参数(见 tools/build.js),支持的构建目标为all、browser、cdn、node四种(默认browser)。核心用法:
# 仅用常用语言构建浏览器版本 node tools/build.js :common # 为 Node.js 构建包含全部语言的版本 node tools/build.js -t node # 调试用:仅构建 python 和 ruby 两个语言,且不做压缩 node tools/build.js -n python ruby # 构建全部目标(cdn/browser/node),输出到 build/ 下各自子目录 node tools/build.js -t all常用选项说明:
-t, --target <name>:指定构建目标all | browser | cdn | node;-n, --no-minify:禁用压缩(调试语言语法时尤其有用,便于阅读浏览器报错信息);--no-esm:禁用 ESM 构建;<language...>:位置参数,指定要打包的语言名或语言类别(如:common),不指定则按目标默认包含全部语言。
各目标的行为差异(源码注释见 tools/build.js):
| 目标 | 产物与用途 |
|---|---|
| browser | 默认目标。将核心与全部语言打包为highlight.js,默认同时生成压缩版(除非传入--no-minify) |
| cdn | 打包为highlight.min.js,并把全部语言与样式拆分为独立文件,供 cdnjs、jsdelivr 等 CDN 使用;此目标忽略--no-minify |
| node | 转换为 CommonJS 模块,生成供 Node.js 或 browserify 导入的index.js,默认包含全部语言(可能偏重,可通过指定语言列表瘦身);这也是发布到 npm 的构建 |
| all | 构建全部目标,各自输出到build/下以目标命名的子目录 |
所有构建结果都会输出到build/目录。
测试矩阵:五类测试各管一摊
测试统一使用 Mocha(入口见 test/index.js),npm test会按顺序加载以下测试套件:
test/api/:针对hljs对象暴露的 API 进行测试,例如highlight、getLanguage、registerAlias、数字与二进制数字解析、beginKeywords等;test/parser/:核心解析引擎的回归测试,覆盖beginEndScope、reuseEndsWithParent、compiler-extensions、命名分组反向引用、最大关键字命中数等历史 Bug 修复场景;test/detect/:语言自动检测(highlightAuto)的测试;test/markup/:各语言 HTML 渲染标记测试,防止已修复的高亮错误复发;test/regex/:正则的致命问题检查(如指数级回溯 backtracking);test/special/:仅在浏览器场景生效的测试,借助jsdom在 Node 中模拟浏览器,检查已有自定义标记、禁用高亮的代码块等行为。
这也是 docs/building-testing.rst 中"必要时还要单独跑npm run test-markup、npm run test-detect、npm run test-parser"的实践来源。
可视化调试:tools/developer.html
开发语言定义时,最高效的调试方式是可视化调试。你需要先用目标语言单独构建(不压缩),然后打开开发者工具页tools/developer.html(需先执行npm run build-browser生成浏览器构建),在其中:
- 将测试片段粘贴到编辑区;
- 在 Language 下拉框中显式选择你的语言(自动检测在此场景下往往不可靠);
- 点击 "Update highlighting" 查看渲染结果,并可切换主题、切换 "Show/hide structure" 查看标记结构。
该工具还支持 "visible-structure" 视图,用带data-klass属性的 span 直观展示每个 token 的类别。测试片段应当短小精悍,能体现该语言的整体观感即可,不必覆盖每一种语法元素,甚至不必具有实际语义。
自动化验证:detect 与 markup 测试
当你对可视化结果满意后,需要确保你的语法定义不会破坏整个语言套件的自动检测:
- 将调试用的片段保存到
test/detect/<language>/default.txt(如 test/detect 下各语言子目录所示); - 用全部语言构建 Node 版本并运行测试套件;
- 如果检测被破坏,需要通过改进**相关性评分(relevance)**来修复——这是文档中特别提到的"black art"(一门玄学),拿不准时应回到讨论组求助。
对于隔离的语法构造测试(例如某语言有 19 种字符串字面量,或需要复杂启发式区分除法/与正则/.../),应提供 markup 测试。一个 markup 测试用例由一对文件组成:
test/markup/<language>/<test_name>.txt:测试代码;test/markup/<language>/<test_name>.expect.txt:期望渲染结果。
仓库中现成的例子,例如 test/markup/c/atomic-types.txt 与对应的atomic-types.expect.txt。期望渲染结果可通过tools/developer.html生成:显式选择语言后渲染,将输出保存为.expect.txt。
对于尚未支持(或作为未来工作跟踪)的边缘情况,测试套件不允许携带失败的测试。正确做法是使用.skip后缀提交这对文件:
test/markup/<language>/<test_name>.skip.txttest/markup/<language>/<test_name>.skip.expect.txt
这些用例会被 Mocha 注册为跳过(skipped)的测试:它们出现在报告中但不会使 CI 失败。这比直接省略该用例更好,因为缺口保持可见;添加 skip 用例时应在 PR 中链接跟踪 issue,或在附近注释说明。
用 Docker 构建与预览(可选)
如果你不想在宿主机安装依赖,可以使用仓库根目录的 Dockerfile 构建一个容器:
docker build -t highlight-js . docker run -d --name highlight-js --rm -p 80:80 highlight-js然后打开http://127.0.0.1/tools/developer.html即可预览开发者页面(端口可通过-p 80:8080等映射方式调整;也可以不绑定端口,直接交互式进入容器作为开发环境)。进阶用法是绑定源码目录并热更新:
docker run -d --name highlight-js --volume $PWD/src:/var/www/html/src --rm -p 80:80 highlight-js docker exec highlight-js node tools/build.js :common改完代码后在容器内重新构建、刷新页面即可看到效果;完成后用docker stop highlight-js清理容器。完整的构建与测试细节可参考 docs/building-testing.rst。
AI 辅助贡献政策:工具是工具,作者是你
随着 AI 编程工具普及,项目方在 docs/ai-contributions.md 中明确了完整的 AI 辅助贡献政策,态度可以概括为:欢迎使用,但你必须仍然是作者。CONTRIBUTING.md 中的核心要求是:提交前务必自行审查,理解你的改动,不要把未经检查的机器输出直接丢给维护者。
政策的关键条款包括:
- 人在回路(Human in the loop):在请求维护者评审前,必须阅读并审查工具产出的全部内容;不得提交未经检查的机器生成的 PR、issue 或评审意见。
- 你拥有这项改动:你必须足够理解所提交的内容,能够在评审中解释并回答问题;如果你无法为某一行代码辩护,就不要提交它。
- 拒绝"垃圾输出"(No slop):未经验证、低质量、批量流水线式输出会浪费稀缺的维护者评审时间,不是可接受的贡献。优先做小而聚焦、评审价值大于评审成本的改动。
- 与任何 PR 同等的标准:测试、风格、范围以及常规贡献指南仍然适用,工具不会降低门槛。
- 鼓励透明声明:如果贡献在很大程度上借助了工具,建议在 PR 描述或 commit trailer 中说明,例如:
Assisted-by: <model> (<effort>)例如Assisted-by: Claude Sonnet 4 (high)、Assisted-by: Copilot;未知时只写工具名也可以。
明确不允许的行为包括:未经人工逐个批准就自动开/更新 PR 或 issue 的无人值守机器人;用 AI 端到端"代劳"完成 good first issue 而不学习代码库——这些 issue 存在的意义就是让人成长,完全自动化就失去了意义。此外,贡献者需自行确保有权在项目许可(BSD-3-Clause,见 LICENSE)下贡献相关材料:用工具重新生成受版权保护的材料并不会使其可自由再授权。
维护者处理违规时按梯度执行:先请求修改(附简短说明与政策链接);若 PR 明显偏离轨道则关闭;同一账号二次明显违规则升级到仓库管理员,限制其继续开 PR 的能力。
结语:从阅读这份指南到提交第一个 PR
综合来看,参与 highlight.js 的路径清晰而务实:先理解"核心保持小巧、边缘交给插件"的哲学,再按规范提出功能或语言请求,用最小化且带测试的改动提交 PR,并遵守 AI 辅助贡献的透明与责任要求。对于语言语法贡献者,本指南与 docs/language-guide.rst、docs/language-contribution.rst 构成了从入门到打包发布的完整链路;对于核心引擎贡献者,docs/mode-reference.rst 与 src/lib/ 下的解析器源码(如 src/lib/mode_compiler.js、src/lib/compile_keywords.js)则值得进一步研读。无论选择哪条路,一句建议贯穿始终:改动前先讨论,改动后先自测,提交前先自审。
【免费下载链接】highlight.jsJavaScript syntax highlighter with language auto-detection and zero dependencies.项目地址: https://gitcode.com/gh_mirrors/hi/highlight.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考