Resolve bare core-module names to the builtin over npm packages

Node checks its builtins before node_modules for a bare core-module name,
so an installed npm package named string_decoder (a transitive dependency
of pi) never answers `import ... from "string_decoder"`. scriptc
attributed the import to the package and refused it. The npm resolution
now answers null for bare builtin names (prefix-only builtins such as
node:test keep their bare names for packages), for ES imports and CommonJS
requires alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-18 15:15:05 -07:00
co-authored by Claude Opus 5.5
parent bcc35ddf1a
commit 98b3ced80c
9 changed files with 43 additions and 0 deletions
@@ -3971,6 +3971,12 @@ function resolveNpmImport7(
if (isRelativeSpecifier(specifier) || specifier.startsWith("node:")) {
return null;
}
// Node resolves a bare core-module name (`string_decoder`, `events`,
// `fs/promises`) to the builtin before it looks in node_modules, so an
// installed npm package of the same name never answers it. Prefix-only
// builtins (`node:test`) are listed with their prefix, so a bare `test`
// still names the package.
if (nodeBuiltinNames.has(specifier)) return null;
// --provenance-sources: a registered specifier is NOT an npm import —
// its attested source compiles as program modules (resolveProjectModule
@@ -0,0 +1,7 @@
{
"order": [
"<repo>/tests/corpus/6235-bare-builtin-shadowed-package/helper.cjs",
"<repo>/tests/corpus/6235-bare-builtin-shadowed-package/main.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
// A CommonJS require of the bare names resolves to the builtins too.
const { StringDecoder } = require("string_decoder");
exports.decodeEuro = () => {
const d = new StringDecoder("utf8");
return d.write(Buffer.from([0xe2, 0x82])) + d.end(Buffer.from([0xac]));
};
@@ -0,0 +1,17 @@
// A bare core-module specifier names the Node builtin even when an npm
// package of the same name is installed (pi depends on string_decoder
// transitively): Node checks its builtins before node_modules.
import { StringDecoder } from "string_decoder";
import { EventEmitter } from "events";
import { createRequire } from "node:module";
const decoder = new StringDecoder("utf8");
console.log(decoder.write(Buffer.from([0xe2, 0x82])) + decoder.end(Buffer.from([0xac])));
const emitter = new EventEmitter();
emitter.on("ping", (n: number) => console.log("ping", n));
emitter.emit("ping", 1);
const require = createRequire(import.meta.url);
const helper = require("./helper.cjs") as { decodeEuro(): string };
console.log("cjs", helper.decodeEuro());
@@ -0,0 +1 @@
throw new Error("the npm events package was loaded");
@@ -0,0 +1 @@
{ "name": "events", "version": "3.3.0", "main": "events.js" }
@@ -0,0 +1,3 @@
// An installed package named like the core module: Node never loads it
// for the bare specifier.
throw new Error("the npm string_decoder package was loaded");
@@ -0,0 +1 @@
{ "name": "string_decoder", "version": "1.3.0", "main": "index.js" }
@@ -0,0 +1 @@
{ "type": "module" }