From a76e6cead10e2d6f0f4e84c5b47da152bf61d28c Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Wed, 15 Apr 2020 15:08:41 -0700 Subject: [PATCH 1/6] Allow out of tree platforms to work without custom metro configs --- packages/cli-types/src/index.ts | 1 + packages/cli/src/tools/loadMetroConfig.ts | 25 ++++++++-- .../cli/src/tools/metroPlatformResolver.ts | 49 +++++++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 packages/cli/src/tools/metroPlatformResolver.ts diff --git a/packages/cli-types/src/index.ts b/packages/cli-types/src/index.ts index 0d8ea11a9..5acbad060 100644 --- a/packages/cli-types/src/index.ts +++ b/packages/cli-types/src/index.ts @@ -63,6 +63,7 @@ interface PlatformConfig< DependencyConfig, DependencyParams > { + npmPackageName?: string; projectConfig: ( projectRoot: string, projectParams: ProjectParams | void, diff --git a/packages/cli/src/tools/loadMetroConfig.ts b/packages/cli/src/tools/loadMetroConfig.ts index 0eae0fe0a..c0d718472 100644 --- a/packages/cli/src/tools/loadMetroConfig.ts +++ b/packages/cli/src/tools/loadMetroConfig.ts @@ -5,6 +5,7 @@ import path from 'path'; // @ts-ignore - no typed definition for the package import {loadConfig} from 'metro-config'; import {Config} from '@react-native-community/cli-types'; +import {reactNativePlatformResolver} from './metroPlatformResolver'; const INTERNAL_CALLSITES_REGEX = new RegExp( [ @@ -21,6 +22,12 @@ const INTERNAL_CALLSITES_REGEX = new RegExp( export interface MetroConfig { resolver: { + resolveRequest?: ( + context: any, + realModuleName: string, + platform: string, + moduleName: string, + ) => any; resolverMainFields: string[]; platforms: string[]; }; @@ -50,6 +57,19 @@ export interface MetroConfig { export const getDefaultConfig = (ctx: Config): MetroConfig => { return { resolver: { + resolveRequest: + Object.keys(ctx.platforms).filter( + platform => ctx.platforms[platform].npmPackageName, + ).length === 0 + ? undefined + : reactNativePlatformResolver( + Object.keys(ctx.platforms) + .filter(platform => ctx.platforms[platform].npmPackageName) + .reduce<{[platform: string]: string}>((result, platform) => { + result[platform] = ctx.platforms[platform].npmPackageName!; + return result; + }, {}), + ), resolverMainFields: ['react-native', 'browser', 'main'], platforms: [...Object.keys(ctx.platforms), 'native'], }, @@ -77,10 +97,7 @@ export const getDefaultConfig = (ctx: Config): MetroConfig => { babelTransformerPath: require.resolve( 'metro-react-native-babel-transformer', ), - assetRegistryPath: path.join( - ctx.reactNativePath, - 'Libraries/Image/AssetRegistry', - ), + assetRegistryPath: 'react-native/Libraries/Image/AssetRegistry', }, watchFolders: [], }; diff --git a/packages/cli/src/tools/metroPlatformResolver.ts b/packages/cli/src/tools/metroPlatformResolver.ts new file mode 100644 index 000000000..3b7a3ec2e --- /dev/null +++ b/packages/cli/src/tools/metroPlatformResolver.ts @@ -0,0 +1,49 @@ +/** + * 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 produce bundles for multiple out of tree platforms at a time. + * + * @param platformImplementations + * A map of platform to npm package that implements that platform + * + * Ex: + * { + * windows: 'react-native-windows' + * macos: 'react-native-macos' + * } + */ +// @ts-ignore - no typed definition for the package +import {resolve} from 'metro-resolver'; + +export function reactNativePlatformResolver(platformImplementations: { + [platform: string]: string; +}) { + return ( + context: any, + _realModuleName: string, + platform: string, + moduleName: string, + ) => { + let backupResolveRequest = context.resolveRequest; + delete context.resolveRequest; + + try { + let modifiedModuleName = moduleName; + if (platformImplementations[platform]) { + if (moduleName === 'react-native') { + modifiedModuleName = platformImplementations[platform]; + } else if (moduleName.startsWith('react-native/')) { + modifiedModuleName = `${ + platformImplementations[platform] + }/${modifiedModuleName.slice('react-native/'.length)}`; + } + } + let result = resolve(context, modifiedModuleName, platform); + context.resolveRequest = backupResolveRequest; + return result; + } catch (e) { + context.resolveRequest = backupResolveRequest; + throw e; + } + }; +} From 744a769b8d23ad3dc567b999ed85f38ac94c3353 Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Wed, 6 May 2020 14:12:59 -0700 Subject: [PATCH 2/6] Need to add the platofrm specifc InitializeCore to getModulesRunBeforeMainModule --- packages/cli/package.json | 1 + packages/cli/src/tools/loadMetroConfig.ts | 25 ++++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 1dbdf9d6e..1d5fc1cb4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -53,6 +53,7 @@ "metro-config": "^0.58.0", "metro-core": "^0.58.0", "metro-react-native-babel-transformer": "^0.58.0", + "metro-resolver": "^0.58.0", "minimist": "^1.2.0", "mkdirp": "^0.5.1", "node-stream-zip": "^1.9.1", diff --git a/packages/cli/src/tools/loadMetroConfig.ts b/packages/cli/src/tools/loadMetroConfig.ts index c0d718472..d20fa49b7 100644 --- a/packages/cli/src/tools/loadMetroConfig.ts +++ b/packages/cli/src/tools/loadMetroConfig.ts @@ -55,29 +55,40 @@ export interface MetroConfig { * Default configuration */ export const getDefaultConfig = (ctx: Config): MetroConfig => { + const outOfTreePlatforms = Object.keys(ctx.platforms).filter( + platform => ctx.platforms[platform].npmPackageName, + ); + return { resolver: { resolveRequest: - Object.keys(ctx.platforms).filter( - platform => ctx.platforms[platform].npmPackageName, - ).length === 0 + outOfTreePlatforms.length === 0 ? undefined : reactNativePlatformResolver( - Object.keys(ctx.platforms) - .filter(platform => ctx.platforms[platform].npmPackageName) - .reduce<{[platform: string]: string}>((result, platform) => { + outOfTreePlatforms.reduce<{[platform: string]: string}>( + (result, platform) => { result[platform] = ctx.platforms[platform].npmPackageName!; return result; - }, {}), + }, + {}, + ), ), resolverMainFields: ['react-native', 'browser', 'main'], platforms: [...Object.keys(ctx.platforms), 'native'], }, serializer: { + // We can include multiple copies of InitializeCore here because metro will + // only add ones that are already part of the bundle getModulesRunBeforeMainModule: () => [ require.resolve( path.join(ctx.reactNativePath, 'Libraries/Core/InitializeCore'), ), + ...outOfTreePlatforms.map(platform => + require.resolve( + `${ctx.platforms[platform] + .npmPackageName!}/Libraries/Core/InitializeCore`, + ), + ), ], getPolyfills: () => require(path.join(ctx.reactNativePath, 'rn-get-polyfills'))(), From 452b175b688ed2f12543d425f3563fce2ce70707 Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Wed, 6 May 2020 14:15:03 -0700 Subject: [PATCH 3/6] lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index bb7a4bd16..599535f86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7752,7 +7752,7 @@ metro-react-native-babel-transformer@^0.58.0: metro-react-native-babel-preset "0.58.0" metro-source-map "0.58.0" -metro-resolver@0.58.0: +metro-resolver@0.58.0, metro-resolver@^0.58.0: version "0.58.0" resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.58.0.tgz#4d03edc52e2e25d45f16688adf3b3f268ea60df9" integrity sha512-XFbAKvCHN2iWqKeiRARzEXn69eTDdJVJC7lu16S4dPQJ+Dy82dZBr5Es12iN+NmbJuFgrAuIHbpWrdnA9tOf6Q== From c5481d106bcdb677370c604ad3443b9655fb5736 Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Thu, 7 May 2020 13:22:54 -0700 Subject: [PATCH 4/6] Add npmPackageName to schema --- packages/cli/src/tools/config/schema.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cli/src/tools/config/schema.ts b/packages/cli/src/tools/config/schema.ts index fa962c9db..6ce6d3537 100644 --- a/packages/cli/src/tools/config/schema.ts +++ b/packages/cli/src/tools/config/schema.ts @@ -83,6 +83,7 @@ export const dependencyConfig = t platforms: map( t.string(), t.object({ + npmPackageName: t.string().optional(), dependencyConfig: t.func(), projectConfig: t.func(), linkConfig: t.func(), @@ -178,6 +179,7 @@ export const projectConfig = t platforms: map( t.string(), t.object({ + npmPackageName: t.string().optional(), dependencyConfig: t.func(), projectConfig: t.func(), linkConfig: t.func(), From 121ea011ab4c3ca94c7e51ee9915f86ff819d391 Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Thu, 7 May 2020 14:02:32 -0700 Subject: [PATCH 5/6] Some documentation --- docs/platforms.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/platforms.md b/docs/platforms.md index 8156e3680..58b54f2d2 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -40,6 +40,7 @@ At the end, a map of available platforms is passed to the bundler (Metro) to mak ```ts type PlatformConfig = { + npmPackageName?: string; projectConfig: (string, ProjectParams) => ?ProjectConfig, dependencyConfig: (string, ProjectParams) => ?DependencyConfig, linkConfig: () => { @@ -57,6 +58,13 @@ type PlatformConfig = { }; ``` +### npmPackageName + +Returns the name of the npm package that should be used as the source for react-native JS code for platforms that provide platform specific overrides to core JS files. This causes the default metro config to redirect imports of react-native to another package based when bundling for that platform. The package specified should provide a complete react-native implementation for that platform. + +If this property is not specified, it is assumed that the code in core `react-native` works for the platform. + + ### projectConfig Returns a project configuration for a given platform or `null`, when no project found. This is later used inside `linkConfig` to perform linking and unlinking. From 001874e664ca84a73fc0a631b7c27c9634b40e4a Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Thu, 7 May 2020 14:08:22 -0700 Subject: [PATCH 6/6] Use finally to restore resolveRequest --- packages/cli/src/tools/metroPlatformResolver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/tools/metroPlatformResolver.ts b/packages/cli/src/tools/metroPlatformResolver.ts index 3b7a3ec2e..fbaa8946a 100644 --- a/packages/cli/src/tools/metroPlatformResolver.ts +++ b/packages/cli/src/tools/metroPlatformResolver.ts @@ -39,11 +39,11 @@ export function reactNativePlatformResolver(platformImplementations: { } } let result = resolve(context, modifiedModuleName, platform); - context.resolveRequest = backupResolveRequest; return result; } catch (e) { - context.resolveRequest = backupResolveRequest; throw e; + } finally { + context.resolveRequest = backupResolveRequest; } }; }