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

[jest-resolve] Add support for packageFilter for custom resolvers #10393

Merged
merged 9 commits into from
Aug 12, 2020
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-resolve]` Add support for `packageFilter` on custom resolver ([#10393](https://github.com/facebook/jest/pull/10393))

### Fixes

### Chore & Maintenance
Expand Down
31 changes: 31 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ This option allows the use of a custom resolver. This resolver must be a node mo
"extensions": [string],
"moduleDirectory": [string],
"paths": [string],
"packageFilter": "function(pkg, pkgdir)",
"rootDir": [string]
}
```
Expand All @@ -712,6 +713,36 @@ For example, if you want to respect Browserify's [`"browser"` field](https://git
}
```

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"`:
SimenB marked this conversation as resolved.
Show resolved Hide resolved

```json
{
...
"jest": {
"resolver": "my-module-resolve"
}
}
```

```js
// my-module-resolve package

module.exports = (request, options) => {
// Call the defaultResolver, so we leverage its cache, error handling, etc.
return options.defaultResolver(request, {
...options,
// Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
packageFilter: pkg => {
return {
...pkg,
// Alter the value of `main` before resolving the package
main: pkg.module || pkg.main,
};
},
});
};
```

### `restoreMocks` [boolean]

Default: `false`
Expand Down
37 changes: 37 additions & 0 deletions packages/jest-resolve/src/__tests__/resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import * as path from 'path';
import * as fs from 'graceful-fs';
import {ModuleMap} from 'jest-haste-map';
import {sync as resolveSync} from 'resolve';
import Resolver = require('../');
import userResolver from '../__mocks__/userResolver';
import nodeModulesPaths from '../nodeModulesPaths';
Expand All @@ -17,8 +18,23 @@ import type {ResolverConfig} from '../types';

jest.mock('../__mocks__/userResolver');

// Do not fully mock `resolve` because it is used by Jest. Doing it will crash
// in very strange ways. Instead just spy on the method `sync`.
jest.mock('resolve', () => {
const originalModule = jest.requireActual('resolve');
return {
...originalModule,
sync: jest.spyOn(originalModule, 'sync'),
};
});

const mockResolveSync = <
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full disclosure: I've never used Jest+TS, I don't know if there is a better way of doing this.

jest.Mock<ReturnType<typeof resolveSync>, Parameters<typeof resolveSync>>
>resolveSync;

beforeEach(() => {
userResolver.mockClear();
mockResolveSync.mockClear();
});

describe('isCoreModule', () => {
Expand Down Expand Up @@ -93,6 +109,27 @@ describe('findNodeModule', () => {
rootDir: undefined,
});
});

it('passes packageFilter to the resolve module when using the default resolver', () => {
const packageFilter = jest.fn();

// A resolver that delegates to defaultResolver with a packageFilter implementation
userResolver.mockImplementation((request, opts) =>
opts.defaultResolver(request, {...opts, packageFilter}),
);

Resolver.findNodeModule('test', {
basedir: '/',
resolver: require.resolve('../__mocks__/userResolver'),
});

expect(mockResolveSync).toHaveBeenCalledWith(
'test',
expect.objectContaining({
packageFilter,
}),
);
});
});

describe('resolveModule', () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/jest-resolve/src/defaultResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import * as fs from 'graceful-fs';
import {sync as resolveSync} from 'resolve';
import {Opts as ResolveOpts, sync as resolveSync} from 'resolve';
import pnpResolver from 'jest-pnp-resolver';
import {tryRealpath} from 'jest-util';
import type {Config} from '@jest/types';
Expand All @@ -20,6 +20,7 @@ type ResolverOptions = {
moduleDirectory?: Array<string>;
paths?: Array<Config.Path>;
rootDir?: Config.Path;
packageFilter?: ResolveOpts['packageFilter'];
};

declare global {
Expand All @@ -45,6 +46,7 @@ export default function defaultResolver(
isDirectory,
isFile,
moduleDirectory: options.moduleDirectory,
packageFilter: options.packageFilter,
paths: options.paths,
preserveSymlinks: false,
realpathSync,
Expand Down