Skip to content

Commit

Permalink
Add support for "exports" root subpath array shorthand
Browse files Browse the repository at this point in the history
Summary:
Adds support for the `"exports"` root value being an array of strings, an edge-case legacy usage that technically remains in the Node.js 19.x spec — see [`"exports"` field (Type)](https://nodejs.org/docs/latest-v19.x/api/packages.html#exports).

Behaviour:

```js
  "exports": [
    "./lib/index.js"
  ],
```

expands to

```js
  "exports": {
    "./lib/index.js": "./lib/index.js"
  },
```

Changelog: **[Experimental]** Add compatibility with legacy Node.js "exports" array formats

Reviewed By: jacdebug

Differential Revision: D44338043

fbshipit-source-id: e9e719358c2610f4ae6d120d8312fcc647727da4
  • Loading branch information
huntie authored and facebook-github-bot committed Mar 31, 2023
1 parent 4002576 commit f321cff
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 4 deletions.
21 changes: 18 additions & 3 deletions packages/metro-resolver/src/PackageExportsResolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
* @oncall react_native
*/

import type {ExportMap, FileResolution, ResolutionContext} from './types';
import type {
ExportMap,
ExportsField,
FileResolution,
ResolutionContext,
} from './types';

import path from 'path';
import InvalidModuleSpecifierError from './errors/InvalidModuleSpecifierError';
Expand Down Expand Up @@ -46,7 +51,7 @@ export function resolvePackageTargetFromExports(
* to a package-relative subpath for comparison.
*/
modulePath: string,
exportsField: ExportMap | string,
exportsField: ExportsField,
platform: string | null,
): FileResolution {
const raiseConfigError = (reason: string) => {
Expand Down Expand Up @@ -142,13 +147,23 @@ function getExportsSubpath(packagePath: string, modulePath: string): string {
* See https://nodejs.org/docs/latest-v19.x/api/packages.html#exports-sugar.
*/
function normalizeExportsField(
exportsField: ExportMap | string,
exportsField: ExportsField,
raiseConfigError: (reason: string) => void,
): ExportMap {
if (typeof exportsField === 'string') {
return {'.': exportsField};
}

if (Array.isArray(exportsField)) {
return exportsField.reduce(
(result, subpath) => ({
...result,
[subpath]: subpath,
}),
{},
);
}

const firstLevelKeys = Object.keys(exportsField);
const subpathKeys = firstLevelKeys.filter(subpathOrCondition =>
subpathOrCondition.startsWith('.'),
Expand Down
23 changes: 23 additions & 0 deletions packages/metro-resolver/src/__tests__/package-exports-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,29 @@ describe('with package exports resolution enabled', () => {
});
});

test('should expand array of strings as subpath mapping (root shorthand)', () => {
const logWarning = jest.fn();
const context = {
...baseContext,
...createPackageAccessors({
'/root/node_modules/test-pkg/package.json': JSON.stringify({
exports: ['./index.js', './foo.js'],
}),
}),
unstable_logWarning: logWarning,
};

expect(Resolver.resolve(context, 'test-pkg/index.js', null)).toEqual({
type: 'sourceFile',
filePath: '/root/node_modules/test-pkg/index.js',
});
expect(Resolver.resolve(context, 'test-pkg/foo.js', null)).toEqual({
type: 'sourceFile',
filePath: '/root/node_modules/test-pkg/foo.js',
});
expect(logWarning).not.toHaveBeenCalled();
});

describe('should resolve "exports" target directly', () => {
test('without expanding `sourceExts`', () => {
expect(Resolver.resolve(baseContext, 'test-pkg/foo.js', null)).toEqual({
Expand Down
4 changes: 3 additions & 1 deletion packages/metro-resolver/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ export type ExportMap = $ReadOnly<{
[subpathOrCondition: string]: ExportMap | string | null,
}>;

export type ExportsField = string | $ReadOnlyArray<string> | ExportMap;

export type PackageJson = $ReadOnly<{
name?: string,
main?: string,
exports?: string | ExportMap,
exports?: ExportsField,
...
}>;

Expand Down

0 comments on commit f321cff

Please sign in to comment.