Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/

import type {CustomResolutionContext} from 'metro-resolver/src/types';

import {reactNativePlatformResolver} from '../metroPlatformResolver';

jest.dontMock('../metroPlatformResolver');

describe('reactNativePlatformResolver', () => {
let resolveRequest = jest.fn();
let metroContextMock: Partial<{...CustomResolutionContext}> = {
customResolverOptions: {},
resolveRequest,
};
beforeEach(() => {
resolveRequest.mockReset();
});

test('forwards non react-native module', () => {
reactNativePlatformResolver({visionos: 'react-native-visionos'})(
// $FlowFixMe[incompatible-call]
metroContextMock,
'module-name',
'ios',
);

expect(resolveRequest).toHaveBeenCalledWith(
metroContextMock,
'module-name',
'ios',
);
});

test('rewrites react-native module to out-of-tree platform "macos"', () => {
reactNativePlatformResolver({macos: 'react-native-macos'})(
// $FlowFixMe[incompatible-call]
metroContextMock,
'react-native',
'macos',
);

expect(resolveRequest).toHaveBeenCalledWith(
metroContextMock,
'react-native-macos',
'macos',
);
});

test('rewrites internal react-native/Libraries/Utilities/Platform path to out-of-tree platform "macos"', () => {
reactNativePlatformResolver({macos: 'react-native-macos'})(
// $FlowFixMe[incompatible-call]
metroContextMock,
'react-native/Libraries/Utilities/Platform',
'macos',
);

expect(resolveRequest).toHaveBeenCalledWith(
metroContextMock,
'react-native-macos/Libraries/Utilities/Platform',
'macos',
);
});

test('rewrites react-native module to out-of-tree variant "visionos" based on "ios" platform', () => {
const metroContextMockWithVariant: Partial<{...CustomResolutionContext}> = {
...metroContextMock,
customResolverOptions: {variant: 'visionos'},
};
reactNativePlatformResolver({visionos: 'react-native-visionos'})(
// $FlowFixMe[incompatible-call]
metroContextMockWithVariant,
'react-native',
'ios',
);

expect(resolveRequest).toHaveBeenCalledWith(
metroContextMockWithVariant,
'react-native-visionos',
'ios',
);
});

test('rewrites internal react-native/Libraries/Utilities/Platform path to out-of-tree variant "visionos" based on "ios" platform', () => {
const metroContextMockWithVariant: Partial<{...CustomResolutionContext}> = {
...metroContextMock,
customResolverOptions: {variant: 'visionos'},
};
reactNativePlatformResolver({visionos: 'react-native-visionos'})(
// $FlowFixMe[incompatible-call]
metroContextMockWithVariant,
'react-native/Libraries/Utilities/Platform',
'ios',
);

expect(resolveRequest).toHaveBeenCalledWith(
metroContextMockWithVariant,
'react-native-visionos/Libraries/Utilities/Platform',
'ios',
);
});
});
21 changes: 17 additions & 4 deletions packages/community-cli-plugin/src/utils/metroPlatformResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ import type {CustomResolver} from 'metro-resolver';

/**
* This is an implementation of a metro resolveRequest option which will remap react-native imports
* to different npm packages based on the platform requested. This allows a single metro instance/config
* to different npm packages based on the platform requested. This allows a single metro instance/config
* to produce bundles for multiple out of tree platforms at a time.
*
* This resolver also supports custom `variant` option, which is intended to be used for resolving
* different variants of the same platform, like "visionos" or "tvos" based on "ios".
*
* Ex (in the devserver query string):
* - `?platform=ios&resolver.variant=visionos`
* - `?platform=ios&resolver.variant=tvos`
*
* @param platformImplementations
* A map of platform to npm package that implements that platform
*
Expand All @@ -29,13 +36,19 @@ export function reactNativePlatformResolver(platformImplementations: {
[platform: string]: string,
}): CustomResolver {
return (context, moduleName, platform) => {
let platformOrVariant: string | null =
// $FlowFixMe
context.customResolverOptions.variant || platform;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure how to cope with this type, so I disabled the checker. Would love to resolve it properly
image

let modifiedModuleName = moduleName;
if (platform != null && platformImplementations[platform]) {
if (
platformOrVariant != null &&
platformImplementations[platformOrVariant]
) {
if (moduleName === 'react-native') {
modifiedModuleName = platformImplementations[platform];
modifiedModuleName = platformImplementations[platformOrVariant];
} else if (moduleName.startsWith('react-native/')) {
modifiedModuleName = `${
platformImplementations[platform]
platformImplementations[platformOrVariant]
}/${modifiedModuleName.slice('react-native/'.length)}`;
}
}
Expand Down