Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b8dd7d2
Throw MODULE_NOT_FOUND with its code from statement-level requires
cramforce Oct 20, 2026
4993060
Convert string | URL unions to strings through the URL's href
cramforce Oct 20, 2026
1e38f02
Map @types/node 20's class-declared Timeout and Immediate handles
cramforce Oct 20, 2026
c59457b
Map npm packages to built-in implementations and mark optional depend…
cramforce Oct 20, 2026
a6003ec
Implement undici 5.x and 7.x as a built-in package
cramforce Oct 20, 2026
13fa2c9
Document built-in packages and unloaded optional dependencies
cramforce Oct 20, 2026
f5f8c48
Merge remote-tracking branch 'origin/main' into opt/native-undici
cramforce Oct 20, 2026
8e41f76
Merge remote-tracking branch 'origin/main' into opt/native-undici
cramforce Oct 20, 2026
ea14ae9
Merge remote-tracking branch 'origin/opt/native-undici' into opt/npm-…
cramforce Oct 20, 2026
f80e4c1
Compile polyfill packages that are Node builtins as the builtins
cramforce Oct 20, 2026
2efa5d9
Fold global detection typeof tests and follow var builtin aliases
cramforce Oct 20, 2026
6a61ece
Merge remote-tracking branch 'origin/opt/npm-cjs-exports' into opt/np…
cramforce Oct 20, 2026
3a9c13a
Format the alias tests and drop the merged duplicate moduleNs case
cramforce Oct 20, 2026
c0e2e69
Record order baselines for the alias cases and corpus programs
cramforce Oct 20, 2026
3d8c725
Hoist lazily initialized ES module classes and follow on-demand works…
cramforce Oct 20, 2026
6f2a7ce
Share the initializer and top-level binding shapes with the class hoi…
cramforce Oct 20, 2026
1b282f9
Merge branch 'opt/npm-cjs-exports' into opt/npm-polyfill-shims
cramforce Oct 20, 2026
1a17bd3
Merge branch 'opt/npm-polyfill-shims' into opt/npm-esm-class-hoist
cramforce Oct 20, 2026
3cf710c
Format the npm case filter
cramforce Oct 20, 2026
3ddf964
Format the npm case filter
cramforce Oct 20, 2026
849002d
Merge branch 'opt/npm-cjs-exports' into opt/npm-polyfill-shims
cramforce Oct 20, 2026
c1da876
Merge branch 'opt/npm-polyfill-shims' into opt/npm-esm-class-hoist
cramforce Oct 20, 2026
78f713e
Implement fs-extra 8-11 as a built-in package
cramforce Oct 20, 2026
8320393
Merge branch 'opt/npm-esm-class-hoist' into opt/npm-fs-extra-shim
cramforce Oct 20, 2026
5b53ce7
Hoist lazily defined classes over global constructors
cramforce Oct 20, 2026
655ba77
Compile the fs-extra shim under @types/node as well
cramforce Oct 20, 2026
071024f
Hoist classes of an initializer declared last in a shared var statement
cramforce Oct 20, 2026
78dbc31
Find dynamic imports with a comment before their parenthesis
ctate Oct 20, 2026
4d29f27
Hoist lazily initialized ES module classes; follow on-demand workspac…
ctate Oct 20, 2026
7e6da3a
Merge remote-tracking branch 'origin/opt/npm-polyfill-shims' into opt…
cramforce Oct 20, 2026
29b7f82
Keep a hoisted class's next piece when the initializer ends with it
cramforce Oct 20, 2026
bebc094
Point bundled copies of shimmed packages at the shim; guard every shim
cramforce Oct 20, 2026
14f1fe9
Keep a hoisted class's next piece when the initializer ends with it
cramforce Oct 20, 2026
547166d
Merge branch 'opt/npm-polyfill-shims' into opt/npm-fs-extra-shim
cramforce Oct 20, 2026
d118fcb
Format the bundled-shim changes
cramforce Oct 20, 2026
1e422cb
Merge opt/npm-esm-wrappers
ctate Oct 20, 2026
15ae146
Keep builtin aliases off local bindings and unloaded packages, and va…
ctate Oct 20, 2026
12221ad
Merge opt/npm-polyfill-shims and apply modes and inode checks in the …
ctate Oct 20, 2026
2a836f8
Compile TypeScript enums in npm packages as the objects they build
cramforce Oct 20, 2026
1803406
Compile the packages a module loaded through import() requires
cramforce Oct 20, 2026
891a42f
Split a chained Set call the self-hosted compiler cannot lower
cramforce Oct 20, 2026
10e39b8
Merge remote-tracking branch 'origin/opt/npm-esm-wrappers' into opt/n…
cramforce Oct 20, 2026
4bb476a
Merge remote-tracking branch 'origin/opt/npm-polyfill-shims' into opt…
cramforce Oct 20, 2026
9458d38
Merge remote-tracking branch 'origin/opt/npm-polyfill-shims' into opt…
cramforce Oct 20, 2026
66c6bfc
Keep export on ES module enums the enum rewrite respells
ctate Oct 20, 2026
5632dec
Skip untaken global-detection arms, and fix the shim test checks
ctate Oct 20, 2026
8a40fb5
Compile the packages an import()-loaded module requires; compile Type…
ctate Oct 20, 2026
2081aaa
Compile readable-stream 3.x as Node's stream module too
cramforce Oct 20, 2026
aaf1e83
Merge remote-tracking branch 'origin/opt/npm-polyfill-shims' into opt…
cramforce Oct 20, 2026
313a984
Keep regular destination files when fs-extra's copy meets a symlink s…
ctate Oct 20, 2026
d1c516a
Implement fs-extra as a built-in package; hoist lazily defined error …
ctate Oct 20, 2026
8c40f92
Merge main
ctate Oct 20, 2026
f284f94
Merge main
ctate Oct 20, 2026
b2b7d33
Re-record the readable-stream order-parity baselines for main's modul…
ctate Oct 20, 2026
74633db
Merge main
ctate Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ node_modules/
!tests/fixtures/npm-static/nested/inner/node_modules/
!tests/fixtures/npm-static/node_modules/*/node_modules/
!tests/fixtures/npm/store/*/node_modules/
!tests/fixtures/npm/node_modules/*/node_modules/
!tests/fixtures/npm/workspace/*/node_modules/
!tests/fixtures/npm/cases/*/node_modules/
!tests/library-mode/*/node_modules/
!tests/fixtures/provenance/node_modules/
Expand Down Expand Up @@ -49,3 +51,6 @@ tmp-*/
.env.local
.env*
.env*.local
# the vendored fs-extra corpus package: its lib/output module and jsonfile's own universalify
!tests/corpus/fs-extra-package/node_modules/fs-extra/lib/output/
!tests/corpus/fs-extra-package/node_modules/jsonfile/node_modules/
53 changes: 52 additions & 1 deletion docs/content/docs/dependencies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,58 @@ Some packages your program imports directly ship no declarations, so their types
$ scriptc build app.ts --npm-static=three -o app >/dev/null
```

Naming a package skips the declaration requirement above, but the package still has to compile. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=fs-extra...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated.
Naming a package skips the declaration requirement above, but the package still has to compile. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=semver...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated.

## Built-in packages

scriptc implements a few packages itself and compiles that implementation in place of the package's published JavaScript. Which implementation compiles depends on the version your program has installed, so an import resolves the same way it does under Node.js: a package that isn't installed is still a missing package, and a version scriptc doesn't implement compiles from its own files as usual.

`undici` 5.x and 7.x are built in. The implementation covers `request()`, `Dispatcher` (including subclasses that override `dispatch`), `Client`, `Pool` with its `stats`, `Agent` with per-origin `factory` functions and `connect` options, `buildConnector`, the `errors` classes and their `UND_ERR_*` codes, `setGlobalDispatcher` and `getGlobalDispatcher`, `fetch` and `Headers`, and the request events undici publishes on `node:diagnostics_channel`. Requests use HTTP/1.1 with keep-alive connection reuse and undici's headers, body and idle timeouts, and each version keeps its own behavior where 5.x and 7.x differ, such as the error an aborted request rejects with. The package's other exports aren't declared, so using one is a type error at build time. Known differences:

- A response body supports the readable-stream members undici's body mixin uses: `on`, `once`, `pause`, `resume`, `destroy`, `text()`, `json()`, `arrayBuffer()`, `bytes()`, `blob()` and `dump()`. It can't be piped or iterated with `for await`.
- Diagnostics channel messages carry the request but not the socket or connector.
- `fetch` with a dispatcher returns a response whose `url` is empty and whose `redirected` is `false`, offers only `gzip, deflate` encodings, and sends string and byte bodies. Without a dispatcher it is the built-in `fetch`.
- Request bodies are strings and bytes; stream, iterable and form-data bodies, `pipelining` above 1, `CONNECT` requests and Unix socket paths aren't supported.

`fs-extra` 8.x through 11.x is built in, and its declarations replace `@types/fs-extra`. The default import, a namespace import and named imports all read the same exports. fs's members are re-exported, and fs-extra's wrapped methods follow its rule: with a callback as the last argument they call back, otherwise they return a promise. The extra methods cover `copy`, `copySync`, `move`, `moveSync`, `remove`, `removeSync`, `emptyDir`, `ensureDir`, `mkdirs`, `mkdirp`, `ensureFile`, `pathExists`, `outputFile`, `readJson`, `readJsonSync`, `writeJson`, `writeJsonSync`, `outputJson` and their aliases, with fs-extra's error messages. Known differences:

- `ensureLink`, `ensureSymlink`, `createLink`, `createSymlink`, `gracefulify`, the promise forms of the descriptor-based methods, and the callback forms of `stat`, `lstat`, `readdir`, `mkdtemp`, `realpath` and `readJson` aren't declared, so using one is a type error at build time. Paths are strings, text is UTF-8, and write flags, string modes and `copyFile` mode flags aren't accepted.
- graceful-fs's retry queue, which fs-extra uses under Node.js, isn't part of the implementation, so a program that runs out of file descriptors fails at the first `EMFILE` error instead of retrying.
- An ES module that imports fs-extra can use any of its exports by name. Node.js only exposes the names its CommonJS lexer finds in fs-extra's source, so a name like `readFile` throws a `SyntaxError` there.

Small polyfill packages whose module is exactly a Node.js built-in on Node.js 24 compile as that built-in, at the `require()` that loads them, from your program's CommonJS files, from compiled packages, and from esbuild bundles whose module paths name the package and its version (as pnpm installs do). The package's own files aren't compiled.

<table>
<thead>
<tr><th>Package</th><th>Versions</th><th>Compiles as</th></tr>
</thead>
<tbody>
<tr><td><code>isarray</code></td><td>0.x, 1.x, 2.x</td><td><code>Array.isArray</code></td></tr>
<tr><td><code>safe-buffer</code></td><td>5.x</td><td><code>require("buffer")</code></td></tr>
<tr><td><code>process-nextick-args</code></td><td>2.x</td><td><code>process</code></td></tr>
<tr><td><code>inherits</code></td><td>2.x</td><td><code>require("util").inherits</code></td></tr>
<tr><td><code>util-deprecate</code></td><td>1.x</td><td><code>require("util").deprecate</code></td></tr>
<tr><td><code>readable-stream</code></td><td>2.x, 3.x, 4.x</td><td><code>require("stream")</code> and its classes</td></tr>
</tbody>
</table>

`readable-stream` compiles as Node.js's built-in streams, which is its own `READABLE_STREAM=disable` mode for 2.x and 4.x. That can differ from its default behavior under Node.js; see [limitations](/docs/limitations#readable-stream).

## Optional dependencies that never load

Some packages load an optional dependency inside `try`/`catch` and work without it, such as `node-fetch` with `encoding`. To keep such a dependency out of the native build even when it is installed, mark it unloaded in your entry's `package.json`:

```json:package.json
{
"scriptc": {
"packages": {
"encoding": "unloaded"
}
}
}
```

A `require()` of an unloaded package throws the same `MODULE_NOT_FOUND` error Node.js throws when the package isn't installed, and the package isn't compiled.

## CommonJS packages

Expand Down
4 changes: 4 additions & 0 deletions docs/content/docs/limitations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ title: "Limitations"

<include>../limitations/buffer-construction.mdx</include>

### readable-stream

<include>../limitations/readable-stream.mdx</include>

### HTTP headers

<include>../limitations/http-headers.mdx</include>
Expand Down
1 change: 1 addition & 0 deletions docs/content/limitations/readable-stream.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`readable-stream` 2.x, 3.x and 4.x compile as Node.js's built-in `stream` module. For 2.x and 4.x, that is the package's own behavior when the `READABLE_STREAM` environment variable is `disable`. Without that variable, Node.js runs the package's JavaScript copy of the stream classes, so a native build can differ from Node.js wherever a program depends on that difference: `instanceof` checks against the built-in classes, class identity, error messages, and timing details. The package's `lib/_stream_*.js` files also compile as the matching built-in classes, although Node.js loads the package's own implementations through them in every environment. `readable-stream` 3.x is a copy of Node.js 10's streams. Its own `disable` mode throws on Node.js 24, so no setting makes Node.js run it against the built-in classes; a native build maps the members its entry exports (the stream classes, `Stream`, `finished` and `pipeline`) to the built-in ones.
4 changes: 4 additions & 0 deletions internal/compatibility/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ Statuses are evidence-aware: `supported` and `partial` require test evidence; `r
Run `pnpm node-compat:backlog` from the repository root for a summary. The filters `--tier`, `--action`, `--priority`, `--chapter`, and `--status` compose; use `--format=json` or `--format=tsv` for tooling.

Run `pnpm node-compat` from the repository root after changing the Node pin or the support manifest. `pnpm node-compat:check` is the offline drift/evidence gate; `pnpm --filter @internal/compatibility check:upstream` additionally verifies the pinned upstream inputs over the network.

## npm packages that compile as builtins

The ledger counts Node APIs, so npm packages that compile as a Node builtin have no rows. `packages/compiler/src/frontend/builtin-package-aliases.ts` lists them, each with the checked major versions and the reason its module is that builtin on Node 24. One entry is a documented divergence rather than an identity: `readable-stream` 2.x, 3.x and 4.x compile as `node:stream`; for 2.x and 4.x that is the package's `READABLE_STREAM=disable` behavior. Without that variable Node runs the package's own JavaScript streams. The `tests/corpus/readable-stream-disable` program pins the disabled behavior. 3.x, a copy of Node 10's streams, cannot run disabled on Node 24 (its disable branch throws), so `tests/corpus/readable-stream-3` pins only behavior its own streams share with the builtin.
1 change: 1 addition & 0 deletions packages/compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"files": [
"dist",
"ambient",
"shims",
"surface-manifest.json"
],
"publishConfig": {
Expand Down
Loading
Loading