fix(resolver): allow builtins to be imported via subpath imports

This commit is contained in:
dave caruso
2023-12-06 20:12:09 -08:00
parent b1c8ae97ff
commit ea9b4c0f68
4 changed files with 53 additions and 16 deletions

View File

@@ -1569,10 +1569,15 @@ pub const Resolver = struct {
// Find the parent directory with the "package.json" file
var dir_info_package_json: ?*DirInfo = dir_info;
while (dir_info_package_json != null and dir_info_package_json.?.package_json == null) : (dir_info_package_json = dir_info_package_json.?.getParent()) {}
while (dir_info_package_json != null and dir_info_package_json.?.package_json == null)
dir_info_package_json = dir_info_package_json.?.getParent();
// Check for subpath imports: https://nodejs.org/api/packages.html#subpath-imports
if (dir_info_package_json != null and strings.hasPrefix(import_path, "#") and !forbid_imports and dir_info_package_json.?.package_json.?.imports != null) {
if (dir_info_package_json != null and
strings.hasPrefixComptime(import_path, "#") and
!forbid_imports and
dir_info_package_json.?.package_json.?.imports != null)
{
return r.loadPackageImports(import_path, dir_info_package_json.?, kind, global_cache);
}
@@ -2871,7 +2876,9 @@ pub const Resolver = struct {
const esmodule = ESModule{
.conditions = switch (kind) {
ast.ImportKind.require, ast.ImportKind.require_resolve => r.opts.conditions.require,
ast.ImportKind.require,
ast.ImportKind.require_resolve,
=> r.opts.conditions.require,
else => r.opts.conditions.import,
},
.allocator = r.allocator,
@@ -2881,7 +2888,15 @@ pub const Resolver = struct {
const esm_resolution = esmodule.resolveImports(import_path, imports_map.root);
if (esm_resolution.status == .PackageResolve)
if (esm_resolution.status == .PackageResolve) {
if (JSC.HardcodedModule.Aliases.get(esm_resolution.path, .bun)) |builtin| {
return .{
.success = .{
.path_pair = .{ .primary = bun.fs.Path.init(builtin.path) },
},
};
}
return r.loadNodeModules(
esm_resolution.path,
kind,
@@ -2889,6 +2904,7 @@ pub const Resolver = struct {
global_cache,
true,
);
}
if (r.handleESMResolution(esm_resolution, package_json.source.path.name.dir, kind, package_json, "")) |result| {
return .{ .success = result };

View File

@@ -0,0 +1,8 @@
{
"name": "hello",
"imports": {
"#async_hooks": "async_hooks",
"#bun": "bun",
"#bun_test": "bun:test"
}
}

View File

@@ -51,20 +51,20 @@ it("#imports with wildcard", async () => {
});
it("import.meta.resolve", async () => {
expect(await import.meta.resolve("./resolve-test.test.js")).toBe(import.meta.path);
expect(await import.meta.resolve("./resolve-test.js")).toBe(import.meta.path);
expect(await import.meta.resolve("./resolve-test.test.js", import.meta.path)).toBe(import.meta.path);
expect(await import.meta.resolve("./resolve-test.js", import.meta.path)).toBe(import.meta.path);
expect(
// optional second param can be any path, including a dir
await import.meta.resolve("./resolve/resolve-test.test.js", join(import.meta.path, "../")),
await import.meta.resolve("./resolve/resolve-test.js", join(import.meta.path, "../")),
).toBe(import.meta.path);
// can be a package path
expect((await import.meta.resolve("react", import.meta.path)).length > 0).toBe(true);
// file extensions are optional
expect(await import.meta.resolve("./resolve-test.test")).toBe(import.meta.path);
expect(await import.meta.resolve("./resolve-test")).toBe(import.meta.path);
// works with tsconfig.json "paths"
expect(await import.meta.resolve("foo/bar")).toBe(join(import.meta.path, "../baz.js"));
@@ -108,12 +108,12 @@ it("import.meta.resolve", async () => {
// the slightly lower level API, which doesn't prefill the second param
// and expects a directory instead of a filepath
it("Bun.resolve", async () => {
expect(await Bun.resolve("./resolve-test.test.js", import.meta.dir)).toBe(import.meta.path);
expect(await Bun.resolve("./resolve-test.js", import.meta.dir)).toBe(import.meta.path);
});
// synchronous
it("Bun.resolveSync", () => {
expect(Bun.resolveSync("./resolve-test.test.js", import.meta.dir)).toBe(import.meta.path);
expect(Bun.resolveSync("./resolve-test.js", import.meta.dir)).toBe(import.meta.path);
});
it("self-referencing imports works", async () => {

View File

@@ -7,18 +7,14 @@ it("spawn test file", () => {
writePackageJSONImportsFixture();
writePackageJSONExportsFixture();
copyFileSync(join(import.meta.dir, "resolve-test.js"), join(import.meta.dir, "resolve-test.test.js"));
const { exitCode } = Bun.spawnSync({
cmd: [bunExe(), "test", "resolve-test.test.js"],
cmd: [bunExe(), "test", "./resolve-test.js"],
env: bunEnv,
cwd: import.meta.dir,
stdio: ['inherit', 'inherit', 'inherit'],
});
expect(exitCode).toBe(0);
rmSync(join(import.meta.dir, "resolve-test.test.js"));
expect(existsSync(join(import.meta.dir, "resolve-test.test.js"))).toBe(false);
});
function writePackageJSONExportsFixture() {
@@ -78,6 +74,8 @@ function writePackageJSONImportsFixture() {
"#foo": "./foo/private-foo.js",
"#internal-react": "react",
"#to_node_module": "async_hooks",
},
},
null,
@@ -291,3 +289,18 @@ it("import long string should not segfault", async () => {
await import.meta.require.resolve("a".repeat(10000));
} catch {}
});
it('import override to node builtin', async() => {
// @ts-expect-error
expect(await import("#async_hooks")).toBeDefined();
});
it('import override to bun', async() => {
// @ts-expect-error
expect(await import("#bun")).toBeDefined();
});
it.todo('import override to bun:test', async() => {
// @ts-expect-error
expect(await import("#bun_test")).toBeDefined();
});