Align Codegen between iOS and Android (#33864)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/33864

This Diff aligns the way in which iOS and Android codegen the modules and components.
Android takes all the JS in the project root folder and creates components starting from there.
iOS used to required to specify a specific path for each component, within a JSON field called `libraries`. This Diff let iOS work in the same way as android does

**Backward compatibility:** This diff still support the old way for iOS, but we are deprecating it.

## Changelog
[iOS][Added] - Support codegen from a single folder

Reviewed By: cortinico

Differential Revision: D36473005

fbshipit-source-id: 1e8cf0f9764f529c02e948984c74d1982a84030b
This commit is contained in:
Riccardo Cipolleschi 2022-05-19 05:52:40 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 1c1fbce2d1
Коммит 05aaba9514
7 изменённых файлов: 309 добавлений и 71 удалений

Просмотреть файл

@ -65,7 +65,7 @@ rn_library(
"pfh:ReactNative_CommonInfrastructurePlaceholder",
"supermodule:xplat/default/public.react_native.playground",
],
native_component_spec_name = "MyNativeViewSpec",
native_component_spec_name = "AppSpecs",
skip_processors = True,
visibility = ["PUBLIC"],
deps = [
@ -317,7 +317,7 @@ rn_xplat_cxx_library2(
reexport_all_header_dependencies = False,
visibility = ["PUBLIC"],
deps = [
":generated_components-MyNativeViewSpec",
":generated_components-AppSpecs",
"//xplat/js/react-native-github:RCTFabricComponentViewsBase",
],
)

Просмотреть файл

@ -7,10 +7,10 @@
#import "RNTMyNativeViewComponentView.h"
#import <react/renderer/components/MyNativeViewSpec/ComponentDescriptors.h>
#import <react/renderer/components/MyNativeViewSpec/EventEmitters.h>
#import <react/renderer/components/MyNativeViewSpec/Props.h>
#import <react/renderer/components/MyNativeViewSpec/RCTComponentViewHelpers.h>
#import <react/renderer/components/AppSpecs/ComponentDescriptors.h>
#import <react/renderer/components/AppSpecs/EventEmitters.h>
#import <react/renderer/components/AppSpecs/Props.h>
#import <react/renderer/components/AppSpecs/RCTComponentViewHelpers.h>
#import "RCTFabricComponentsPlugins.h"

Просмотреть файл

@ -31,21 +31,8 @@
"ws": "^6.1.4"
},
"codegenConfig": {
"libraries": [
{
"name": "ScreenshotManagerSpec",
"type": "modules",
"ios": {},
"android": {},
"jsSrcsDir": "NativeModuleExample"
},
{
"name": "MyNativeViewSpec",
"type": "components",
"ios": {},
"android": {},
"jsSrcsDir": "NativeComponentExample/js"
}
]
"name": "AppSpecs",
"type": "all",
"jsSrcsDir": "."
}
}

Просмотреть файл

