Skip to content

Commit

Permalink
add bare minimum support for exports in package.json
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Oct 18, 2021
1 parent 8c00cc1 commit 30890ee
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 36 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Features

- `[jest-resolver]` Add support for package `exports` ([11961](https://github.com/facebook/jest/pull/11961))

### Fixes

### Chore & Maintenance
Expand Down
78 changes: 48 additions & 30 deletions packages/jest-resolve/src/defaultResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/

import {resolve} from 'path';
import {isAbsolute} from 'path';
import pnpResolver from 'jest-pnp-resolver';
import {sync as resolveSync} from 'resolve';
import {
Options as ResolveExportsOptions,
resolve as resolveExports,
} from 'resolve.exports';
import slash = require('slash');
import type {Config} from '@jest/types';
import {
PkgJson,
Expand Down Expand Up @@ -59,10 +60,8 @@ export default function defaultResolver(
...options,
isDirectory,
isFile,
packageFilter: createPackageFilter(
options.conditions,
options.packageFilter,
),
packageFilter: createPackageFilter(path, options.packageFilter),
pathFilter: createPathFilter(path, options.conditions, options.pathFilter),
preserveSymlinks: false,
readPackageSync,
realpathSync,
Expand All @@ -82,45 +81,64 @@ function readPackageSync(_: unknown, file: Config.Path): PkgJson {
}

function createPackageFilter(
conditions?: Array<string>,
originalPath: Config.Path,
userFilter?: ResolverOptions['packageFilter'],
): ResolverOptions['packageFilter'] {
function attemptExportsFallback(pkg: PkgJson) {
const options: ResolveExportsOptions = conditions
? {conditions, unsafe: true}
: // no conditions were passed - let's assume this is Jest internal and it should be `require`
{browser: false, require: true};

try {
return resolveExports(pkg, '.', options);
} catch {
return undefined;
}
if (shouldIgnoreRequestForExports(originalPath)) {
return userFilter;
}

return function packageFilter(pkg, packageDir) {
return function packageFilter(pkg, ...rest) {
let filteredPkg = pkg;

if (userFilter) {
filteredPkg = userFilter(filteredPkg, packageDir);
}

if (filteredPkg.main != null) {
return filteredPkg;
filteredPkg = userFilter(filteredPkg, ...rest);
}

const indexInRoot = resolve(packageDir, './index.js');

// if the module contains an `index.js` file in root, `resolve` will request
// that if there is no `main`. Since we don't wanna break that, add this
// check
if (isFile(indexInRoot)) {
if (filteredPkg.exports == null) {
return filteredPkg;
}

return {
...filteredPkg,
main: attemptExportsFallback(filteredPkg),
// remove `main` so `resolve` doesn't look at it and confuse the `.`
// loading in `pathFilter`
main: undefined,
};
};
}

function createPathFilter(
originalPath: Config.Path,
conditions?: Array<string>,
userFilter?: ResolverOptions['pathFilter'],
): ResolverOptions['pathFilter'] {
if (shouldIgnoreRequestForExports(originalPath)) {
return userFilter;
}

const options: ResolveExportsOptions = conditions
? {conditions, unsafe: true}
: // no conditions were passed - let's assume this is Jest internal and it should be `require`
{browser: false, require: true};

return function pathFilter(pkg, path, relativePath, ...rest) {
let pathToUse = relativePath;

if (userFilter) {
pathToUse = userFilter(pkg, path, relativePath, ...rest);
}

// this `index` thing can backfire, but `resolve` adds it: https://github.com/browserify/resolve/blob/f1b51848ecb7f56f77bfb823511d032489a13eab/lib/sync.js#L192
const isRootRequire =
!originalPath.endsWith('/index') && pathToUse === 'index';

const newPath = isRootRequire ? '.' : slash(pathToUse);

return resolveExports(pkg, newPath, options) || pathToUse;
};
}

// if it's a relative import or an absolute path, exports are ignored
const shouldIgnoreRequestForExports = (path: Config.Path) =>
path.startsWith('.') || isAbsolute(path);
10 changes: 4 additions & 6 deletions packages/jest-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ export default class Runtime {
this._virtualMocks,
from,
moduleName,
isInternal ? undefined : {conditions: this.cjsConditions},
{conditions: this.cjsConditions},
);
let modulePath: string | undefined;

Expand Down Expand Up @@ -782,11 +782,9 @@ export default class Runtime {
}

if (!modulePath) {
modulePath = this._resolveModule(
from,
moduleName,
isInternal ? undefined : {conditions: this.cjsConditions},
);
modulePath = this._resolveModule(from, moduleName, {
conditions: this.cjsConditions,
});
}

if (this.unstable_shouldLoadAsEsm(modulePath)) {
Expand Down

0 comments on commit 30890ee

Please sign in to comment.