NEON for JavaScript
The package @nette/neon reads and writes NEON in JavaScript and TypeScript, in Node and in the browser. With
it you:
- decode a configuration file into JavaScript values and encode values back to NEON
- edit a file while every comment and space you did not touch stays where it was
- check files with
neon-lintin your editor, in CI or after an AI agent
Installation
npm install @nette/neon
It is an ES module with TypeScript types, for Node 22 or newer or any current browser, and it has no dependencies. Only the
command neon-lint uses the API of Node.
Decoding and Encoding
The function decode() turns NEON into JavaScript values and encode() turns them back:
import { decode, encode } from '@nette/neon';
decode('roles: [guest, admin]'); // { roles: ['guest', 'admin'] }
encode({ roles: ['guest', 'admin'] }); // '{roles: [guest, admin]}'
encode({ roles: ['guest', 'admin'] }, true); // 'roles:\n\t- guest\n\t- admin\n\n'
The library does not read files itself, so a file is read by Node:
import { readFileSync } from 'node:fs';
const config = decode(readFileSync('config.neon', 'utf8'));
Instead of true, the second argument of encode() can be a function that decides for each array
whether to write it as a block. It gets the array and the list of keys on the way to it:
encode(config, (value, path) => !path.length); // blocks for the first level only
The values of the format become these JavaScript values:
| NEON | JavaScript |
|---|---|
null, true, yes … |
null, boolean |
| integer | number; beyond 253 a bigint |
| float | number |
| string | string |
| date | NeonDate |
| entity, chain | Entity |
| sequence | Array |
| mapping | a plain object |
A NeonDate holds the date as it was written, including a missing time zone; toDate(zone) turns it
into a Date. A mapping is a plain object with its own properties only, so a key such as __proto__ is
just a key.
Two things follow from JavaScript itself. A float without a fractional part, such as 1.0, is the same
number as 1 and is written back as 1. And an object lists keys that look like integers
first, in ascending order; the syntax tree keeps the order of the file.
Errors
When the input is not valid NEON, decode() and parse() throw NeonError. Its property
position holds the line and the column of the problem:
import { decode, NeonError } from '@nette/neon';
try {
decode('database:\n\thost: localhost\n\thost: db');
} catch (e) {
if (e instanceof NeonError) {
e.message; // "Duplicated key 'host'"
e.position.line; // 3
}
}
Editing Files
The function parse() gives the syntax tree of a file, and through it you change the file so that the rest of it
stays as its author wrote it:
import { readFileSync, writeFileSync } from 'node:fs';
import { parse } from '@nette/neon';
const doc = parse(readFileSync('config.neon', 'utf8'));
doc.setValue(['database', 'host'], 'db.example.com');
writeFileSync('config.neon', doc.print());
How to change, add, remove and rename items, and how to find your way around the tree, is described on the page Editing Files.
Checking Files: neon-lint
The package comes with the command neon-lint. Give it files or directories, and it prints one line for each
problem:
npx neon-lint config/
config/local.neon:3:2: Duplicated key 'user'
The command, its options and its exit codes are the same as in the PHP
library. To run it without installing the package, use npx --package=@nette/neon neon-lint config/.
An AI agent that runs it after every change of a .neon file learns about a broken file right away. The guide for AI agents shows how to set it up as a
hook, and it works whatever the language of the project.
More examples, each with its real output, are in the repository of the library.