Skip to content

Commit 77b0f17

Browse files
authored
feat(resolver): support package self-reference (#12682)
1 parent defbc05 commit 77b0f17

File tree

11 files changed

+95
-4
lines changed

11 files changed

+95
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
- `[jest-reporters]` Add GitHub Actions reporter ([#11320](https://github.com/facebook/jest/pull/11320), [#12658](https://github.com/facebook/jest/pull/12658)
4141
- `[jest-reporters]` Pass `reporterContext` to custom reporter constructors as third argument ([#12657](https://github.com/facebook/jest/pull/12657))
4242
- `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373))
43+
- `[jest-resolve]` Support package self-reference ([#12682](https://github.com/facebook/jest/pull/12682))
4344
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
4445
- `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540))
4546
- `[jest-runner]` Allow `setupFiles` module to export an async function ([#12042](https://github.com/facebook/jest/pull/12042))

packages/jest-resolve/src/__mocks__/self-ref/foo/file.js

Whitespace-only changes.

packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-no-exports/file.js

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "foo-no-exports"
3+
}

packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-own-pkg/file.js

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "foo-other-name",
3+
"exports": "./file.js"
4+
}

packages/jest-resolve/src/__mocks__/self-ref/foo/nested/file.js

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "foo",
3+
"exports": "./file.js"
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "self-ref"
3+
}

packages/jest-resolve/src/__tests__/resolve.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ beforeEach(() => {
3737
userResolver.mockClear();
3838
userResolverAsync.async.mockClear();
3939
mockResolveSync.mockClear();
40+
41+
Resolver.clearDefaultResolverCache();
4042
});
4143

4244
describe('isCoreModule', () => {
@@ -251,6 +253,52 @@ describe('findNodeModule', () => {
251253
);
252254
});
253255
});
256+
257+
describe('self-reference', () => {
258+
const selfRefRoot = path.resolve(__dirname, '../__mocks__/self-ref');
259+
260+
test('supports self-reference', () => {
261+
const result = Resolver.findNodeModule('foo', {
262+
basedir: path.resolve(selfRefRoot, './foo/index.js'),
263+
conditions: [],
264+
});
265+
266+
expect(result).toEqual(path.resolve(selfRefRoot, './foo/file.js'));
267+
});
268+
269+
test('supports nested self-reference', () => {
270+
const result = Resolver.findNodeModule('foo', {
271+
basedir: path.resolve(selfRefRoot, './foo/nested/index.js'),
272+
conditions: [],
273+
});
274+
275+
expect(result).toEqual(path.resolve(selfRefRoot, './foo/file.js'));
276+
});
277+
278+
test('fails if own pkg.json with different name', () => {
279+
const result = Resolver.findNodeModule('foo', {
280+
basedir: path.resolve(
281+
selfRefRoot,
282+
'./foo/nested-with-own-pkg/index.js',
283+
),
284+
conditions: [],
285+
});
286+
287+
expect(result).toEqual(null);
288+
});
289+
290+
test('fails if own pkg.json with no exports', () => {
291+
const result = Resolver.findNodeModule('foo-no-exports', {
292+
basedir: path.resolve(
293+
selfRefRoot,
294+
'./foo/nested-with-no-exports/index.js',
295+
),
296+
conditions: [],
297+
});
298+
299+
expect(result).toEqual(null);
300+
});
301+
});
254302
});
255303

256304
describe('findNodeModuleAsync', () => {

packages/jest-resolve/src/defaultResolver.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from 'resolve.exports';
1515
import {
1616
PkgJson,
17+
findClosestPackageJson,
1718
isDirectory,
1819
isFile,
1920
readPackageCached,
@@ -36,7 +37,7 @@ interface ResolverOptions {
3637
}
3738

3839
type UpstreamResolveOptionsWithConditions = UpstreamResolveOptions &
39-
Pick<ResolverOptions, 'conditions'>;
40+
Pick<ResolverOptions, 'basedir' | 'conditions'>;
4041

4142
export type SyncResolver = (path: string, options: ResolverOptions) => string;
4243
export type AsyncResolver = (
@@ -112,6 +113,30 @@ function getPathInModule(
112113
moduleName = `${moduleName}/${segments.shift()}`;
113114
}
114115

116+
// self-reference
117+
const closestPackageJson = findClosestPackageJson(options.basedir);
118+
if (closestPackageJson) {
119+
const pkg = readPackageCached(closestPackageJson);
120+
121+
if (pkg.name === moduleName && pkg.exports) {
122+
const subpath = segments.join('/') || '.';
123+
124+
const resolved = resolveExports(
125+
pkg,
126+
subpath,
127+
createResolveOptions(options.conditions),
128+
);
129+
130+
if (!resolved) {
131+
throw new Error(
132+
'`exports` exists, but no results - this is a bug in Jest. Please report an issue',
133+
);
134+
}
135+
136+
return pathResolve(dirname(closestPackageJson), resolved);
137+
}
138+
}
139+
115140
let packageJsonPath = '';
116141

117142
try {
@@ -132,10 +157,13 @@ function getPathInModule(
132157
createResolveOptions(options.conditions),
133158
);
134159

135-
// TODO: should we throw if not?
136-
if (resolved) {
137-
return pathResolve(dirname(packageJsonPath), resolved);
160+
if (!resolved) {
161+
throw new Error(
162+
'`exports` exists, but no results - this is a bug in Jest. Please report an issue',
163+
);
138164
}
165+
166+
return pathResolve(dirname(packageJsonPath), resolved);
139167
}
140168
}
141169
}

0 commit comments

Comments
 (0)