From b2392353422a18189830ea216fa6094bc0279da1 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 24 May 2023 03:36:36 -0700 Subject: [PATCH] Add "exports" special case handling for @babel/runtime (#990) Summary: Pull Request resolved: https://github.com/facebook/metro/pull/990 This is a workaround for https://github.com/facebook/metro/issues/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: - https://github.com/babel/babel/pull/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 Differential Revision: D46107056 fbshipit-source-id: e3b8f453d98a45c7b10df1e6c2ff97b4f017529f --- .../src/__tests__/package-exports-test.js | 99 ++++++++++--------- packages/metro-resolver/src/resolve.js | 14 ++- 2 files changed, 64 insertions(+), 49 deletions(-) diff --git a/packages/metro-resolver/src/__tests__/package-exports-test.js b/packages/metro-resolver/src/__tests__/package-exports-test.js index 5c5e84d70e..89f8aad7ae 100644 --- a/packages/metro-resolver/src/__tests__/package-exports-test.js +++ b/packages/metro-resolver/src/__tests__/package-exports-test.js @@ -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', }); }); @@ -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', + }); + }); + }); }); diff --git a/packages/metro-resolver/src/resolve.js b/packages/metro-resolver/src/resolve.js index 1f847a8b06..e67ece38a3 100644 --- a/packages/metro-resolver/src/resolve.js +++ b/packages/metro-resolver/src/resolve.js @@ -260,9 +260,21 @@ function resolvePackage( const exportsField = pkg?.packageJson.exports; if (pkg != null && exportsField != null) { + let conditionNamesOverride = context.unstable_conditionNames; + + // Do not assert the "import" condition for requests to `@babel/runtime`. + // This is a workaround for ESM <-> CJS interop, as we need the CJS + // versions of `@babel/runtime` helpers. Remove once we dynamically + // assert "require" and "import" conditions based on import kind. + 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,