Skip to content

lib: add experimental node:yaml module - #67157

Open
marcopiraccini wants to merge 1 commit into
nodejs:mainfrom
marcopiraccini:yaml-module
Open

marcopiraccini wants to merge 1 commit into
nodejs:mainfrom
marcopiraccini:yaml-module

Conversation

@marcopiraccini

@marcopiraccini marcopiraccini commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

This is a proposal for a built-in YAML parser, with a basic implementation to discuss on: an experimental, parse-only node:yaml module behind --experimental-yaml.

const yaml = require('node:yaml');
yaml.parse('name: app\nports: [8080, 8443]\n');
// { name: 'app', ports: [ 8080, 8443 ] }

The parser supports the YAML 1.2 core schema, anchors, aliases, tags, and multi-document streams. It is written from scratch and has no external dependencies.

This gives Node.js a built-in YAML parser for future configuration support, following the discussion about .noderc in #53787, where the config file ended up JSON-only because core had no parser.

Bun parity

Bun ships Bun.YAML since v1.2.21. yaml.parse() follows Bun.YAML.parse() so that code written for it runs unchanged:

  • parse(input) takes a string or binary input (ArrayBuffer, TypedArray, DataView) decoded as UTF-8.
  • One document returns its value, a multi-document stream returns an array, an empty stream returns null.
  • Invalid input throws a SyntaxError (here with the ERR_INVALID_YAML code and the line and column).
  • Aliases of an anchored collection are the same object, and a collection may contain an alias to itself, so results can hold cycles.
  • Core schema tags such as !!str and !!int are applied.

Not covered yet: Bun.YAML.stringify(value, replacer, space), Blob input, and importing .yaml files as modules. See the next steps.

Tests

Tests include the official YAML Test Suite, vendored at a fixed revision. Of 402 cases, 400 pass.

The two that fail, 2JQS and X38W, are documents with duplicate keys: two empty keys, and a key that is an alias of the previous key. The suite only checks that they parse, since duplicate keys are not a syntax error, and provides no expected output for them. The YAML 1.2 specification requires mapping keys to be unique, and this parser enforces it and throws Duplicate mapping key, as the yaml and js-yaml packages do by default. They are listed as known failures with that reason. If the reviewers prefer the JSON.parse() behavior, where the last value wins, the check can be removed and both pass.

Review guide

  1. doc/api/yaml.md: API behavior.
  2. lib/yaml.js: input validation and document return values.
  3. lib/internal/yaml/parser.js: parser implementation.
  4. test/parallel/test-yaml.js: API tests. test/parallel/test-yaml-test-suite.js: official suite runner.
  5. The experimental flag follows the node:vfs setup in vfs: add minimal node:vfs subsystem #63115.

Next steps, if accepted

  1. yaml.stringify(value, replacer, space) with Bun's semantics.
  2. import data from './config.yaml' with { type: 'yaml' }.
  3. YAML support in --experimental-config-file.
  4. A yaml subsystem label and commit subsystem, as was done for vfs.
  5. Graduation from the flag once the API has settled.

Refs:

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/loaders
  • @nodejs/startup

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. labels Oct 31, 2026
@marcopiraccini marcopiraccini changed the title yaml: add experimental node:yaml module lib: add experimental node:yaml module Oct 31, 2026
@marcopiraccini
marcopiraccini marked this pull request as ready for review October 31, 2026 07:37
@marcopiraccini
marcopiraccini force-pushed the yaml-module branch 2 times, most recently from ad1f666 to 09ed139 Compare October 31, 2026 08:29
@codecov

codecov Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.73%. Comparing base (d44625a) to head (bbfacd1).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #67157      +/-   ##
==========================================
+ Coverage   90.69%   90.73%   +0.03%     
==========================================
  Files         797      799       +2     
  Lines      278787   280456    +1669     
  Branches    53607    54257     +650     
