Skip to content

Commit

Permalink
Allow out of tree platforms to work without custom metro configs (rea…
Browse files Browse the repository at this point in the history
…ct-native-community#1115)

* Allow out of tree platforms to work without custom metro configs

* Need to add the platofrm specifc InitializeCore to getModulesRunBeforeMainModule

* lock

* Add npmPackageName to schema

* Some documentation

* Use finally to restore resolveRequest
  • Loading branch information
acoates-ms authored May 8, 2020
1 parent 2d71e50 commit b92fbc6
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 5 deletions.
8 changes: 8 additions & 0 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ At the end, a map of available platforms is passed to the bundler (Metro) to mak

```ts
type PlatformConfig<ProjectParams, ProjectConfig, DependencyConfig> = {
npmPackageName?: string;
projectConfig: (string, ProjectParams) => ?ProjectConfig,
dependencyConfig: (string, ProjectParams) => ?DependencyConfig,
linkConfig: () => {
Expand All @@ -57,6 +58,13 @@ type PlatformConfig<ProjectParams, ProjectConfig, DependencyConfig> = {
};
```

### 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.
Expand Down
1 change: 1 addition & 0 deletions packages/cli-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ interface PlatformConfig<
DependencyConfig,
DependencyParams
> {
npmPackageName?: string;
projectConfig: (
projectRoot: string,
projectParams: ProjectParams | void,
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/tools/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
36 changes: 32 additions & 4 deletions packages/cli/src/tools/loadMetroConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
[
Expand All @@ -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[];
};
Expand Down Expand Up @@ -48,16 +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:
outOfTreePlatforms.length === 0
? undefined
: reactNativePlatformResolver(
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'))(),
Expand All @@ -77,10 +108,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: [],
};
Expand Down
49 changes: 49 additions & 0 deletions packages/cli/src/tools/metroPlatformResolver.ts
Original file line number Diff line number Diff line change
@@ -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);
return result;
} catch (e) {
throw e;
} finally {
context.resolveRequest = backupResolveRequest;
}
};
}
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7750,7 +7750,7 @@ metro-react-native-babel-transformer@^0.58.0:
metro-react-native-babel-preset "0.58.0"
metro-source-map "0.58.0"

[email protected]:
[email protected], 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==
Expand Down

0 comments on commit b92fbc6

Please sign in to comment.