Skip to content

Commit 38b96f8

Browse files
huntiefacebook-github-bot
authored andcommitted
Implement package exports subpath resolution (experimental)
Summary: - Initial experimental support for Package Exports subpaths + tests. - `resolveFileOrDir` is renamed `resolvePackage`, and `"exports"` main entry point resolution logic is lifted into this function and shared with subpath handling. Changelog: **[Experimental]** Add package exports subpath resolution Reviewed By: robhogan Differential Revision: D43055160 fbshipit-source-id: eca0cfafe4bb10be5b6b2ff4f6cb0cf6b84fb495
1 parent 69e29da commit 38b96f8

File tree

5 files changed

+338
-118
lines changed

5 files changed

+338
-118
lines changed

packages/metro-resolver/src/PackageExportsResolve.js

+49-24
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,57 @@
99
* @oncall react_native
1010
*/
1111

12-
import type {ExportMap, PackageInfo, ResolutionContext} from './types';
12+
import type {ExportMap, ResolutionContext, SourceFileResolution} from './types';
1313

14+
import path from 'path';
1415
import invariant from 'invariant';
16+
import toPosixPath from './utils/toPosixPath';
1517

1618
/**
17-
* Resolve the main entry point subpath for a package.
19+
* Resolve a package subpath based on the entry points defined in the package's
20+
* "exports" field. If there is no match for the given subpath (which may be
21+
* augmented by resolution of conditional exports for the passed `context`),
22+
* returns `null`.
1823
*
1924
* Implements modern package resolution behaviour based on the [Package Entry
2025
* Points spec](https://nodejs.org/docs/latest-v19.x/api/packages.html#package-entry-points).
2126
*/
22-
export function getPackageEntryPointFromExports(
27+
export function resolvePackageTargetFromExports(
2328
context: ResolutionContext,
24-
packageInfo: PackageInfo,
29+
/**
30+
* The path to the containing npm package directory.
31+
*/
32+
packageRoot: string,
33+
/**
34+
* The unresolved absolute path to the target module. This will be converted
35+
* to a package-relative subpath for comparison.
36+
*/
37+
modulePath: string,
38+
exportsField: ExportMap | string,
2539
platform: string | null,
26-
): ?string {
27-
return matchSubpathFromExports('.', context, packageInfo, platform);
40+
): SourceFileResolution | null {
41+
const packageSubpath = path.relative(packageRoot, modulePath);
42+
const subpath =
43+
// Convert to prefixed POSIX path for "exports" lookup
44+
packageSubpath === '' ? '.' : './' + toPosixPath(packageSubpath);
45+
const match = matchSubpathFromExports(
46+
context,
47+
subpath,
48+
exportsField,
49+
platform,
50+
);
51+
52+
if (match != null) {
53+
const filePath = path.join(packageRoot, match);
54+
55+
if (context.doesFileExist(filePath)) {
56+
return {type: 'sourceFile', filePath};
57+
}
58+
// TODO(T143882479): Throw InvalidPackageConfigurationError (entry point
59+
// missing) and log as warning in calling context.
60+
}
61+
62+
return null;
2863
}
2964

3065
/**
@@ -33,22 +68,16 @@ export function getPackageEntryPointFromExports(
3368
* Implements modern package resolution behaviour based on the [Package Entry
3469
* Points spec](https://nodejs.org/docs/latest-v19.x/api/packages.html#package-entry-points).
3570
*/
36-
export function matchSubpathFromExports(
71+
function matchSubpathFromExports(
72+
context: ResolutionContext,
3773
/**
3874
* The package-relative subpath (beginning with '.') to match against either
3975
* an exact subpath key or subpath pattern key in "exports".
4076
*/
4177
subpath: string,
42-
context: ResolutionContext,
43-
{packageJson}: PackageInfo,
78+
exportsField: ExportMap | string,
4479
platform: string | null,
4580
): ?string {
46-
const {exports: exportsField} = packageJson;
47-
48-
if (exportsField == null) {
49-
return null;
50-
}
51-
5281
const conditionNames = new Set([
5382
'default',
5483
...context.unstable_conditionNames,
@@ -57,15 +86,7 @@ export function matchSubpathFromExports(
5786
: []),
5887
]);
5988

60-
let exportMap: FlattenedExportMap;
61-
62-
try {
63-
exportMap = reduceExportsField(exportsField, conditionNames);
64-
} catch (e) {
65-
// TODO(T143882479): Log a warning if the "exports" field cannot be parsed
66-
// NOTE: Under strict mode, this should throw an InvalidPackageConfigurationError
67-
return null;
68-
}
89+
const exportMap = reduceExportsField(exportsField, conditionNames);
6990

7091
return exportMap[subpath];
7192
}
@@ -93,6 +114,8 @@ function reduceExportsField(
93114
subpathOrCondition.startsWith('.'),
94115
);
95116

117+
// TODO(T143882479): Throw InvalidPackageConfigurationError and log as
118+
// warning in calling context.
96119
invariant(
97120
subpathKeys.length === 0 || subpathKeys.length === firstLevelKeys.length,
98121
'"exports" object cannot have keys mapping both subpaths and conditions ' +
@@ -125,6 +148,8 @@ function reduceExportsField(
125148
value => value != null && !value.startsWith('./'),
126149
);
127150

151+
// TODO(T143882479): Throw InvalidPackageConfigurationError and log as
152+
// warning in calling context.
128153
invariant(
129154
invalidValues.length === 0,
130155
'One or more mappings for subpaths in "exports" is invalid. All values ' +

0 commit comments

Comments
 (0)