Skip to content

Commit 48a34f8

Browse files
committed
add noderc proposal
1 parent 0f59cad commit 48a34f8

File tree

7 files changed

+213
-0
lines changed

7 files changed

+213
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"schema": 0,
3+
"extends": [ "./.noderc.json" ],
4+
"watch": {
5+
"path": [ "./src", "./app.ts" ]
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"schema": 0,
3+
"import": [ "amaro/register" ]
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"schema": 0,
3+
"extends": [ "./.noderc.json" ],
4+
"import": [ "./monitor.ts" ]
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"schema": 0,
3+
"extends": [ "./.noderc.json" ],
4+
"test": {
5+
"reporter": "lcov",
6+
"reporter-destination": "./lcov.info",
7+
"coverage": true
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"schema": 0,
3+
"extends": [ "./.noderc.json" ],
4+
"test": {
5+
"reporter": "tap",
6+
"name-pattern": [ "test [1-3]", "/test [4-5]/i" ]
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "multi-config",
3+
"version": "1.0.0",
4+
"noderc": {
5+
"dev": "./.noderc.dev.json",
6+
"prod": "./.noderc.prod.json",
7+
"test": "./.noderc.test.json",
8+
"test:coverage": "./.noderc.test.coverage.json"
9+
},
10+
"scripts": {
11+
"dev": "node --watch ./app.ts",
12+
"prod": "node ./app.ts",
13+
"test": "node --test",
14+
"test:coverage": "node --run test"
15+
}
16+
}

doc/design/proposal-noderc.md

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# On-disk Node.js runtime configuration (noderc)
2+
3+
The motivation is to provide a way for users to specify runtime configuration for a Node.js application in an on-disk file that will be discovered automatically by Node.js.
4+
5+
For background and early discussions about the format, etc., see https://github.com/nodejs/node/issues/53787
6+
7+
## Overview
8+
9+
### `"noderc"` in package.json
10+
11+
An on-disk runtime configuration should be first specified in a relevant `package.json` file using the `"noderc"` field. A `package.json` file considered relevant when it's placed in any directory from the root directory to the base directory. The base directory is either the directory where the Node.js application entry point file is (if Node.js is launched to execute a file), or the current working directory (e.g. if Node.js is launched as a REPL). An example `package.json` file looks like this:
12+
13+
```json
14+
{
15+
"name": "my-project",
16+
"schema": "1.0.0",
17+
"noderc": "./.noderc.json"
18+
}
19+
```
20+
21+
In the initial iteration, the runtime configuration file must be in JSON format, and has an extension that ends with `.json`. We are open to support more formats identified by other extensions in the future but the details will remain to be discussed and it won't be implemented in the initial iteration.
22+
23+
The idiomatic way to specify this configuration would be using a file named `.noderc.json` in the same directory as the `package.json`, which tends to be in the project root directory.
24+
25+
26+
### Basic of `noderc` in JSON
27+
28+
```json
29+
{
30+
"schema": 0,
31+
"import": [ "amaro/register", "./monitor.ts" ]
32+
}
33+
```
34+
35+
- The `schema` field is only meant for breaking changes to the schema, so it's a single number.
36+
- At schema 0, there is no stability guarantee about the schema
37+
- When we iterate on the schema to a point to consider it stable, the schema will be set to 1
38+
- If the noderc is using a schema that's not the latest supported by the running Node.js version (e.g. it uses 2 but the latest schema supported by the Node.js version is 3), Node.js converts the older schema to the newer schema in the underlying implementation.
39+
- If the noderc is using a schema that the current Node.js version doesn't support (e.g. it's schema 3 while the Node.js version only supports up to 2), an error is thrown.
40+
- If the noderc is using a feature that the current Node.js version doesn't support, a warning is emitted, and the unsupported feature is ignored (or an error can be thrown).
41+
- The other fields come from a selected list of features that can be configured using noderc.
42+
- The most commonly used ones should be `import` and `require`, similar to `--import` and `--require`
43+
44+
Compared to regular CLI flags, a structured representation of the configuration allows more granular control of the behavior, for example, it may be expanded as
45+
46+
```json
47+
{
48+
"schema": 0,
49+
"import": [ { "specifier": "./monitor.js", "mainThreadOnly": false } ]
50+
}
51+
```
52+
53+
It also allows nicer-looking sub-configurations without repeating the prefix required by CLI flags, for example:
54+
55+
```json
56+
{
57+
"schema": 0,
58+
"test": {
59+
"reporter": "tap",
60+
"name-pattern": [ "test [1-3]", "/test [4-5]/i" ]
61+
}
62+
}
63+
```
64+
65+
### Overriding the noderc file being applied
66+
67+
We should provide a way for users to override the noderc file that's discoverd through `package.json` lookups, or to disable it. This can be done through either environment variables, or CLI flags, or both.
68+
69+
The environment variable/CLI flag can specify a registered rc file in `package.json`, where `"noderc"` contains key-value pairs instead of just a string:
70+
71+
```json
72+
{
73+
"noderc": {
74+
"default": "./.noderc.json",
75+
"test": "./.noderc.test.json",
76+
"test:coverage": "./.noderc.test.coverage.json"
77+
}
78+
}
79+
```
80+
81+
Suppose the environment variable is called `NODE_RC`, then `NODE_RC=test` results in the resolution of `./.noderc.test.json`. If `NODE_RC` is unspecified, and the `"noderc"` field in `package.json` contains key-value pairs, the rc file pointed by `"default"` will be selected by default.
82+
83+
Open question: what should be the key of the default noderc? `default` might still class with potential `script` fields, which may or may not be a problem in the script -> noderc mapping we'll discuss below.
84+
85+
### Accompanying JS APIs
86+
87+
This feature should have some accompanying JS APIs to:
88+
89+
1. Parse a given rc file
90+
2. Serialize a given configuration
91+
3. Querying the current configuration applied to the process (with an option to include additional configurations added by CLI flags/environment variables/runtime APIs), and where they come from
92+
4. Querying the current schema schema, and the features supported
93+
94+
IDEs and other tooling are expected to use a matching Node.js version (likely specified by the "engine" field in the applicable `package.json`) to modify the rc files.
95+
96+
This will be left to later iterations.
97+
98+
### Reusing/extending configurations
99+
100+
A `noderc` file can extend other `noderc` files. For example, for the main `noderc` pointed by `package.json`, it has an import that enables TypeScript loading:
101+
102+
```json
103+
{
104+
"schema": 0,
105+
"import": [ "amaro/register" ]
106+
}
107+
```
108+
109+
In addition, there can be another `./.noderc.prod.json` extending it for starting the server in production with monitor on:
110+
111+
```json
112+
{
113+
"schema": 0,
114+
"extends": [ "./.noderc.json" ],
115+
"import": [ "./monitor.ts" ]
116+
}
117+
```
118+
119+
Open question: how should we handle override v.s. concatenation? Should we invent a special syntax? For example, to concatenate, use `"+import"`, otherwise, use `"import"`? Or is that too cryptic and we should just do "override if it's not an array, concatenate if it's an array?"
120+
121+
122+
### Mapping script entries to noderc entries
123+
124+
The basic idea is that task runners need to perform the mapping between the `script` chosen to be run to the matching `noderc` entires. Consider this example:
125+
126+
```json
127+
{
128+
"noderc": {
129+
"dev": "./.noderc.dev.json",
130+
"prod": "./.noderc.prod.json",
131+
"test": "./.noderc.test.json",
132+
"test:coverage": "./.noderc.test.coverage.json"
133+
},
134+
"scripts": {
135+
"dev": "node --watch ./app.js",
136+
"prod": "node ./app.js",
137+
"test": "node --test",
138+
"test:coverage": "node --run test"
139+
}
140+
}
141+
```
142+
143+
It's the job of the task runners to match them via the environment variable described before e.g. translating `npm_lifecycle_event=test:coverage` to `NODE_RC=test:coverage` before running the `test:coverage` target.
144+
145+
See the directory [./examples/noderc/multi-config](./examples/noderc/multi-config) for a sketch.
146+
147+
Before the task runners implement these, users can choose to translate the environment variables themselves in the `scripts` target using something like `cross-env`.
148+
149+
```json
150+
{
151+
"noderc": {
152+
"dev": "./.noderc.dev.json",
153+
"prod": "./.noderc.prod.json",
154+
"test": "./.noderc.test.json",
155+
"test:coverage": "./.noderc.test.coverage.json"
156+
},
157+
"scripts": {
158+
"dev": "cross-env NODE_RC=dev node --watch ./app.js",
159+
"prod": "cross-env NODE_RC=prod node ./app.js",
160+
"test": "cross-env NODE_RC=test node --test",
161+
"test:coverage": "cross-env NODE_RC=test:coverage node --run test"
162+
}
163+
}
164+
```

0 commit comments

Comments
 (0)