Skip to content

Commit fcf26b9

Browse files
committed
feat(node-resolve): add support for self-package imports
1 parent a4a424c commit fcf26b9

File tree

8 files changed

+81
-37
lines changed

8 files changed

+81
-37
lines changed

packages/node-resolve/src/package/resolvePackageImports.js

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,8 @@
1-
/* eslint-disable no-await-in-loop */
2-
import path from 'path';
3-
import fs from 'fs';
41
import { pathToFileURL } from 'url';
5-
import { promisify } from 'util';
62

7-
import { createBaseErrorMsg, InvalidModuleSpecifierError } from './utils';
3+
import { createBaseErrorMsg, findPackageJson, InvalidModuleSpecifierError } from './utils';
84
import resolvePackageImportsExports from './resolvePackageImportsExports';
95

10-
const fileExists = promisify(fs.exists);
11-
12-
function isModuleDir(current, moduleDirs) {
13-
return moduleDirs.some((dir) => current.endsWith(dir));
14-
}
15-
16-
async function findPackageJson(base, moduleDirs) {
17-
const { root } = path.parse(base);
18-
let current = base;
19-
20-
while (current !== root && !isModuleDir(current, moduleDirs)) {
21-
const pkgJsonPath = path.join(current, 'package.json');
22-
if (await fileExists(pkgJsonPath)) {
23-
const pkgJsonString = fs.readFileSync(pkgJsonPath, 'utf-8');
24-
return { pkgJson: JSON.parse(pkgJsonString), pkgPath: current, pkgJsonPath };
25-
}
26-
current = path.resolve(current, '..');
27-
}
28-
return null;
29-
}
30-
316
async function resolvePackageImports({
327
importSpecifier,
338
importer,

packages/node-resolve/src/package/utils.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
/* eslint-disable no-await-in-loop */
2+
import path from 'path';
3+
import fs from 'fs';
4+
import { promisify } from 'util';
5+
6+
const fileExists = promisify(fs.exists);
7+
8+
function isModuleDir(current, moduleDirs) {
9+
return moduleDirs.some((dir) => current.endsWith(dir));
10+
}
11+
12+
export async function findPackageJson(base, moduleDirs) {
13+
const { root } = path.parse(base);
14+
let current = base;
15+
16+
while (current !== root && !isModuleDir(current, moduleDirs)) {
17+
const pkgJsonPath = path.join(current, 'package.json');
18+
if (await fileExists(pkgJsonPath)) {
19+
const pkgJsonString = fs.readFileSync(pkgJsonPath, 'utf-8');
20+
return { pkgJson: JSON.parse(pkgJsonString), pkgPath: current, pkgJsonPath };
21+
}
22+
current = path.resolve(current, '..');
23+
}
24+
return null;
25+
}
26+
127
export function isUrl(str) {
228
try {
329
return !!new URL(str);

packages/node-resolve/src/resolveImportSpecifiers.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,29 @@ import { exists, realpath } from './fs';
99
import { isDirCached, isFileCached, readCachedFile } from './cache';
1010
import resolvePackageExports from './package/resolvePackageExports';
1111
import resolvePackageImports from './package/resolvePackageImports';
12-
import { ResolveError } from './package/utils';
12+
import { findPackageJson, ResolveError } from './package/utils';
1313

1414
const resolveImportPath = promisify(resolve);
1515
const readFile = promisify(fs.readFile);
1616

17+
async function getPackageJson(importer, pkgName, resolveOptions, moduleDirectories) {
18+
if (importer) {
19+
const selfPackageJsonResult = await findPackageJson(importer, moduleDirectories);
20+
if (selfPackageJsonResult && selfPackageJsonResult.pkgJson.name === pkgName) {
21+
// the referenced package name is the current package
22+
return selfPackageJsonResult;
23+
}
24+
}
25+
26+
try {
27+
const pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions);
28+
const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
29+
return { pkgJsonPath, pkgJson };
30+
} catch (_) {
31+
return null;
32+
}
33+
}
34+
1735
async function resolveId({
1836
importer,
1937
importSpecifier,
@@ -88,22 +106,17 @@ async function resolveId({
88106
});
89107
location = fileURLToPath(resolveResult);
90108
} else if (pkgName) {
91-
// this is a bare import, resolve using package exports if possible
92-
let pkgJsonPath;
93-
let pkgJson;
94-
try {
95-
pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions);
96-
pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
97-
} catch (_) {
98-
// if there is no package.json we defer to regular resolve behavior
99-
}
109+
// it's a bare import, find the package.json and resolve using package exports if available
110+
const result = await getPackageJson(importer, pkgName, resolveOptions, moduleDirectories);
100111

101-
if (pkgJsonPath && pkgJson && pkgJson.exports) {
112+
if (result && result.pkgJson.exports) {
113+
const { pkgJson, pkgJsonPath } = result;
102114
try {
103115
const subpath =
104116
pkgName === importSpecifier ? '.' : `.${importSpecifier.substring(pkgName.length)}`;
105117
const pkgDr = pkgJsonPath.replace('package.json', '');
106118
const pkgURL = pathToFileURL(pkgDr);
119+
107120
const context = {
108121
importer,
109122
importSpecifier,

packages/node-resolve/test/fixtures/node_modules/self-package-import/foo/a.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/self-package-import/foo/b.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/node-resolve/test/fixtures/node_modules/self-package-import/package.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import selfPkgImport from 'self-package-import/a';
2+
3+
export default selfPkgImport;

packages/node-resolve/test/package-entry-points.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,19 @@ test('can override a star pattern using null', async (t) => {
349349

350350
t.true(errors[0].message.includes('Could not resolve import "exports-null-override/foo/a" in '));
351351
});
352+
353+
test('can self-import a package when using exports field', async (t) => {
354+
const bundle = await rollup({
355+
input: 'self-package-import',
356+
onwarn: () => {
357+
t.fail('No warnings were expected');
358+
},
359+
plugins: [nodeResolve()]
360+
});
361+
const { module } = await testBundle(t, bundle);
362+
363+
t.deepEqual(module.exports, {
364+
a: 'a',
365+
b: 'b'
366+
});
367+
});

0 commit comments

Comments
 (0)