mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
feat(resolver): add support for self-referencing (#15284)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
@@ -1632,18 +1632,32 @@ pub const Resolver = struct {
|
||||
}
|
||||
}
|
||||
|
||||
var is_self_reference = false;
|
||||
|
||||
// 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();
|
||||
|
||||
// Check for subpath imports: https://nodejs.org/api/packages.html#subpath-imports
|
||||
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);
|
||||
if (dir_info_package_json) |_dir_info_package_json| {
|
||||
const package_json = _dir_info_package_json.package_json.?;
|
||||
|
||||
if (strings.hasPrefixComptime(import_path, "#") and !forbid_imports and package_json.imports != null) {
|
||||
return r.loadPackageImports(import_path, _dir_info_package_json, kind, global_cache);
|
||||
}
|
||||
|
||||
// https://nodejs.org/api/packages.html#packages_self_referencing_a_package_using_its_name
|
||||
const package_name = ESModule.Package.parseName(import_path);
|
||||
if (package_name) |_package_name| {
|
||||
if (strings.eql(_package_name, package_json.name) and package_json.exports != null) {
|
||||
if (r.debug_logs) |*debug| {
|
||||
debug.addNoteFmt("\"{s}\" is a self-reference", .{import_path});
|
||||
}
|
||||
dir_info = _dir_info_package_json;
|
||||
is_self_reference = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const esm_ = ESModule.Package.parse(import_path, bufs(.esm_subpath));
|
||||
@@ -1653,21 +1667,28 @@ pub const Resolver = struct {
|
||||
const use_node_module_resolver = global_cache != .force;
|
||||
|
||||
// Then check for the package in any enclosing "node_modules" directories
|
||||
// or in the package root directory if it's a self-reference
|
||||
while (use_node_module_resolver) {
|
||||
// Skip directories that are themselves called "node_modules", since we
|
||||
// don't ever want to search for "node_modules/node_modules"
|
||||
if (dir_info.hasNodeModules()) {
|
||||
if (dir_info.hasNodeModules() or is_self_reference) {
|
||||
any_node_modules_folder = true;
|
||||
var _paths = [_]string{ dir_info.abs_path, "node_modules", import_path };
|
||||
const abs_path = r.fs.absBuf(&_paths, bufs(.node_modules_check));
|
||||
const abs_path = if (is_self_reference)
|
||||
dir_info.abs_path
|
||||
else brk: {
|
||||
var _parts = [_]string{ dir_info.abs_path, "node_modules", import_path };
|
||||
break :brk r.fs.absBuf(&_parts, bufs(.node_modules_check));
|
||||
};
|
||||
if (r.debug_logs) |*debug| {
|
||||
debug.addNoteFmt("Checking for a package in the directory \"{s}\"", .{abs_path});
|
||||
}
|
||||
|
||||
const prev_extension_order = r.extension_order;
|
||||
defer r.extension_order = prev_extension_order;
|
||||
|
||||
if (esm_) |esm| {
|
||||
const abs_package_path = brk: {
|
||||
if (is_self_reference) break :brk dir_info.abs_path;
|
||||
var parts = [_]string{ dir_info.abs_path, "node_modules", esm.name };
|
||||
break :brk r.fs.absBuf(&parts, bufs(.esm_absolute_package_path));
|
||||
};
|
||||
|
||||
68
test/cli/run/self-reference.test.ts
Normal file
68
test/cli/run/self-reference.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { spawn } from "bun";
|
||||
import { bunExe, tmpdirSync } from "harness";
|
||||
import { join } from "path";
|
||||
import { writeFile } from "fs/promises";
|
||||
|
||||
const testWord = "bunny";
|
||||
const testString = `${testWord} ${testWord}`;
|
||||
|
||||
describe("bun", () => {
|
||||
test("should resolve self-imports by name", async () => {
|
||||
const tempDir = tmpdirSync();
|
||||
|
||||
for (const packageName of ["pkg", "@scope/pkg"]) {
|
||||
// general check without exports
|
||||
await writeFile(
|
||||
join(tempDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: packageName,
|
||||
}),
|
||||
);
|
||||
await writeFile(join(tempDir, "index.js"), `module.exports.testWord = "${testWord}";`);
|
||||
await writeFile(
|
||||
join(tempDir, "other.js"),
|
||||
`const pkg = require("${packageName}");\nimport pkg2 from "${packageName}"\nconsole.log(pkg.testWord,pkg2.testWord);`,
|
||||
);
|
||||
|
||||
let subprocess = spawn({
|
||||
cmd: [bunExe(), "run", "other.js"],
|
||||
cwd: tempDir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
let out = await new Response(subprocess.stdout).text();
|
||||
expect(out).not.toContain(testString);
|
||||
|
||||
// should not resolve not exported files
|
||||
await writeFile(
|
||||
join(tempDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: packageName,
|
||||
exports: { "./index.js": "./index.js" },
|
||||
}),
|
||||
);
|
||||
|
||||
subprocess = spawn({
|
||||
cmd: [bunExe(), "run", "other.js"],
|
||||
cwd: tempDir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
out = await new Response(subprocess.stdout).text();
|
||||
expect(out).not.toContain(testString);
|
||||
|
||||
// should resolve exported files
|
||||
await writeFile(
|
||||
join(tempDir, "other.js"),
|
||||
`const pkg = require("${packageName}/index.js");\nimport pkg2 from "${packageName}/index.js"\nconsole.log(pkg.testWord,pkg2.testWord);`,
|
||||
);
|
||||
|
||||
subprocess = spawn({
|
||||
cmd: [bunExe(), "run", "other.js"],
|
||||
cwd: tempDir,
|
||||
stdout: "pipe",
|
||||
});
|
||||
out = await new Response(subprocess.stdout).text();
|
||||
expect(out).toContain(testString);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user