Skip to content

Commit

Permalink
Add "exports" special case handling for @babel/runtime (facebook#990)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#990

This is a workaround for facebook#984. It adds a specific exception in the resolver for `babel/runtime` to prevent asserting the `"import"` condition name on these modules (when using Package Exports). This is necessary so that the CJS version of these Babel helpers are resolved, which enable CJS/MJS interop for all other modules (given our current strategy of resolving both `"require"` and `"import"` in all other packages and using Babel-driven ESM compatibility).

This workaround is removable if/when any of:
- babel/babel#15643 is merged and updated in React Native.
- We implement dynamic handling of `"require"` and `"import"` conditions via ESM support in a future version of Metro.

Changelog: **[Experimental]** Fix `babel/runtime` issue when using Package Exports

Reviewed By: motiz88

Differential Revision: D46107056

fbshipit-source-id: 5c34c5b3e1aaec16b998499401c81fadee34c30d
  • Loading branch information
huntie authored and facebook-github-bot committed May 24, 2023
1 parent 40f9f06 commit 1e424f2
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 49 deletions.
99 changes: 51 additions & 48 deletions packages/metro-resolver/src/__tests__/package-exports-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -871,74 +871,38 @@ describe('with package exports resolution enabled', () => {
const context = {
...createResolutionContext({
'/root/src/main.js': '',
'/root/node_modules/@babel/runtime/package.json': JSON.stringify({
'/root/node_modules/test-pkg/package.json': JSON.stringify({
exports: {
'./helpers/typeof': [
{
node: './helpers/typeof.js',
import: './helpers/esm/typeof.js',
default: './helpers/typeof.js',
},
'./helpers/typeof.js',
],
'./helpers/interopRequireDefault': [
'.': [
{
node: './helpers/interopRequireDefault.js',
import: './helpers/esm/interopRequireDefault.js',
default: './helpers/interopRequireDefault.js',
'react-native': './index-react-native.js',
default: './index.js',
},
'./helpers/interopRequireDefault.js',
'./index.js',
],
},
}),
'/root/node_modules/@babel/runtime/helpers/interopRequireDefault.js':
'',
'/root/node_modules/@babel/runtime/helpers/esm/interopRequireDefault.js':
'',
'/root/node_modules/@babel/runtime/helpers/esm/typeof.js': '',
'/root/node_modules/test-pkg/index.js': '',
'/root/node_modules/test-pkg/index-react-native.js': '',
}),
originModulePath: '/root/src/main.js',
unstable_conditionNames: [],
unstable_enablePackageExports: true,
};

expect(
Resolver.resolve(
context,
'@babel/runtime/helpers/interopRequireDefault',
null,
),
).toEqual({
type: 'sourceFile',
filePath:
'/root/node_modules/@babel/runtime/helpers/interopRequireDefault.js',
});
expect(
Resolver.resolve(
{...context, unstable_conditionNames: ['import']},
'@babel/runtime/helpers/interopRequireDefault',
null,
),
).toEqual({
expect(Resolver.resolve(context, 'test-pkg', null)).toEqual({
type: 'sourceFile',
filePath:
'/root/node_modules/@babel/runtime/helpers/esm/interopRequireDefault.js',
filePath: '/root/node_modules/test-pkg/index.js',
});
// Check internal self-reference case explicitly!
expect(
Resolver.resolve(
{
...context,
originModulePath:
'/root/node_modules/@babel/runtime/helpers/esm/interopRequireDefault.js',
unstable_conditionNames: ['import'],
},
'@babel/runtime/helpers/typeof',
{...context, unstable_conditionNames: ['react-native']},
'test-pkg',
null,
),
).toEqual({
type: 'sourceFile',
filePath: '/root/node_modules/@babel/runtime/helpers/esm/typeof.js',
filePath: '/root/node_modules/test-pkg/index-react-native.js',
});
});

Expand Down Expand Up @@ -1035,4 +999,43 @@ describe('with package exports resolution enabled', () => {
});
});
});

describe('@babel/runtime compatibility (special case)', () => {
test('should never assert "import" condition', () => {
const context = {
...createResolutionContext({
'/root/src/main.js': '',
'/root/node_modules/@babel/runtime/package.json': JSON.stringify({
exports: {
'./helpers/interopRequireDefault': [
{
node: './helpers/interopRequireDefault.js',
import: './helpers/esm/interopRequireDefault.js',
default: './helpers/interopRequireDefault.js',
},
'./helpers/interopRequireDefault.js',
],
},
}),
'/root/node_modules/@babel/runtime/helpers/interopRequireDefault.js':
'',
}),
originModulePath: '/root/src/main.js',
unstable_conditionNames: ['require', 'import'],
unstable_enablePackageExports: true,
};

expect(
Resolver.resolve(
context,
'@babel/runtime/helpers/interopRequireDefault',
null,
),
).toEqual({
type: 'sourceFile',
filePath:
'/root/node_modules/@babel/runtime/helpers/interopRequireDefault.js',
});
});
});
});
14 changes: 13 additions & 1 deletion packages/metro-resolver/src/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,21 @@ function resolvePackage(
const exportsField = pkg?.packageJson.exports;

if (pkg != null && exportsField != null) {
let conditionNamesOverride = context.unstable_conditionNames;

// HACK!: Do not assert the "import" condition for `@babel/runtime`. This
// is a workaround for ESM <-> CJS interop, as we need the CJS versions of
// `@babel/runtime` helpers.
// TODO(T154157178): Remove with better "require"/"import" solution
if (pkg.packageJson.name === '@babel/runtime') {
conditionNamesOverride = context.unstable_conditionNames.filter(
condition => condition !== 'import',
);
}

try {
const packageExportsResult = resolvePackageTargetFromExports(
context,
{...context, unstable_conditionNames: conditionNamesOverride},
pkg.rootPath,
modulePath,
exportsField,
Expand Down

0 comments on commit 1e424f2

Please sign in to comment.