#3131

docs: document Node islands and correct the pages that deny them

Draftcramforcewants to merge intofrom

Documents Node islands and corrects the pages that said every package compiles to native code.

Why

A build runs npm packages it can't compile natively in Node.js, loaded into the program on first use, and prints an SC8001 note for each. No page under docs/content/docs/ mentioned islands or the libnode pack, dependencies.mdx said every package compiles to native code, and the first islanded dependency of a fresh install stops with SC8005, whose hint names a path inside this repository.

What changed

  • New page docs/content/docs/node-islands.mdx ("Node Islands", in the Guides group after npm Dependencies): what an island is, a worked example (ejs), how to get a libnode pack today, what runs in an island and the reason classes, how to see it (SC8001, scriptc coverage, --explain-island, --fail-on), how to move code into or out of an island (--node-islands, --node-island, --native, package.json), how values cross and one refusal with its fix, worker threads, measured cost, shipping, @scriptc/node, the SC80xx codes, and current limits.
  • Corrections: dependencies.mdx (intro, "Which packages compile", "Unsupported code inside a package", "Upgrading from scriptc 0.2"), index.mdx, quickstart.mdx, how-it-works.mdx, coverage.mdx (a "Node islands" section, the report-field table, --fail-on), cli.mdx (--node-islands, --node-island, --native, --explain-island, --libnode, the island values of --fail-on, SCRIPTC_LIBNODE), the landing page's three cards, and the README's first paragraph.
  • Limitations: a "Node islands" section built from ten partials under docs/content/limitations/node-islands/; runtime-code-evaluation.mdx no longer says a compiled program can never evaluate source; native-addons.mdx and ffi.mdx say that a package with a native addon runs in an island.
  • Registration: meta.json, PAGE_TITLES, and the route-test fixture (the new page with twelve heading anchors, and the new headings on the coverage and limitations pages).

No compiler, runtime or test code changes.

What the page says where main is still moving

  • Getting a pack. The @scriptc/libnode-* packages are not on npm and scriptc depends on none, so the page says that plainly, shows the real SC8005, and documents the one route that works today: pnpm --filter @scriptc/libnode-<platform> build:native in a checkout, which I ran (38 minutes on 8 vCPUs, a 127 MB pack). When the packs are published this section shrinks to an install line.
  • Crossing rules. The page states the principle (a value crosses by reference unless a copy can't be observed), the four cases that hold on main, and one refusal, a WeakMap, which stays refused under the open changes to the rule. It says the refused set is shrinking and does not list it.
  • Stale tool text, quoted as it is on main. #3116 changes the CLI's island wording, so two strings quoted on the page will change when it lands and the page needs a follow-up then: the coverage header Node islands (would-be, report-only: the build is unchanged; …) with (would-be, report-only) in --explain-island, and the SC8001 hint --node-islands=off makes this an error. The page has one sentence explaining each; those sentences go when the strings do. #3116 also makes scriptc run pass arguments to the program; this PR does not touch the sentences that say it doesn't.
  • SCRIPTC_NODE_ISLANDS_TRACE=boot appears in the cost section because it is the only way to see when Node.js starts. It is a runtime debug variable; strike that fence if it should stay undocumented.

Proof

  • Docs gate, in a Linux x64 Sandbox on this branch: cd docs && NEXT_DIST_DIR=.next-check pnpm check — passes: compatibility drift check, typecheck, unit tests, production build, and 26 of 26 route tests, including the new page's title, twelve heading anchors, Markdown twin and internal link fragments.
  • pnpm lint (includes the one-sentence-per-line check of the limitations partials) and pnpm format:check — 0 warnings, all files formatted.
  • Every console fence on the new page comes from a sandbox transcript of the checkout built at 714482d49 with scriptc 0.4.9, Node.js 24.22.1, Ubuntu 26.04, Intel Xeon @ 2.50GHz, 8 vCPU. A script checked the page's 39 commands and 13 source fences against the transcripts and the example files: every command ran, and every output line shown is in that command's output (lines are trimmed, none added).

Measured cost (Linux x64, one machine, hyperfine, 50 runs each)

ProgramStart to exitPeak RSSExecutable
ms compiled natively0.69 ms2.3 MB603,896 bytes
ms in an island50.3 ms85.1 MB691,368 bytes
ejs island never reached0.66 ms2.4 MB
ejs island used once57.8 ms88.4 MB
node duration.ts83.9 ms64.5 MB

100,000 calls of ms("2h"): 43.1 ms native, 160.7 ms in an island, 96.4 ms under Node, so about one microsecond per crossing after the start-up. The pack is 127 MB.

Found while measuring

These are behaviours of main that the page documents as limits rather than hides. None is changed here.

  • --native=<pkg> and --node-islands=off do not fail a build: the package compiles natively and its Node-only operations throw at run time. SC8002/SC8003 appear only in coverage.
  • A crossing refusal (SC8010) on an automatically chosen island becomes an SC8023 warning and a native build that fails at run time.
  • An island used from a worker thread aborts with internal error: dyn handle ops not installed (#3007 and #3047 are open for this).
  • --node-island=<file>.ts is accepted and ignored.
  • A package in an island does not see the program's process.argv (--node-island=commander).
  • JSON.stringify of an island object throws at run time (lodash groupBy).
  • Coverage's island list can name a package the build compiles natively. One such package, mustache@4.2.0, compiles natively to wrong output with no diagnostic (Mustache.render("plain", {}) returns ""); that is a compiler bug, not documented here as a limit, and needs its own issue.
  • A package with a native addon (@node-rs/crc32) runs in an island and works, so the "addons can't load" statements were out of date.

🤖 Generated with Claude Code

Activity

8
vercel[bot]6d ago

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated
scriptc ReadyPreview, v0Nov 7, 2026 3:07pm UTC
vercel[bot]reviewed6d ago
cramforcereviewed6d ago
cramforce6d ago

On hold: the "When a value can't cross" section shows the SC8010 error and the SC8023 warning for labels.ts, which main no longer prints since #3005 merged (the build now succeeds and the executable fails at run time with TypeError: scriptc: this native object cannot cross into a Node island yet). The page waits for the build-time refusal of a WeakMap crossing into an island to be restored; its console blocks will then be re-run against a tree that has the fix.

This pull request is a draft

Merging is unavailable until it is marked ready for review.