@ -10,6 +10,48 @@
'use-strict';
const SINGLE_LIBRARY_CODEGEN_CONFIG = {
codegenConfig: {
libraries: [
{
name: 'react-native',
type: 'all',
jsSrcsDir: '.',
},
],
},
};
const MULTIPLE_LIBRARIES_CODEGEN_CONFIG = {
codegenConfig: {
libraries: [
{
name: 'react-native',
type: 'all',
jsSrcsDir: '.',
},
{
name: 'my-component',
type: 'components',
jsSrcsDir: 'component/js',
},
{
name: 'my-module',
type: 'module',
jsSrcsDir: 'module/js',
},
],
},
};
const NO_LIBRARIES_CONFIG_FILE = {
codegenConfig: {
name: 'AppModules',
type: 'all',
jsSrcsDir: '.',
},
};
const SCHEMA_TEXT = `
{
"modules": {
@ -84,4 +126,7 @@ const SCHEMA = JSON.parse(SCHEMA_TEXT);
module.exports = {
schemaText: SCHEMA_TEXT,
schema: SCHEMA,
noLibrariesConfigFile: NO_LIBRARIES_CONFIG_FILE,
singleLibraryCodegenConfig: SINGLE_LIBRARY_CODEGEN_CONFIG,
multipleLibrariesCodegenConfig: MULTIPLE_LIBRARIES_CODEGEN_CONFIG,
};

Просмотреть файл

@ -11,8 +11,13 @@
'use strict';
const underTest = require('../generate-artifacts-executor');
const fixtures = require('../__test_fixtures__/fixtures');
const path = require('path');
const codegenConfigKey = 'codegenConfig';
const reactNativeDependencyName = 'react-native';
const rootPath = path.join(__dirname, '../../..');
describe('generateCode', () => {
it('executeNodes with the right arguents', () => {
// Define variables and expected values
@ -69,3 +74,130 @@ describe('generateCode', () => {
expect(mkdirSyncInvocationCount).toBe(2);
});
});
describe('extractLibrariesFromJSON', () => {
it('throws if in react-native and no dependencies found', () => {
let libraries = [];
let configFile = {};
expect(() => {
underTest._extractLibrariesFromJSON(
configFile,
libraries,
codegenConfigKey,
);
}).toThrow();
});
it('it skips if not into react-native and no dependencies found', () => {
let libraries = [];
let configFile = {};
underTest._extractLibrariesFromJSON(
configFile,
libraries,
codegenConfigKey,
'some-node-module',
'node_modules/some',
);
expect(libraries.length).toBe(0);
});
it('extracts a single dependency when config has no libraries', () => {
let libraries = [];
let configFile = fixtures.noLibrariesConfigFile;
underTest._extractLibrariesFromJSON(
configFile,
libraries,
codegenConfigKey,
'my-app',
'.',
);
expect(libraries.length).toBe(1);
expect(libraries[0]).toEqual({
library: 'my-app',
config: {
name: 'AppModules',
type: 'all',
jsSrcsDir: '.',
},
libraryPath: '.',
});
});
it("extract codegenConfig when it's empty", () => {
const configFile = {codegenConfig: {libraries: []}};
let libraries = [];
underTest._extractLibrariesFromJSON(
configFile,
codegenConfigKey,
libraries,
reactNativeDependencyName,
rootPath,
);
expect(libraries.length).toBe(0);
});
it('extract codegenConfig when dependency is one', () => {
const configFile = fixtures.singleLibraryCodegenConfig;
let libraries = [];
underTest._extractLibrariesFromJSON(
configFile,
libraries,
codegenConfigKey,
reactNativeDependencyName,
rootPath,
);
expect(libraries.length).toBe(1);
expect(libraries[0]).toEqual({
library: reactNativeDependencyName,
config: {
name: 'react-native',
type: 'all',
jsSrcsDir: '.',
},
libraryPath: rootPath,
});
});
it('extract codegenConfig with multiple dependencies', () => {
const configFile = fixtures.multipleLibrariesCodegenConfig;
const myDependency = 'my-dependency';
const myDependencyPath = path.join(__dirname, myDependency);
let libraries = [];
underTest._extractLibrariesFromJSON(
configFile,
libraries,
codegenConfigKey,
myDependency,
myDependencyPath,
);
expect(libraries.length).toBe(3);
expect(libraries[0]).toEqual({
library: myDependency,
config: {
name: 'react-native',
type: 'all',
jsSrcsDir: '.',
},
libraryPath: myDependencyPath,
});
expect(libraries[1]).toEqual({
library: myDependency,
config: {
name: 'my-component',
type: 'components',
jsSrcsDir: 'component/js',
},
libraryPath: myDependencyPath,
});
expect(libraries[2]).toEqual({
library: myDependency,
config: {
name: 'my-module',
type: 'module',
jsSrcsDir: 'module/js',
},
libraryPath: myDependencyPath,
});
});
});

Просмотреть файл

@ -52,6 +52,97 @@ function readPackageJSON(appRootDir) {
}
// Reading Libraries
function extractLibrariesFromConfigurationArray(
configFile,
codegenConfigKey,
libraries,
dependency,
dependencyPath,
) {
console.log(`[Codegen] Found ${dependency}`);
configFile[codegenConfigKey].libraries.forEach(config => {
const libraryConfig = {
library: dependency,
config,
libraryPath: dependencyPath,
};
libraries.push(libraryConfig);
});
}
function extractLibrariesFromJSON(
configFile,
libraries,
codegenConfigKey,
dependency,
dependencyPath,
) {
var isBlocking = false;
if (dependency == null) {
dependency = REACT_NATIVE_DEPENDENCY_NAME;
dependencyPath = RN_ROOT;
// If we are exploring the ReactNative libraries, we want to raise an error
// if the codegen is not properly configured.
isBlocking = true;
}
if (configFile[codegenConfigKey] == null) {
if (isBlocking) {
throw `[Codegen] Error: Could not find codegen config for ${dependency} .`;
}
return;
}
if (configFile[codegenConfigKey].libraries == null) {
console.log(`[Codegen] Found ${dependency}`);
var config = configFile[codegenConfigKey];
libraries.push({
library: dependency,
config,
libraryPath: dependencyPath,
});
} else {
console.log(`[Codegen] CodegenConfig Deprecated Setup for ${dependency}.
The configuration file still contains the codegen in the libraries array.
If possible, replace it with a single object.
`);
console.debug(`BEFORE:
{
// ...
"codegenConfig": {
"libraries": [
{
"name": "libName1",
"type": "all|components|modules",
"jsSrcsRoot": "libName1/js"
},
{
"name": "libName2",
"type": "all|components|modules",
"jsSrcsRoot": "libName2/src"
}
]
}
}
AFTER:
{
"codegenConfig": {
"name": "libraries",
"type": "all",
"jsSrcsRoot": "."
}
}
`);
extractLibrariesFromConfigurationArray(
configFile,
codegenConfigKey,
libraries,
dependency,
dependencyPath,
);
}
}
function handleReactNativeCodeLibraries(
libraries,
@ -66,21 +157,7 @@ function handleReactNativeCodeLibraries(
throw '[Codegen] Error: Could not find config file for react-native.';
}
const reactNativeConfigFile = JSON.parse(fs.readFileSync(reactNativePkgJson));
if (
reactNativeConfigFile[codegenConfigKey] == null ||
reactNativeConfigFile[codegenConfigKey].libraries == null
) {
throw '[Codegen] Error: Could not find codegen config for react-native.';
}
console.log('[Codegen] Found react-native');
reactNativeConfigFile[codegenConfigKey].libraries.forEach(config => {
const libraryConfig = {
library: REACT_NATIVE_DEPENDENCY_NAME,
config,
libraryPath: RN_ROOT,
};
libraries.push(libraryConfig);
});
extractLibrariesFromJSON(reactNativeConfigFile, libraries, codegenConfigKey);
}
function handleThirdPartyLibraries(
@ -109,20 +186,13 @@ function handleThirdPartyLibraries(
);
if (fs.existsSync(configFilePath)) {
const configFile = JSON.parse(fs.readFileSync(configFilePath));
if (
configFile[codegenConfigKey] != null &&
configFile[codegenConfigKey].libraries != null
) {
console.log(`[Codegen] Found ${dependency}`);
configFile[codegenConfigKey].libraries.forEach(config => {
const libraryConfig = {
library: dependency,
config,
libraryPath: codegenConfigFileDir,
};
libraries.push(libraryConfig);
});
}
extractLibrariesFromJSON(
configFile,
libraries,
codegenConfigKey,
dependency,
codegenConfigFileDir,
);
}
});
}
@ -137,21 +207,13 @@ function handleInAppLibraries(
'\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in the app',
);
// Handle in-app libraries
if (
pkgJson[codegenConfigKey] != null &&
pkgJson[codegenConfigKey].libraries != null
) {
console.log(`[Codegen] Found ${pkgJson.name}`);
pkgJson[codegenConfigKey].libraries.forEach(config => {
const libraryConfig = {
library: pkgJson.name,
config,
libraryPath: appRootDir,
};
libraries.push(libraryConfig);
});
}
extractLibrariesFromJSON(
pkgJson,
libraries,
codegenConfigKey,
pkgJson.name,
appRootDir,
);
}
// CodeGen
@ -377,6 +439,8 @@ function execute(
module.exports = {
execute: execute,
_executeNodeScript: executeNodeScript, // exported for testing purposes only
_generateCode: generateCode, // exported for testing purposes only
// exported for testing purposes only:
_extractLibrariesFromJSON: extractLibrariesFromJSON,
_executeNodeScript: executeNodeScript,
_generateCode: generateCode,
};

Просмотреть файл

@ -356,10 +356,20 @@ def get_react_codegen_script_phases(options={})
app_package_path = File.join(app_path, 'package.json')
app_codegen_config = get_codegen_config_from_file(app_package_path, config_key)
file_list = []
app_codegen_config['libraries'].each do |library|
library_dir = File.join(app_path, library['jsSrcsDir'])
file_list.concat (`find #{library_dir} -type f \\( -name "Native*.js" -or -name "*NativeComponent.js" \\)`.split("\n").sort)
if app_codegen_config['libraries'] then
Pod::UI.warn '[Deprecated] You are using the old `libraries` array to list all your codegen.\nThis method will be removed in the future.\nUpdate your `package.json` with a single object.'
app_codegen_config['libraries'].each do |library|
library_dir = File.join(app_path, library['jsSrcsDir'])
file_list.concat (`find #{library_dir} -type f \\( -name "Native*.js" -or -name "*NativeComponent.js" \\)`.split("\n").sort)
end
elsif app_codegen_config['jsSrcsDir'] then
codegen_dir = File.join(app_path, app_codegen_config['jsSrcsDir'])
file_list.concat (`find #{codegen_dir} -type f \\( -name "Native*.js" -or -name "*NativeComponent.js" \\)`.split("\n").sort)
else
Pod::UI.warn '[Error] Codegen not properly configured. Please add the `codegenConf` entry to your `package.json`'
exit 1
end
input_files = file_list.map { |filename| "${PODS_ROOT}/../#{Pathname.new(filename).realpath().relative_path_from(Pod::Config.instance.installation_root)}" }
# Add a script phase to trigger generate artifact.