Nueyaml 语法完全参考:为可预测性而设计的精简 YAML 解析器
【免费下载链接】nueFastest way to build modern websites项目地址: https://gitcode.com/GitHub_Trending/nu/nue
Nueyaml 是 Nue 生态中"去掉问题的 YAML"——它砍掉了标准 YAML 规范中 80 页容易引发歧义的特性,只保留配置场景真正需要的类型与结构。本文是 yaml-syntax.md 的完整参考解读,结合 nueyaml.js 的源码实现与 nueyaml.test.js 的测试用例,系统讲解 Nueyaml 的文件结构、六大数据类型、多行字符串、属性名、缩进、注释、错误处理与全部限制。读完本文,你将能零歧义地编写和排查 Nueyaml 配置文件,并理解它为何能作为 Nue Kit 站点配置(site.yaml)与 Nuemark 文档元数据的底层解析引擎。
Nueyaml 的设计哲学:一个规则,可预测
标准 YAML 的一个核心痛点是"它猜测你的意思,而且经常猜错"(详见 nueyaml.md):
# 标准 YAML 的意外 country: NO # 可能变成 false("挪威问题") time: 12:30 # 可能变成 750(分钟数) version: 1.10 # 可能变成 1.1(浮点精度) port: 08080 # 可能变成 4176(八进制)Nueyaml 只有一条规则:对普通人来说看起来像字符串的,就是字符串。只有一眼就能看出的数字(123、45.67)才是数字,只有true和false才是布尔值,其余一律保持字符串。这一点在源码 nueyaml.js 的parseValue中体现得淋漓尽致:isNumber使用严格的正则/^-?\d+(\.\d+)?$/判断,且显式拒绝以0开头的值(str[0] == '0'直接返回 false),从而从根上消除了前导零被当作八进制解析的隐患。
文件结构
所有 Nueyaml 文件必须以根级对象开头。解析器永远返回一个对象,绝不返回数组或原始值(源码 nueyaml.js 中parseYAML最终由buildObject构建结果,天然保证返回值是对象):
# 合法 - 根对象 name: My App version: 1.0.0 # 非法 - 根数组 \- item1 \- item2文件统一使用UTF-8编码。
数据类型
Nueyaml 只支持六种类型:字符串、数字、布尔、日期、Null、数组与对象——"没有二进制数据、没有集合、没有有序映射、没有自定义类型、没有挪威问题",这正是 nueyaml.md 中"简单类型系统"一节的承诺。
字符串
默认一切都是字符串,无需引号,除非需要强制一个看起来像其他类型的值:
name: John Doe title: Senior Developer message: Hello world country: NO # 字符串 "NO",不是 false time: 12:30 # 字符串 "12:30",不是 750 分钟带引号的字符串会自动解包(为兼容 YAML 习惯):
message: "Hello world" # 变成: Hello world note: a "quoted" word # 变成: a "quoted" word force: "123" # 保持字符串 "123",不是数字源码中parseValue的引号处理逻辑(nueyaml.js)会先判断值是否以"或'开头和结尾,若是则去掉首尾引号返回内部内容;测试 nueyaml.test.js 覆盖了双引号、单引号和"中间夹引号"三种情形。
数字
只有整数和小数会被解析为数字,且"必须看起来像人眼可识别的数字":
age: 30 # 整数 price: 19.99 # 小数 count: 0 # 零 negative: -42 # 负整数不支持的写法(全部退化为字符串):
scientific: 1.2e3 # 字符串 "1.2e3" octal: 0o755 # 字符串 "0o755" hex: 0xFF # 字符串 "0xFF" underscore: 1_000 # 字符串 "1_000"isNumber的实现(nueyaml.js)同时排除了空字符串、单独的-/+以及任何以0开头的值;测试 nueyaml.test.js 验证了00800、hello、空串和-都不是数字。
布尔
只有true和false会被解析为布尔值,其余全是字符串:
enabled: true visible: false active: yes # 字符串 "yes" on: ON # 字符串 "ON"parseValue中(nueyaml.js)对true/false的匹配是大小写敏感的精确字符串比较,这正是它拒绝yes、YES、on、True等 YAML 替代布尔字面量的原因。
日期
仅支持单一 ISO 格式:YYYY-MM-DD或YYYY-MM-DDTHH:MM:SSZ:
created: 2024-01-15 updated: 2024-01-15T10:30:00Z其他格式一律成为字符串:
us_date: 01/15/2024 # 字符串 euro_date: 15.01.2024 # 字符串 text_date: Jan 15, 2024 # 字符串日期判定由正则/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}Z)?$/完成,匹配后返回new Date(val)(nueyaml.js);测试 nueyaml.test.js 确认两种格式都解析为对应的Date对象。
Null
空值变为 null:
description: # null avatar: # nullparseValue的第一行就是if (val == '') return null(nueyaml.js),而在buildObject中,一个没有子级的空键值对同样会落到value = null分支(nueyaml.js),这保证了空键、空值两种写法结果一致。
数组
同时支持内联和块两种语法:
# 内联数组 tags: [frontend, javascript, react] numbers: [1, 2, 3] mixed: [hello, 42, true] # 块数组 tags: - frontend - javascript - react # 嵌套数组 matrix: - [1, 2, 3] - [4, 5, 6] - [7, 8, 9]内联数组由parseYAMLArray处理(nueyaml.js):它要求整行匹配/^\s*\w+\s*:\s*\[(.*)\]$/,即"键名 + 冒号 + 方括号"的形式,然后对逗号分隔的每一项递归调用parseValue,因此[hello, 42, true]会得到['hello', 42, true]这样的混合类型数组。测试还专门验证了"看起来像数组但不是数组"的情况(nueyaml.test.js):如hey [foo](没有冒号)、hey: "[foo]"(被引号包裹)都会安全地返回 null 而非误解析。
对象
仅支持块语法,不支持内联对象:
# 合法 - 块对象 user: name: John Doe age: 30 active: true # 非法 - 内联对象 user: {name: John, age: 30} # 变成字符串对象层级完全由缩进驱动:buildObject(nueyaml.js)在遇到空值的键值对时向前看,把所有缩进大于当前层的块收拢为子级,再根据子级首块的类型决定构建数组、多行字符串还是嵌套对象。测试 nueyaml.test.js 验证了"对象数组"这一最复杂的结构:users下每个- name:项连同缩进更深的age: 30被正确组装为{ name: 'John', age: 30 }。
多行字符串
当值在下一行以更大缩进继续时,它就变成多行字符串:
description: This is a multi-line string that preserves line breaks exactly as written. # 结果: "This is a multi-line\nstring that preserves\nline breaks exactly\nas written."多行字符串完整保留:
- 换行
- 相对首行的缩进
- 每行的尾部空白
- 内容中的空行
code_example: function hello() { console.log('Hello') } hello() # 保留空行与缩进实现上,detectStructure会把"无冒号、非数组项"的缩进行标记为multiline块(nueyaml.js),buildObject则将连续的 multiline 子块用\n连接(nueyaml.js)。集成测试 nueyaml.test.js 确认description:\n Multi-line\n string content解析为'Multi-line\nstring content'。注意一个细节:连接时各行已经按stripComments后的内容处理,且每行以trim()后的值参与拼接,所以"相对首行的缩进"以每行自身内容为基准。
属性名
属性名可以包含任意字符,包括冒号和特殊字符。解析器以:(冒号后跟空格)作为键值分隔符:
# 键中的特殊字符 /api/users/:id: getUserHandler /api/posts: getPostsHandler @media (max-width: 768px): mobile-styles users[0].name: John api:key: secret-value with spaces: allowed唯一的硬性要求是存在:分隔符:
key:value # 非法 - 冒号后没有空格 key: value # 合法 key : value # 合法 - 多余空格可以 complex:key: value # 合法 - 键名中的冒号Nueyaml 不支持带引号的属性名:
"quoted key": value # 不支持 '单引号键': value # 不支持detectStructure中的键值对拆分逻辑(nueyaml.js)会优先查找': '(冒号+空格),找不到才回退到第一个裸冒号,因此/api/users/:id: getUserHandler能被正确地切分为键/api/users/:id和值getUserHandler。测试 nueyaml.test.js 与集成测试(nueyaml.test.js)都覆盖了这类复杂键。Nue Kit 正是利用这一特性,在 svg.js 等处直接以路径、媒体查询作为配置键。
缩进与空白
缩进只能用空格,不允许使用 Tab:
# 合法 - 一致的 2 空格缩进 app: name: Test config: debug: true # 合法 - 一致的 4 空格缩进 app: name: Test config: debug: true # 非法 - 混用缩进 app: name: Test config: mixed # 错误: 不是 2 的倍数解析器从第一个缩进行检测缩进大小,并强制全文保持一致的倍数。源码validateIndentation(nueyaml.js)分两步执行:先扫描每行行首空白,若含 Tab 立即抛出Tabs not allowed for indentation. Use spaces only. Line N;再由detectIndentSize从首个非空、非注释的缩进行得到基准缩进(默认为 2),收集所有缩进级别并逐一验证是否为基准值的整数倍,否则抛出Inconsistent indentation. Expected multiples of N spaces.。测试 nueyaml.test.js 同时验证了 Tab 缩进报错、字符串值内 Tab 合法、以及 3 空格对 2 空格基准不匹配三种情形。
空白规则汇总:
- 尾部空格被忽略
- 空行允许出现在任何位置
- 字符串值内允许出现 Tab
- Tab 不能用于结构性缩进
注释
使用#写注释。注释必须由空白引导,以避免与包含#的值冲突:
# 整行注释 name: John Doe # 行内注释 password: secret#123 # password 中的 # 不是注释 api_key: sk-1234#abcd # 对密钥类值安全多行注释需要每行一个#。
注释判定规则:
valid # comment # # 前有空格,是注释 no#comment # 无空格,# 属于值 also#not#comment # 无空格的多重 # 全部保留stripComments的实现(nueyaml.js)正是这一规则的直接体现:整行以#开头则返回空串;否则用正则/\s#/查找"空白 + #"的第一个位置,从其索引处截断。测试 nueyaml.test.js 覆盖了无注释、行内注释、整行注释、值内#四种情况,而集成测试 nueyaml.test.js 进一步确认password: secret#123#abc整体保留。
错误处理
解析器提供带行号的清晰错误信息:
# 错误示例: user: name: John age: 30 # 错误: Line 3: 缩进不一致 data: - item1 item2 # 错误: Line 3: 期望数组项指示符 (-)需要说明的是:validateIndentation抛出的 Tab 错误会带行号(Line ${i + 1}),缩进倍数错误会给出期望的空格倍数;而"期望数组项指示符"这类错误在实际实现中表现为:detectStructure遇到item2(无-前缀、无:分隔符)时将其归类为multiline块,当它出现在数组上下文中时会被多行字符串逻辑接管——这正是文档建议在编写时保持数组项格式一致的用意。测试 nueyaml.test.js 用parseYAML的集成断言验证了 Tab 与缩进不一致两类错误的抛出。
限制:不支持的 YAML 特性
标准 YAML 中不支持的部分
- 锚点与引用(
&anchor、*reference) - 合并键(
<<: *defaults) - 标签(
!!str、!!timestamp) - 多文档(
---、...) - 跨多行的流式集合
- 块标量指示符(
|、>、|-、>+) - 转义序列(
\n、\t、\u0041) - 替代布尔值(
yes、no、on、off、YES、True) - 多种日期格式
- 二进制数据
- 集合与有序映射
- 指令(
%YAML、%TAG)
有意的限制
- 根必须是对象(不能是数组或标量)
- 不支持
{}内联对象 - 不支持带引号的属性名
- 不支持科学计数法
- 不支持八进制或十六进制数字
- Tab 仅用于内容(不可用于缩进)
- 仅支持单一日期格式
这些限制不是缺陷,而是"可预测性"的代价与保障。正如 nueyaml.md 的 FAQ 所述:Nueyaml 是合法的 YAML,但并非所有 YAML 都是合法的 Nueyaml——它保留有用子集、拒绝危险部分;而现实中绝大多数配置文件本就不使用锚点、标签和多日期格式,因此现有 YAML 文件通常已经天然兼容 Nueyaml。
完整示例
以下示例覆盖了上述全部特性,可直接作为配置文件模板:
# 应用配置 app: name: My Application version: 1.2.0 debug: false launched: 2024-01-15 # 数据库设置 database: host: localhost port: 5432 credentials: username: admin password: # null,留给环境变量 connection_string: postgresql://admin@localhost:5432/myapp ?ssl=true&timeout=10 # API 路由 - 键中的特殊字符 /api/users/:id: getUserHandler /api/posts: getPostsHandler /api/auth/login: loginHandler # 安全 api_keys: github: ghp_xxxxxxxxxxxxxxxxxxxx stripe: sk-test_################# aws: AKIA############# # 功能数组 features: [auth, analytics, caching] experimental: - new-dashboard - dark-mode - websockets # 响应式断点 breakpoints: @media (max-width: 768px): mobile @media (max-width: 1024px): tablet @media (min-width: 1025px): desktop # 多行内容 welcome_message: Welcome to our application! This message spans multiple lines and preserves blank lines and indentation exactly as written. # 服务器配置(对象数组) servers: - name: web-01 ip: 192.168.1.10 active: true roles: [web, api] - name: web-02 ip: 192.168.1.11 active: false roles: [web] - name: db-01 ip: 192.168.1.20 active: true roles: [database, cache] # 元数据(前导下划线完全合法) _internal: build_number: 4567 commit: abc123def timestamp: 2024-01-15T10:30:00Z源码解析管线:从文本到对象
理解 Nueyaml 的整体流程有助于排查配置问题。parseYAML(nueyaml.js)只有四步:
validateIndentation:全文校验 Tab 缩进与缩进倍数一致性(nueyaml.js);detectStructure:逐行剥离注释后,将每行归类为keyvalue(键值对)、arrayitem(数组项)或multiline(多行字符串延续)三种块之一(nueyaml.js);buildObject:递归地把块按缩进层级组装成嵌套对象、数组与多行字符串(nueyaml.js);parseValue在组装过程中负责把单个值文本转换为字符串、数字、布尔、日期或 null(nueyaml.js)。
值得留意的是,detectStructure对数组项的判定优先于键值对(nueyaml.js):以-开头的行会先尝试解析"- key: value"的对象项,否则按纯值项处理;这与集成测试中"列表项带注释、嵌套 cron 数组"的复杂用例(nueyaml.test.js)互相印证,说明解析器对真实世界配置的容忍度。
在 Nue 生态中的实际应用
Nueyaml 并非孤立组件,它是 Nue 工具链的配置基座(package.json 中版本为 0.1.0,入口为 nueyaml.js):
- Nue Kit 站点配置:conf.js 通过
parseYAML读取站点根目录的site.yaml,合并出site、design、server、collections、production、port等配置项,并支持conf.site?.skip追加忽略列表——你完全可以对照本语法参考来扩展自己的站点配置; - 资源与渲染:asset.js 同样依赖
parseYAML解析资源元数据,svg.js 则直接导入parseYAMLArray处理 SVG 组件的数组式参数; - Nuemark 文档解析:parse-blocks.js 与 parse-document.js 用
parseYAML解析 Markdown 文档的元数据块,配合本文所述的特殊字符键名,让@media断点、API 路由这类键可以原样书写。
安装与使用(详见 nueyaml.md 的 Installation 一节):
bun install nueyamlimport { parseYAML } from 'nueyaml' const config = parseYAML(yamlString)小结
Nueyaml 用"可预测"一个原则换来了零意外配置:字符串、数字、布尔、日期、Null、数组与对象六种类型边界清晰,属性名自由、注释规则简单、缩进强制一致、错误信息带行号,同时坚决拒绝锚点、标签、多文档等复杂 YAML 特性。无论你是为 Nue Kit 编写site.yaml,还是在 Nuemark 文档中维护元数据,抑或只是想要一个没有"挪威问题"的配置解析器,本文所覆盖的语法要点都足以让你写出既符合规范、又完全可预期的配置文件。
【免费下载链接】nueFastest way to build modern websites项目地址: https://gitcode.com/GitHub_Trending/nu/nue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考