==========================================
+ Hits       252858   254467    +1609     
- Misses      16292    16343      +51     
- Partials     9637     9646       +9     
Files with missing lines Coverage Δ
lib/internal/bootstrap/realm.js 97.49% <100.00%> (+0.39%) ⬆️
lib/internal/errors.js 98.81% <100.00%> (+<0.01%) ⬆️
lib/internal/yaml/parser.js 100.00% <100.00%> (ø)
lib/yaml.js 100.00% <100.00%> (ø)
src/node_builtins.cc 77.35% <100.00%> (-0.31%) ⬇️
src/node_options.cc 88.20% <100.00%> (+0.01%) ⬆️
src/node_options.h 95.72% <100.00%> (+0.01%) ⬆️

... and 35 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Add a YAML 1.2 parser for the core schema as `node:yaml`, behind
`--experimental-yaml`, with `yaml.parse(input)` following the shape of
`Bun.YAML.parse()`. The parser is written from scratch, in one pass from
text to values, with no new dependency. Anchors and aliases resolve to
shared objects, core schema tags are applied, mappings become plain
objects and duplicate keys are an error.

The official yaml-test-suite is vendored as a JSON fixture and run by
test-yaml-test-suite.js: 400 of its 402 cases pass, the two left are
documents with duplicate keys.

Refs: nodejs#53787
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>

@LiviaMedeiros LiviaMedeiros left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm +0.5 for adding builtin yaml support in general and would be +1 if some concerns were resolved first:

  1. How do we do versioning once v1.3 or subsequent specs are released? While JSON is both blessed in cursed with lack of versioning, in YAML we need to account this somehow. Hardcoding v1.2.2 isn't very future-proof.
    1.1. Should we introduce compatibility with v1.1?
  2. What's the plan for this module, aside from parse() and stringify()? Are we aiming for full-fledged module with all the classes, traversal methods, selectors, transformers, handling multi-document objects, etc.?

Without draft for the desired module API, it's hard to tell if it really should be implemented as separate builtin module.
If we just need this for something similar to #53787, i think an internal/util.parseYAML() (potentially re-exported later for userland as util.parseYAML() or util.YAML.parse()) would be sufficient.

Comment on lines +6 to +8
// parser. test/fixtures/yaml-test-suite.json holds, for every test, the input
// and either the expected documents as JSON or a flag that the input is an
// error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: would be more future-proof if either the file structure was fully defined here, or if there was a tool that regenerates it if test suite gets updated.

Comment thread lib/yaml.js
Comment on lines +17 to +21
let TextDecoder;
function decode(input) {
TextDecoder ??= require('internal/encoding').TextDecoder;
return new TextDecoder('utf-8').decode(input);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit

Suggested change
let TextDecoder;
function decode(input) {
TextDecoder ??= require('internal/encoding').TextDecoder;
return new TextDecoder('utf-8').decode(input);
}
let textDecoder;
function decode(input) {
textDecoder ??= new (require('internal/encoding').TextDecoder)('utf-8');
return textDecoder.decode(input);
}

@ShogunPanda ShogunPanda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm +1 to this. Actually I wanted to implement it but Marco beated me on time.
Only a small nit: make it clear that now it only adds the parser in the commit.

@mcollina

mcollina commented Nov 2, 2026

Copy link
Copy Markdown
Member

Can we verify if a native version would be faster?

I'm +1 on adding it to core, but I think we should strive to have near-json performance if possible.

@jsumners-nr

Copy link
Copy Markdown

It is written from scratch and has no external dependencies.

Why was this path chosen over forking a known tested module and vendoring it?

Comment thread lib/yaml.js
if (documents.length === 0) return null;
if (documents.length === 1) return documents[0];
return documents;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A non-throwing parse variant (returning null on failure to parse) would be helpful to avoid allocating the error stack in cases it's not needed

@aduh95

aduh95 commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Should we pick TOML instead? It's much easier to parse (and a simpler parser usually means less maintenance burden), even for humans arguably

@jsumners-nr

Copy link
Copy Markdown

Should we pick TOML instead? It's much easier to parse (and a simpler parser usually means less maintenance burden), even for humans arguably

It shouldn't be "instead." There are many reasons to parse yaml, and the standard library is not currently providing a means to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants