Skip to content

Commit 42c8e7d

Browse files
authored
feat: implement the defaultAsyncResolver (#15679)
1 parent 070781a commit 42c8e7d

File tree

8 files changed

+86
-54
lines changed

8 files changed

+86
-54
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
## main
22

3+
### Features
4+
5+
- `[jest-resolver]` Implement the `defaultAsyncResolver` ([#15679](https://github.com/jestjs/jest/pull/15679))
6+
37
### Chore & Maintenance
48

5-
- `[*]` Remove and deprecate `jest-repl` package ([15673](https://github.com/jestjs/jest/pull/15673))
9+
- `[*]` Remove and deprecate `jest-repl` package ([#15673](https://github.com/jestjs/jest/pull/15673))
610

711
## 30.0.0
812

@@ -120,6 +124,7 @@
120124
- `[*]` [**BREAKING**] Bundle all of Jest's modules into `index.js` ([#12348](https://github.com/jestjs/jest/pull/12348), [#14550](https://github.com/jestjs/jest/pull/14550) & [#14661](https://github.com/jestjs/jest/pull/14661))
121125
- `[jest-haste-map]` Only spawn one process to check for `watchman` installation ([#14826](https://github.com/jestjs/jest/pull/14826))
122126
- `[jest-runner]` Better cleanup `source-map-support` after test to resolve (minor) memory leak ([#15233](https://github.com/jestjs/jest/pull/15233))
127+
- `[jest-resolver]` Migrate `resolve` and `resolve.exports` to `unrs-resolver` ([#15619](https://github.com/jestjs/jest/pull/15619))
123128
- `[jest-circus, jest-environment-node, jest-repl, jest-runner, jest-util]` Cleanup global variables on environment teardown to reduce memory leaks ([#15215](https://github.com/jestjs/jest/pull/15215) & [#15636](https://github.com/jestjs/jest/pull/15636) & [#15643](https://github.com/jestjs/jest/pull/15643))
124129

125130
### Chore & Maintenance

e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = `
4141
12 | module.exports = () => 'test';
4242
13 |
4343
44-
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1122:17)
44+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1129:17)
4545
at Object.require (index.js:10:1)
4646
at Object.require (__tests__/index.js:10:20)"
4747
`;
@@ -71,7 +71,7 @@ exports[`moduleNameMapper wrong configuration 1`] = `
7171
12 | module.exports = () => 'test';
7272
13 |
7373
74-
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1122:17)
74+
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:1129:17)
7575
at Object.require (index.js:10:1)
7676
at Object.require (__tests__/index.js:10:20)"
7777
`;

e2e/__tests__/__snapshots__/requireMissingExt.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ exports[`shows a proper error from deep requires 1`] = `
2626
12 | test('dummy', () => {
2727
13 | expect(1).toBe(1);
2828
29-
at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/index.js:868:11)
29+
at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/index.js:875:11)
3030
at Object.<anonymous> (node_modules/discord.js/src/index.js:21:12)
3131
at Object.require (__tests__/test.js:10:1)"
3232
`;

e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ exports[`show error message with matching files 1`] = `
3737
| ^
3838
9 |
3939
40-
at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/index.js:868:11)
40+
at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/index.js:875:11)
4141
at Object.require (index.js:8:18)
4242
at Object.require (__tests__/test.js:8:11)"
4343
`;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {pathToFileURL} from 'url';
1313

1414
import userResolver from '../__mocks__/userResolver';
1515
import userResolverAsync from '../__mocks__/userResolverAsync';
16-
import defaultResolver from '../defaultResolver';
16+
import defaultResolver, {defaultAsyncResolver} from '../defaultResolver';
1717
import nodeModulesPaths from '../nodeModulesPaths';
1818
import Resolver from '../resolver';
1919
import type {ResolverConfig} from '../types';
@@ -109,6 +109,7 @@ describe('findNodeModule', () => {
109109
expect(mockUserResolver.mock.calls[0][1]).toStrictEqual({
110110
basedir: '/',
111111
conditions: ['conditions, woooo'],
112+
defaultAsyncResolver,
112113
defaultResolver,
113114
extensions: ['js'],
114115
moduleDirectory: ['node_modules'],
@@ -404,6 +405,7 @@ describe('findNodeModuleAsync', () => {
404405
expect(mockUserResolverAsync.async.mock.calls[0][1]).toStrictEqual({
405406
basedir: '/',
406407
conditions: ['conditions, woooo'],
408+
defaultAsyncResolver,
407409
defaultResolver,
408410
extensions: ['js'],
409411
moduleDirectory: ['node_modules'],

packages/jest-resolve/src/defaultResolver.ts

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {resolve} from 'path';
98
import {fileURLToPath} from 'url';
109
import pnpResolver from 'jest-pnp-resolver';
1110
import {
11+
type ResolveResult,
1212
ResolverFactory,
1313
type NapiResolveOptions as UpstreamResolveOptions,
1414
} from 'unrs-resolver';
@@ -20,7 +20,9 @@ export interface ResolverOptions extends UpstreamResolveOptions {
2020
/** List of export conditions. */
2121
conditions?: Array<string>;
2222
/** Instance of default resolver. */
23-
defaultResolver: typeof defaultResolver;
23+
defaultResolver: SyncResolver;
24+
/** Instance of default async resolver. */
25+
defaultAsyncResolver: AsyncResolver;
2426
/**
2527
* List of directory names to be looked up for modules recursively.
2628
*
@@ -53,7 +55,24 @@ export type AsyncResolver = (
5355

5456
export type Resolver = SyncResolver | AsyncResolver;
5557

56-
const defaultResolver: SyncResolver = (path, options) => {
58+
const handleResolveResult = (result: ResolveResult) => {
59+
if (result.error) {
60+
throw new Error(result.error);
61+
}
62+
return result.path!;
63+
};
64+
65+
function baseResolver(path: string, options: ResolverOptions): string;
66+
function baseResolver(
67+
path: string,
68+
options: ResolverOptions,
69+
async: true,
70+
): Promise<string>;
71+
function baseResolver(
72+
path: string,
73+
options: ResolverOptions,
74+
async?: true,
75+
): string | Promise<string> {
5776
if (process.versions.pnp && options.allowPnp !== false) {
5877
return pnpResolver(path, options);
5978
}
@@ -76,9 +95,6 @@ const defaultResolver: SyncResolver = (path, options) => {
7695
/* eslint-enable prefer-const */
7796
} = options;
7897

79-
// make sure that `basedir` is an absolute path
80-
basedir = resolve(basedir);
81-
8298
modules = modules || moduleDirectory;
8399

84100
const resolveOptions: UpstreamResolveOptions = {
@@ -95,32 +111,50 @@ const defaultResolver: SyncResolver = (path, options) => {
95111
unrsResolver = unrsResolver.cloneWithOptions(resolveOptions);
96112
} else {
97113
unrsResolver = new ResolverFactory(resolveOptions);
114+
setResolver(unrsResolver);
98115
}
99116

100-
setResolver(unrsResolver);
101-
102-
let result = unrsResolver.sync(basedir, path);
103-
104-
if (!result.path && paths?.length) {
105-
const modulesArr =
106-
modules == null || Array.isArray(modules) ? modules : [modules];
107-
if (modulesArr?.length) {
108-
paths = paths.filter(p => !modulesArr.includes(p));
117+
const finalResolver = (
118+
resolve: (
119+
resolver: ResolverFactory,
120+
) => ResolveResult | Promise<ResolveResult>,
121+
) => {
122+
const resolveWithPathsFallback = (result: ResolveResult) => {
123+
if (!result.path && paths?.length) {
124+
const modulesArr =
125+
modules == null || Array.isArray(modules) ? modules : [modules];
126+
if (modulesArr?.length) {
127+
paths = paths.filter(p => !modulesArr.includes(p));
128+
}
129+
if (paths.length > 0) {
130+
unrsResolver = unrsResolver!.cloneWithOptions({
131+
...resolveOptions,
132+
modules: paths,
133+
});
134+
return resolve(unrsResolver);
135+
}
136+
}
137+
return result;
138+
};
139+
const result = resolve(unrsResolver!);
140+
if ('then' in result) {
141+
return result.then(resolveWithPathsFallback).then(handleResolveResult);
109142
}
110-
if (paths.length > 0) {
111-
unrsResolver = unrsResolver.cloneWithOptions({
112-
...resolveOptions,
113-
modules: paths,
114-
});
115-
result = unrsResolver.sync(basedir, path);
116-
}
117-
}
143+
return handleResolveResult(
144+
resolveWithPathsFallback(result) as ResolveResult,
145+
);
146+
};
118147

119-
if (result.error) {
120-
throw new Error(result.error);
121-
}
148+
return finalResolver((resolver: ResolverFactory) =>
149+
async ? resolver.async(basedir, path) : resolver.sync(basedir, path),
150+
);
151+
}
122152

123-
return result.path!;
124-
};
153+
export const defaultResolver: SyncResolver = baseResolver;
154+
155+
export const defaultAsyncResolver: AsyncResolver = (
156+
path: string,
157+
options: ResolverOptions,
158+
) => baseResolver(path, options, true);
125159

126160
export default defaultResolver;

packages/jest-resolve/src/resolver.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import defaultResolver, {
1515
type AsyncResolver,
1616
type Resolver as ResolverInterface,
1717
type SyncResolver,
18+
defaultAsyncResolver,
1819
} from './defaultResolver';
1920
import {clearFsCache} from './fileWalkers';
2021
import isBuiltinModule from './isBuiltinModule';
@@ -122,6 +123,7 @@ export default class Resolver {
122123
return resolver(path, {
123124
basedir: options.basedir,
124125
conditions: options.conditions,
126+
defaultAsyncResolver,
125127
defaultResolver,
126128
extensions: options.extensions,
127129
moduleDirectory: options.moduleDirectory,
@@ -142,7 +144,7 @@ export default class Resolver {
142144
options: FindNodeModuleConfig,
143145
): Promise<string | null> {
144146
const resolverModule = loadResolver(options.resolver);
145-
let resolver: ResolverInterface = defaultResolver;
147+
let resolver: ResolverInterface = defaultAsyncResolver;
146148

147149
if (typeof resolverModule === 'function') {
148150
resolver = resolverModule;
@@ -165,6 +167,7 @@ export default class Resolver {
165167
const result = await resolver(path, {
166168
basedir: options.basedir,
167169
conditions: options.conditions,
170+
defaultAsyncResolver,
168171
defaultResolver,
169172
extensions: options.extensions,
170173
moduleDirectory: options.moduleDirectory,

website/versioned_docs/version-30.0/Configuration.md

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,11 @@ type ResolverOptions = {
14761476
conditions?: Array<string>;
14771477
/** Instance of default resolver. */
14781478
defaultResolver: (path: string, options: ResolverOptions) => string;
1479+
/** Instance of default async resolver. */
1480+
defaultAsyncResolver: (
1481+
path: string,
1482+
options: ResolverOptions,
1483+
) => Promise<string>;
14791484
/** List of file extensions to search in order. */
14801485
extensions?: Array<string>;
14811486
/** List of directory names to be looked up for modules recursively. */
@@ -1491,6 +1496,8 @@ type ResolverOptions = {
14911496

14921497
The `defaultResolver` passed as an option is the Jest default resolver which might be useful when you write your custom one. It takes the same arguments as your custom synchronous one, e.g. `(path, options)` and returns a string or throws.
14931498

1499+
Similarly, the `defaultAsyncResolver` is the default async resolver which takes the same arguments and returns a promise that resolves with a string or rejects with an error.
1500+
14941501
:::
14951502

14961503
For example, if you want to respect Browserify's [`"browser"` field](https://github.com/browserify/browserify-handbook/blob/master/readme.markdown#browser-field), you can use the following resolver:
@@ -1522,25 +1529,6 @@ const config: Config = {
15221529
export default config;
15231530
```
15241531

1525-
By combining `defaultResolver` and `packageFilter` we can implement a `package.json` "pre-processor" that allows us to change how the default resolver will resolve modules. For example, imagine we want to use the field `"module"` if it is present, otherwise fallback to `"main"`:
1526-
1527-
```js
1528-
module.exports = (path, options) => {
1529-
// Call the defaultResolver, so we leverage its cache, error handling, etc.
1530-
return options.defaultResolver(path, {
1531-
...options,
1532-
// Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
1533-
packageFilter: pkg => {
1534-
return {
1535-
...pkg,
1536-
// Alter the value of `main` before resolving the package
1537-
main: pkg.module || pkg.main,
1538-
};
1539-
},
1540-
});
1541-
};
1542-
```
1543-
15441532
### `restoreMocks` \[boolean]
15451533

15461534
Default: `false`

0 commit comments

Comments
 (0)