Skip to content

Commit 8cad3be

Browse files
committed
Merge commit 'b9e3a55ccbf4c1228190ca3eb47bc680abba103b' into 9505-jest-resolve-async
2 parents a4b0c8e + b9e3a55 commit 8cad3be

File tree

18 files changed

+158
-54
lines changed

18 files changed

+158
-54
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323))
1414
- `[jest-jasmine2, jest-runtime]` [**BREAKING**] Use `Symbol` to pass `jest.setTimeout` value instead of `jasmine` specific logic ([#12124](https://github.com/facebook/jest/pull/12124))
1515
- `[jest-jasmine2, jest-types]` [**BREAKING**] Move all `jasmine` specific types from `@jest/types` to its own package ([#12125](https://github.com/facebook/jest/pull/12125))
16+
- `[jest-resolver]` [**BREAKING**] Add support for `package.json` `exports` ([11961](https://github.com/facebook/jest/pull/11961))
1617
- `[jest-snapshot]` [**BREAKING**] Migrate to ESM ([#12342](https://github.com/facebook/jest/pull/12342))
1718
- `[jest-worker]` [**BREAKING**] Allow only absolute `workerPath` ([#12343](https://github.com/facebook/jest/pull/12343))
1819

examples/angular/jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module.exports = {
22
moduleFileExtensions: ['ts', 'html', 'js', 'json'],
33
setupFilesAfterEnv: ['<rootDir>/setupJest.js'],
4-
testEnvironment: 'jsdom',
4+
testEnvironment: '<rootDir>/test-env.js',
55
transform: {
66
'\\.[tj]s$': ['babel-jest', {configFile: require.resolve('./.babelrc')}],
77
},

examples/angular/test-env.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const {
4+
TestEnvironment: JSDOMTestEnvironment,
5+
} = require('jest-environment-jsdom');
6+
7+
module.exports = class AngularEnv extends JSDOMTestEnvironment {
8+
exportConditions() {
9+
// we need to include `node` as `rxjs` defines `node`, `es2015`, `default`, not `browser` or `require`
10+
// https://github.com/ReactiveX/rxjs/pull/6821
11+
return super.exportConditions().concat('node');
12+
}
13+
};

packages/jest-resolve/src/__mocks__/conditions/node_modules/exports/main.js

Whitespace-only changes.

packages/jest-resolve/src/__mocks__/conditions/node_modules/exports/nestedDefault.js

Whitespace-only changes.

packages/jest-resolve/src/__mocks__/conditions/node_modules/exports/nestedRequire.js

Whitespace-only changes.

packages/jest-resolve/src/__mocks__/conditions/node_modules/exports/other.js

Whitespace-only changes.

packages/jest-resolve/src/__mocks__/conditions/node_modules/exports/package.json

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/jest-resolve/src/__mocks__/conditions/node_modules/exports/require.js

Whitespace-only changes.

packages/jest-resolve/src/__mocks__/conditions/node_modules/import/package.json

-6
This file was deleted.

packages/jest-resolve/src/__mocks__/conditions/node_modules/require/package.json

-6
This file was deleted.

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

+59-5
Original file line numberDiff line numberDiff line change
@@ -168,24 +168,78 @@ describe('findNodeModule', () => {
168168
});
169169

170170
test('resolves with import', () => {
171-
const result = Resolver.findNodeModule('import', {
171+
const result = Resolver.findNodeModule('exports', {
172172
basedir: conditionsRoot,
173173
conditions: ['import'],
174174
});
175175

176176
expect(result).toEqual(
177-
path.resolve(conditionsRoot, './node_modules/import/file.js'),
177+
path.resolve(conditionsRoot, './node_modules/exports/import.js'),
178178
);
179179
});
180180

181181
test('resolves with require', () => {
182-
const result = Resolver.findNodeModule('require', {
182+
const result = Resolver.findNodeModule('exports', {
183183
basedir: conditionsRoot,
184184
conditions: ['require'],
185185
});
186186

187187
expect(result).toEqual(
188-
path.resolve(conditionsRoot, './node_modules/require/file.js'),
188+
path.resolve(conditionsRoot, './node_modules/exports/require.js'),
189+
);
190+
});
191+
192+
test('gets default when nothing is passed', () => {
193+
const result = Resolver.findNodeModule('exports', {
194+
basedir: conditionsRoot,
195+
conditions: [],
196+
});
197+
198+
expect(result).toEqual(
199+
path.resolve(conditionsRoot, './node_modules/exports/default.js'),
200+
);
201+
});
202+
203+
test('respects order in package.json, not conditions', () => {
204+
const resultImport = Resolver.findNodeModule('exports', {
205+
basedir: conditionsRoot,
206+
conditions: ['import', 'require'],
207+
});
208+
const resultRequire = Resolver.findNodeModule('exports', {
209+
basedir: conditionsRoot,
210+
conditions: ['require', 'import'],
211+
});
212+
213+
expect(resultImport).toEqual(resultRequire);
214+
});
215+
216+
test('supports nested paths', () => {
217+
const result = Resolver.findNodeModule('exports/nested', {
218+
basedir: conditionsRoot,
219+
conditions: [],
220+
});
221+
222+
expect(result).toEqual(
223+
path.resolve(conditionsRoot, './node_modules/exports/nestedDefault.js'),
224+
);
225+
});
226+
227+
test('supports nested conditions', () => {
228+
const resultRequire = Resolver.findNodeModule('exports/deeplyNested', {
229+
basedir: conditionsRoot,
230+
conditions: ['require'],
231+
});
232+
const resultDefault = Resolver.findNodeModule('exports/deeplyNested', {
233+
basedir: conditionsRoot,
234+
conditions: [],
235+
});
236+
237+
expect(resultRequire).toEqual(
238+
path.resolve(conditionsRoot, './node_modules/exports/nestedRequire.js'),
239+
);
240+
241+
expect(resultDefault).toEqual(
242+
path.resolve(conditionsRoot, './node_modules/exports/nestedDefault.js'),
189243
);
190244
});
191245
});
@@ -319,8 +373,8 @@ describe('resolveModule', () => {
319373
const src = require.resolve('../');
320374
const resolved = resolver.resolveModule(src, 'mockJsDependency', {
321375
paths: [
322-
path.resolve(__dirname, '../../src/__tests__'),
323376
path.resolve(__dirname, '../../src/__mocks__'),
377+
path.resolve(__dirname, '../../src/__tests__'),
324378
],
325379
});
326380
expect(resolved).toBe(require.resolve('../__mocks__/mockJsDependency.js'));

packages/jest-resolve/src/defaultResolver.ts

+52-30
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {resolve} from 'path';
8+
import {isAbsolute} from 'path';
99
import pnpResolver from 'jest-pnp-resolver';
1010
import resolveAsync = require('resolve');
1111
import {
1212
Options as ResolveExportsOptions,
1313
resolve as resolveExports,
1414
} from 'resolve.exports';
15+
import slash = require('slash');
1516
import type {Config} from '@jest/types';
1617
import {
1718
PkgJson,
@@ -106,10 +107,8 @@ function getSyncResolveOptions(
106107
...options,
107108
isDirectory: isDirectorySync,
108109
isFile: isFileSync,
109-
packageFilter: createPackageFilter(
110-
options.conditions,
111-
options.packageFilter,
112-
),
110+
packageFilter: createPackageFilter(path, options.packageFilter),
111+
pathFilter: createPathFilter(path, options.conditions, options.pathFilter),
113112
preserveSymlinks: false,
114113
readPackageSync,
115114
realpathSync,
@@ -159,45 +158,68 @@ function readPackageAsync(
159158
}
160159

161160
function createPackageFilter(
162-
conditions?: Array<string>,
161+
originalPath: Config.Path,
163162
userFilter?: ResolverOptions['packageFilter'],
164163
): ResolverOptions['packageFilter'] {
165-
function attemptExportsFallback(pkg: PkgJson) {
166-
const options: ResolveExportsOptions = conditions
167-
? {conditions, unsafe: true}
168-
: // no conditions were passed - let's assume this is Jest internal and it should be `require`
169-
{browser: false, require: true};
170-
171-
try {
172-
return resolveExports(pkg, '.', options);
173-
} catch {
174-
return undefined;
175-
}
164+
if (shouldIgnoreRequestForExports(originalPath)) {
165+
return userFilter;
176166
}
177167

178-
return function packageFilter(pkg, packageDir) {
168+
return function packageFilter(pkg, ...rest) {
179169
let filteredPkg = pkg;
180170

181171
if (userFilter) {
182-
filteredPkg = userFilter(filteredPkg, packageDir);
172+
filteredPkg = userFilter(filteredPkg, ...rest);
183173
}
184174

185-
if (filteredPkg.main != null) {
186-
return filteredPkg;
187-
}
188-
189-
const indexInRoot = resolve(packageDir, './index.js');
190-
191-
// if the module contains an `index.js` file in root, `resolve` will request
192-
// that if there is no `main`. Since we don't wanna break that, add this
193-
// check
194-
if (isFileSync(indexInRoot)) {
175+
if (filteredPkg.exports == null) {
195176
return filteredPkg;
196177
}
197178

198179
return {
199180
...filteredPkg,
200-
main: attemptExportsFallback(filteredPkg),
181+
// remove `main` so `resolve` doesn't look at it and confuse the `.`
182+
// loading in `pathFilter`
183+
main: undefined,
201184
};
202185
};
203186
}
187+
188+
function createPathFilter(
189+
originalPath: Config.Path,
190+
conditions?: Array<string>,
191+
userFilter?: ResolverOptions['pathFilter'],
192+
): ResolverOptions['pathFilter'] {
193+
if (shouldIgnoreRequestForExports(originalPath)) {
194+
return userFilter;
195+
}
196+
197+
const options: ResolveExportsOptions = conditions
198+
? {conditions, unsafe: true}
199+
: // no conditions were passed - let's assume this is Jest internal and it should be `require`
200+
{browser: false, require: true};
201+
202+
return function pathFilter(pkg, path, relativePath, ...rest) {
203+
let pathToUse = relativePath;
204+
205+
if (userFilter) {
206+
pathToUse = userFilter(pkg, path, relativePath, ...rest);
207+
}
208+
209+
if (pkg.exports == null) {
210+
return pathToUse;
211+
}
212+
213+
// this `index` thing can backfire, but `resolve` adds it: https://github.com/browserify/resolve/blob/f1b51848ecb7f56f77bfb823511d032489a13eab/lib/sync.js#L192
214+
const isRootRequire =
215+
pathToUse === 'index' && !originalPath.endsWith('/index');
216+
217+
const newPath = isRootRequire ? '.' : slash(pathToUse);
218+
219+
return resolveExports(pkg, newPath, options) || pathToUse;
220+
};
221+
}
222+
223+
// if it's a relative import or an absolute path, exports are ignored
224+
const shouldIgnoreRequestForExports = (path: Config.Path) =>
225+
path.startsWith('.') || isAbsolute(path);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "NODE_PATH_dir",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "test_root",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
}
6+
}

packages/jest-runtime/src/index.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ export default class Runtime {
754754
this._virtualMocks,
755755
from,
756756
moduleName,
757-
isInternal ? undefined : {conditions: this.cjsConditions},
757+
{conditions: this.cjsConditions},
758758
);
759759
let modulePath: string | undefined;
760760

@@ -782,11 +782,9 @@ export default class Runtime {
782782
}
783783

784784
if (!modulePath) {
785-
modulePath = this._resolveModule(
786-
from,
787-
moduleName,
788-
isInternal ? undefined : {conditions: this.cjsConditions},
789-
);
785+
modulePath = this._resolveModule(from, moduleName, {
786+
conditions: this.cjsConditions,
787+
});
790788
}
791789

792790
if (this.unstable_shouldLoadAsEsm(modulePath)) {

0 commit comments

Comments
 (0)