Skip to content

Commit

Permalink
Generate ThirdpartyFabricComponentsProvider to extend RCTFabricCompon…
Browse files Browse the repository at this point in the history
…entsPlugins

Summary:
Currently, we don't have a way to extend RCTFabricComponentsPlugins in OSS. This diff  adds a codegen to generate ThirdpartyFabricComponentsProviderwhich will be used to extend RCTFabricComponentsPlugins.

It works almost exactly the same as RCTFabricComponentsPlugins, and in the future we might want to consider merging them together.

Changelog: [internal]

Reviewed By: hramos

Differential Revision: D32128760

fbshipit-source-id: b4f3a082f94c3053251cc6a0323c488f649deaa9
  • Loading branch information
sota000 authored and facebook-github-bot committed Nov 10, 2021
1 parent b27a83b commit d065b06
Show file tree
Hide file tree
Showing 7 changed files with 454 additions and 9 deletions.
68 changes: 59 additions & 9 deletions packages/react-native-codegen/src/generators/RNCodegen.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,28 @@ const generatePropsJavaDelegate = require('./components/GeneratePropsJavaDelegat
const generateTests = require('./components/GenerateTests.js');
const generateShadowNodeCpp = require('./components/GenerateShadowNodeCpp.js');
const generateShadowNodeH = require('./components/GenerateShadowNodeH.js');
const generateThirdPartyFabricComponentsProviderObjCpp = require('./components/GenerateThirdPartyFabricComponentsProviderObjCpp.js');
const generateThirdPartyFabricComponentsProviderH = require('./components/GenerateThirdPartyFabricComponentsProviderH.js');
const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');
const path = require('path');
const schemaValidator = require('../SchemaValidator.js');

import type {SchemaType} from '../CodegenSchema';

type Options = $ReadOnly<{
type LibraryOptions = $ReadOnly<{
libraryName: string,
schema: SchemaType,
outputDirectory: string,
packageName?: string, // Some platforms have a notion of package, which should be configurable.
assumeNonnull: boolean,
}>;

type Generators =
type SchemasOptions = $ReadOnly<{
schemas: {[string]: SchemaType},
outputDirectory: string,
}>;

type LibraryGenerators =
| 'componentsAndroid'
| 'componentsIOS'
| 'descriptors'
Expand All @@ -60,12 +67,19 @@ type Generators =
| 'modulesCxx'
| 'modulesIOS';

type Config = $ReadOnly<{
generators: Array<Generators>,
type SchemasGenerators = 'providerIOS';

type LibraryConfig = $ReadOnly<{
generators: Array<LibraryGenerators>,
test?: boolean,
}>;

type SchemasConfig = $ReadOnly<{
generators: Array<SchemasGenerators>,
test?: boolean,
}>;

const GENERATORS = {
const LIBRARY_GENERATORS = {
descriptors: [generateComponentDescriptorH.generate],
events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
props: [
Expand Down Expand Up @@ -113,6 +127,13 @@ const GENERATORS = {
],
};

const SCHEMAS_GENERATORS = {
providerIOS: [
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH.generate,
],
};

function writeMapToFiles(map: Map<string, string>, outputDir: string) {
let success = true;
map.forEach((contents: string, fileName: string) => {
Expand Down Expand Up @@ -153,14 +174,20 @@ function checkFilesForChanges(

module.exports = {
generate(
{libraryName, schema, outputDirectory, packageName, assumeNonnull}: Options,
{generators, test}: Config,
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
}: LibraryOptions,
{generators, test}: LibraryConfig,
): boolean {
schemaValidator.validate(schema);

const generatedFiles = [];
for (const name of generators) {
for (const generator of GENERATORS[name]) {
for (const generator of LIBRARY_GENERATORS[name]) {
generatedFiles.push(
...generator(libraryName, schema, packageName, assumeNonnull),
);
Expand All @@ -175,7 +202,30 @@ module.exports = {

return writeMapToFiles(filesToUpdate, outputDirectory);
},
generateViewConfig({libraryName, schema}: Options): string {
generateFromSchemas(
{schemas, outputDirectory}: SchemasOptions,
{generators, test}: SchemasConfig,
): boolean {
Object.keys(schemas).forEach(libraryName =>
schemaValidator.validate(schemas[libraryName]),
);

const generatedFiles = [];
for (const name of generators) {
for (const generator of SCHEMAS_GENERATORS[name]) {
generatedFiles.push(...generator(schemas));
}
}

const filesToUpdate = new Map([...generatedFiles]);

if (test === true) {
return checkFilesForChanges(filesToUpdate, outputDirectory);
}

return writeMapToFiles(filesToUpdate, outputDirectory);
},
generateViewConfig({libraryName, schema}: LibraryOptions): string {
schemaValidator.validate(schema);

const result = generateViewConfigJs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Copyright (c) Facebook, Inc. and its 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
* @format
*/

'use strict';

import type {SchemaType} from '../../CodegenSchema';

// File path -> contents
type FilesOutput = Map<string, string>;

const template = `
/*
* ${'C'}opyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderH
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#import <React/RCTComponentViewProtocol.h>
#ifdef __cplusplus
extern "C" {
#endif
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name);
::_LOOKUP_FUNCS_::
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
`;

const lookupFuncTemplate = `
Class<RCTComponentViewProtocol> ::_CLASSNAME_::Cls(void) __attribute__((used)); // ::_LIBRARY_NAME_::
`.trim();

module.exports = {
generate(schemas: {[string]: SchemaType}): FilesOutput {
const fileName = 'RCTThirdPartyFabricComponentsProvider.h';

const lookupFuncs = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
return Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}

const {components} = module;
// No components in this module
if (components == null) {
return null;
}

return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return;
}

return lookupFuncTemplate
.replace(/::_LIBRARY_NAME_::/g, libraryName)
.replace(/::_CLASSNAME_::/g, componentName);
})
.join('\n');
})
.filter(Boolean)
.join('\n');
})
.join('\n');

const replacedTemplate = template.replace(
/::_LOOKUP_FUNCS_::/g,
lookupFuncs,
);

return new Map([[fileName, replacedTemplate]]);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Copyright (c) Facebook, Inc. and its 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
* @format
*/

'use strict';

import type {SchemaType} from '../../CodegenSchema';

// File path -> contents
type FilesOutput = Map<string, string>;

const template = `
/**
* ${'C'}opyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderCpp
*/
// OSS-compatibility layer
#import "RCTThirdPartyFabricComponentsProvider.h"
#import <string>
#import <unordered_map>
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
::_LOOKUP_MAP_::
};
auto p = sFabricComponentsClassMap.find(name);
if (p != sFabricComponentsClassMap.end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
`;

const lookupMapTemplate = `
{"::_CLASSNAME_::", ::_CLASSNAME_::Cls}, // ::_LIBRARY_NAME_::`;

module.exports = {
generate(schemas: {[string]: SchemaType}): FilesOutput {
const fileName = 'RCTThirdPartyFabricComponentsProvider.mm';

const lookupMap = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
return Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}

const {components} = module;
// No components in this module
if (components == null) {
return null;
}

return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
const replacedTemplate = lookupMapTemplate
.replace(/::_CLASSNAME_::/g, componentName)
.replace(/::_LIBRARY_NAME_::/g, libraryName);

return replacedTemplate;
});
})
.filter(Boolean);
})
.join('\n');

const replacedTemplate = template.replace(/::_LOOKUP_MAP_::/g, lookupMap);

return new Map([[fileName, replacedTemplate]]);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+react_native
* @flow strict-local
* @format
*/

'use strict';

const fixtures = require('../__test_fixtures__/fixtures.js');
const generator = require('../GenerateThirdPartyFabricComponentsProviderH.js');

describe('GenerateThirdPartyFabricComponentsProviderH', () => {
it(`can generate fixtures`, () => {
expect(generator.generate(fixtures)).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+react_native
* @flow strict-local
* @format
*/

'use strict';

const fixtures = require('../__test_fixtures__/fixtures.js');
const generator = require('../GenerateThirdPartyFabricComponentsProviderObjCpp.js');

describe('GenerateThirdPartyFabricComponentsProviderObjCpp', () => {
it(`can generate fixtures`, () => {
expect(generator.generate(fixtures)).toMatchSnapshot();
});
});
Loading

0 comments on commit d065b06

Please sign in to comment.