Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(resolver): support package self-reference #12682

Merged
merged 2 commits into from Apr 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -40,6 +40,7 @@
- `[jest-reporters]` Add GitHub Actions reporter ([#11320](https://github.com/facebook/jest/pull/11320), [#12658](https://github.com/facebook/jest/pull/12658)
- `[jest-reporters]` Pass `reporterContext` to custom reporter constructors as third argument ([#12657](https://github.com/facebook/jest/pull/12657))
- `[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))
- `[jest-resolve]` Support package self-reference ([#12682](https://github.com/facebook/jest/pull/12682))
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
- `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540))
- `[jest-runner]` Allow `setupFiles` module to export an async function ([#12042](https://github.com/facebook/jest/pull/12042))
Expand Down
Empty file.
@@ -0,0 +1,3 @@
{
"name": "foo-no-exports"
}
@@ -0,0 +1,4 @@
{
"name": "foo-other-name",
"exports": "./file.js"
}
Empty file.
4 changes: 4 additions & 0 deletions packages/jest-resolve/src/__mocks__/self-ref/foo/package.json
@@ -0,0 +1,4 @@
{
"name": "foo",
"exports": "./file.js"
}
3 changes: 3 additions & 0 deletions packages/jest-resolve/src/__mocks__/self-ref/package.json
@@ -0,0 +1,3 @@
{
"name": "self-ref"
}
48 changes: 48 additions & 0 deletions packages/jest-resolve/src/__tests__/resolve.test.ts
Expand Up @@ -37,6 +37,8 @@ beforeEach(() => {
userResolver.mockClear();
userResolverAsync.async.mockClear();
mockResolveSync.mockClear();

Resolver.clearDefaultResolverCache();
});

describe('isCoreModule', () => {
Expand Down Expand Up @@ -251,6 +253,52 @@ describe('findNodeModule', () => {
);
});
});

describe('self-reference', () => {
const selfRefRoot = path.resolve(__dirname, '../__mocks__/self-ref');

test('supports self-reference', () => {
const result = Resolver.findNodeModule('foo', {
basedir: path.resolve(selfRefRoot, './foo/index.js'),
conditions: [],
});

expect(result).toEqual(path.resolve(selfRefRoot, './foo/file.js'));
});

test('supports nested self-reference', () => {
const result = Resolver.findNodeModule('foo', {
basedir: path.resolve(selfRefRoot, './foo/nested/index.js'),
conditions: [],
});

expect(result).toEqual(path.resolve(selfRefRoot, './foo/file.js'));
});

test('fails if own pkg.json with different name', () => {
const result = Resolver.findNodeModule('foo', {
basedir: path.resolve(
selfRefRoot,
'./foo/nested-with-own-pkg/index.js',
),
conditions: [],
});

expect(result).toEqual(null);
});

test('fails if own pkg.json with no exports', () => {
const result = Resolver.findNodeModule('foo-no-exports', {
basedir: path.resolve(
selfRefRoot,
'./foo/nested-with-no-exports/index.js',
),
conditions: [],
});

expect(result).toEqual(null);
});
});
});

describe('findNodeModuleAsync', () => {
Expand Down
36 changes: 32 additions & 4 deletions packages/jest-resolve/src/defaultResolver.ts
Expand Up @@ -14,6 +14,7 @@ import {
} from 'resolve.exports';
import {
PkgJson,
findClosestPackageJson,
isDirectory,
isFile,
readPackageCached,
Expand All @@ -36,7 +37,7 @@ interface ResolverOptions {
}

type UpstreamResolveOptionsWithConditions = UpstreamResolveOptions &
Pick<ResolverOptions, 'conditions'>;
Pick<ResolverOptions, 'basedir' | 'conditions'>;

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

// self-reference
const closestPackageJson = findClosestPackageJson(options.basedir);
if (closestPackageJson) {
const pkg = readPackageCached(closestPackageJson);

if (pkg.name === moduleName && pkg.exports) {
const subpath = segments.join('/') || '.';

const resolved = resolveExports(
pkg,
subpath,
createResolveOptions(options.conditions),
);

if (!resolved) {
throw new Error(
'`exports` exists, but no results - this is a bug in Jest. Please report an issue',
);
}

return pathResolve(dirname(closestPackageJson), resolved);
}
}

let packageJsonPath = '';

try {
Expand All @@ -132,10 +157,13 @@ function getPathInModule(
createResolveOptions(options.conditions),
);

// TODO: should we throw if not?
if (resolved) {
return pathResolve(dirname(packageJsonPath), resolved);
if (!resolved) {
throw new Error(
'`exports` exists, but no results - this is a bug in Jest. Please report an issue',
);
}

return pathResolve(dirname(packageJsonPath), resolved);
}
}
}
Expand Down