Skip to content

Commit a562216

Browse files
satya164facebook-github-bot
authored andcommitted
feat: support custom library paths in react-native.config.js for codegen on iOS (#34580)
Summary: Currently for codegen to work for a library on iOS, it needs to be located inside `node_modules`. This patch adds support for libraries defined in `react-native.config.js`. This is useful when developing libraries as well as monorepos where the library may exist outside of the `node_modules`. Example: ```js // react-native.config.js const path = require('path'); module.exports = { dependencies: { 'react-native-library-name': { root: path.join(__dirname, '..'), }, }, }; ``` ## Changelog [Internal] [Added] - Support custom library paths in `react-native.config.js` for codegen on iOS Pull Request resolved: #34580 Test Plan: Tested on a test application and ensured that codegen finds the library specified in `react-native.config.js` https://user-images.githubusercontent.com/1174278/188141056-bce03730-2a13-4648-8889-9727aaf2c3c4.mp4 I have also added a basic test case for this scenario. Reviewed By: jacdebug, cortinico Differential Revision: D39257919 Pulled By: cipolleschi fbshipit-source-id: 131189f1941128a59b9b1e28af61a9038eb4536b
1 parent fc42d5b commit a562216

File tree

4 files changed

+202
-52
lines changed

4 files changed

+202
-52
lines changed

repo-config/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,8 @@
4848
"signedsource": "^1.0.0",
4949
"ws": "^6.1.4",
5050
"yargs": "^15.3.1"
51+
},
52+
"devDependencies": {
53+
"mock-fs": "^5.1.4"
5154
}
5255
}

scripts/codegen/__tests__/generate-artifacts-executor-test.js

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,20 @@
1313
const underTest = require('../generate-artifacts-executor');
1414
const fixtures = require('../__test_fixtures__/fixtures');
1515
const path = require('path');
16+
const fs = require('fs');
17+
const child_process = require('child_process');
1618

1719
const codegenConfigKey = 'codegenConfig';
1820
const reactNativeDependencyName = 'react-native';
1921
const rootPath = path.join(__dirname, '../../..');
2022

2123
describe('generateCode', () => {
22-
it('executeNodes with the right arguents', () => {
24+
afterEach(() => {
25+
jest.resetModules();
26+
jest.resetAllMocks();
27+
});
28+
29+
it('executeNodes with the right arguments', () => {
2330
// Define variables and expected values
2431
const iosOutputDir = 'app/ios/build/generated/ios';
2532
const library = {config: {name: 'library', type: 'all'}};
@@ -32,46 +39,32 @@ describe('generateCode', () => {
3239
const tmpOutDir = path.join(tmpDir, 'out');
3340

3441
// mock used functions
35-
let mkdirSyncInvocationCount = 0;
36-
jest.mock('fs', () => ({
37-
mkdirSync: (location, config) => {
38-
if (mkdirSyncInvocationCount === 0) {
39-
expect(location).toEqual(tmpOutDir);
40-
}
41-
if (mkdirSyncInvocationCount === 1) {
42-
expect(location).toEqual(iosOutputDir);
43-
}
44-
45-
mkdirSyncInvocationCount += 1;
46-
},
47-
}));
48-
49-
let execSyncInvocationCount = 0;
50-
jest.mock('child_process', () => ({
51-
execSync: command => {
52-
if (execSyncInvocationCount === 0) {
53-
const expectedCommand = `${node} ${path.join(
54-
rnRoot,
55-
'generate-specs-cli.js',
56-
)} \
57-
--platform ios \
58-
--schemaPath ${pathToSchema} \
59-
--outputDir ${tmpOutDir} \
60-
--libraryName ${library.config.name} \
61-
--libraryType ${libraryType}`;
62-
expect(command).toEqual(expectedCommand);
63-
}
64-
65-
if (execSyncInvocationCount === 1) {
66-
expect(command).toEqual(`cp -R ${tmpOutDir}/* ${iosOutputDir}`);
67-
}
68-
69-
execSyncInvocationCount += 1;
70-
},
71-
}));
42+
jest.spyOn(fs, 'mkdirSync').mockImplementation();
43+
jest.spyOn(child_process, 'execSync').mockImplementation();
7244

7345
underTest._generateCode(iosOutputDir, library, tmpDir, node, pathToSchema);
74-
expect(mkdirSyncInvocationCount).toBe(2);
46+
47+
const expectedCommand = `${node} ${path.join(
48+
rnRoot,
49+
'generate-specs-cli.js',
50+
)} --platform ios --schemaPath ${pathToSchema} --outputDir ${tmpOutDir} --libraryName ${
51+
library.config.name
52+
} --libraryType ${libraryType}`;
53+
54+
expect(child_process.execSync).toHaveBeenCalledTimes(2);
55+
expect(child_process.execSync).toHaveBeenNthCalledWith(1, expectedCommand);
56+
expect(child_process.execSync).toHaveBeenNthCalledWith(
57+
2,
58+
`cp -R ${tmpOutDir}/* ${iosOutputDir}`,
59+
);
60+
61+
expect(fs.mkdirSync).toHaveBeenCalledTimes(2);
62+
expect(fs.mkdirSync).toHaveBeenNthCalledWith(1, tmpOutDir, {
63+
recursive: true,
64+
});
65+
expect(fs.mkdirSync).toHaveBeenNthCalledWith(2, iosOutputDir, {
66+
recursive: true,
67+
});
7568
});
7669
});
7770

@@ -202,6 +195,83 @@ describe('extractLibrariesFromJSON', () => {
202195
});
203196
});
204197

198+
describe('findCodegenEnabledLibraries', () => {
199+
const mock = require('mock-fs');
200+
const {
201+
_findCodegenEnabledLibraries: findCodegenEnabledLibraries,
202+
} = require('../generate-artifacts-executor');
203+
204+
afterEach(() => {
205+
mock.restore();
206+
});
207+
208+
it('returns libraries defined in react-native.config.js', () => {
209+
const projectDir = path.join(__dirname, '../../../../test-project');
210+
const baseCodegenConfigFileDir = path.join(__dirname, '../../..');
211+
const baseCodegenConfigFilePath = path.join(
212+
baseCodegenConfigFileDir,
213+
'package.json',
214+
);
215+
216+
mock({
217+
[baseCodegenConfigFilePath]: `
218+
{
219+
"codegenConfig": {}
220+
}
221+
`,
222+
[projectDir]: {
223+
app: {
224+
'package.json': `{
225+
"name": "my-app"
226+
}`,
227+
'react-native.config.js': '',
228+
},
229+
'library-foo': {
230+
'package.json': `{
231+
"name": "react-native-foo",
232+
"codegenConfig": {
233+
"name": "RNFooSpec",
234+
"type": "modules",
235+
"jsSrcsDir": "src"
236+
}
237+
}`,
238+
},
239+
},
240+
});
241+
242+
jest.mock(path.join(projectDir, 'app', 'react-native.config.js'), () => ({
243+
dependencies: {
244+
'react-native-foo': {
245+
root: path.join(projectDir, 'library-foo'),
246+
},
247+
'react-native-bar': {
248+
root: path.join(projectDir, 'library-bar'),
249+
},
250+
},
251+
}));
252+
253+
const libraries = findCodegenEnabledLibraries(
254+
`${projectDir}/app`,
255+
baseCodegenConfigFileDir,
256+
`package.json`,
257+
'codegenConfig',
258+
);
259+
260+
expect(libraries).toEqual([
261+
{
262+
library: 'react-native',
263+
config: {},
264+
libraryPath: baseCodegenConfigFileDir,
265+
},
266+
{
267+
library: 'react-native-foo',
268+
config: {name: 'RNFooSpec', type: 'modules', jsSrcsDir: 'src'},
269+
libraryPath: path.join(projectDir, 'library-foo'),
270+
},
271+
]);
272+
});
273+
});
274+
205275
describe('delete empty files and folders', () => {
206276
beforeEach(() => {
207277
jest.resetModules();

scripts/codegen/generate-artifacts-executor.js

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,55 @@ function handleThirdPartyLibraries(
204204
});
205205
}
206206

207+
function handleLibrariesFromReactNativeConfig(
208+
libraries,
209+
codegenConfigKey,
210+
codegenConfigFilename,
211+
appRootDir,
212+
) {
213+
const rnConfigFileName = 'react-native.config.js';
214+
215+
console.log(
216+
`\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${rnConfigFileName}`,
217+
);
218+
219+
const rnConfigFilePath = path.join(appRootDir, rnConfigFileName);
220+
221+
if (fs.existsSync(rnConfigFilePath)) {
222+
const rnConfig = require(rnConfigFilePath);
223+
224+
if (rnConfig.dependencies != null) {
225+
Object.keys(rnConfig.dependencies).forEach(name => {
226+
const dependencyConfig = rnConfig.dependencies[name];
227+
228+
if (dependencyConfig.root) {
229+
const codegenConfigFileDir = path.resolve(
230+
appRootDir,
231+
dependencyConfig.root,
232+
);
233+
const configFilePath = path.join(
234+
codegenConfigFileDir,
235+
codegenConfigFilename,
236+
);
237+
const pkgJsonPath = path.join(codegenConfigFileDir, 'package.json');
238+
239+
if (fs.existsSync(configFilePath)) {
240+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath));
241+
const configFile = JSON.parse(fs.readFileSync(configFilePath));
242+
extractLibrariesFromJSON(
243+
configFile,
244+
libraries,
245+
codegenConfigKey,
246+
pkgJson.name,
247+
codegenConfigFileDir,
248+
);
249+
}
250+
}
251+
});
252+
}
253+
}
254+
}
255+
207256
function handleInAppLibraries(
208257
libraries,
209258
pkgJson,
@@ -362,6 +411,39 @@ function createComponentProvider(
362411
}
363412
}
364413

414+
function findCodegenEnabledLibraries(
415+
appRootDir,
416+
baseCodegenConfigFileDir,
417+
codegenConfigFilename,
418+
codegenConfigKey,
419+
) {
420+
const pkgJson = readPackageJSON(appRootDir);
421+
const dependencies = {...pkgJson.dependencies, ...pkgJson.devDependencies};
422+
const libraries = [];
423+
424+
handleReactNativeCodeLibraries(
425+
libraries,
426+
codegenConfigFilename,
427+
codegenConfigKey,
428+
);
429+
handleThirdPartyLibraries(
430+
libraries,
431+
baseCodegenConfigFileDir,
432+
dependencies,
433+
codegenConfigFilename,
434+
codegenConfigKey,
435+
);
436+
handleLibrariesFromReactNativeConfig(
437+
libraries,
438+
codegenConfigKey,
439+
codegenConfigFilename,
440+
appRootDir,
441+
);
442+
handleInAppLibraries(libraries, pkgJson, codegenConfigKey, appRootDir);
443+
444+
return libraries;
445+
}
446+
365447
// It removes all the empty files and empty folders
366448
// it finds, starting from `filepath`, recursively.
367449
//
@@ -429,23 +511,12 @@ function execute(
429511
}
430512

431513
try {
432-
const pkgJson = readPackageJSON(appRootDir);
433-
const dependencies = {...pkgJson.dependencies, ...pkgJson.devDependencies};
434-
const libraries = [];
435-
436-
handleReactNativeCodeLibraries(
437-
libraries,
438-
codegenConfigFilename,
439-
codegenConfigKey,
440-
);
441-
handleThirdPartyLibraries(
442-
libraries,
514+
const libraries = findCodegenEnabledLibraries(
515+
appRootDir,
443516
baseCodegenConfigFileDir,
444-
dependencies,
445517
codegenConfigFilename,
446518
codegenConfigKey,
447519
);
448-
handleInAppLibraries(libraries, pkgJson, codegenConfigKey, appRootDir);
449520

450521
if (libraries.length === 0) {
451522
console.log('[Codegen] No codegen-enabled libraries found.');
@@ -482,6 +553,7 @@ module.exports = {
482553
execute: execute,
483554
// exported for testing purposes only:
484555
_extractLibrariesFromJSON: extractLibrariesFromJSON,
556+
_findCodegenEnabledLibraries: findCodegenEnabledLibraries,
485557
_executeNodeScript: executeNodeScript,
486558
_generateCode: generateCode,
487559
_cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders,

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5301,6 +5301,11 @@ mkdirp@^0.5.1:
53015301
dependencies:
53025302
minimist "0.0.8"
53035303

5304+
mock-fs@^5.1.4:
5305+
version "5.1.4"
5306+
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.1.4.tgz#d64dc37b2793613ca7148b510b1167b5b8afb6b8"
5307+
integrity sha512-sudhLjCjX37qWIcAlIv1OnAxB2wI4EmXByVuUjILh1rKGNGpGU8GNnzw+EAbrhdpBe0TL/KONbK1y3RXZk8SxQ==
5308+
53045309
53055310
version "2.0.0"
53065311
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"

0 commit comments

Comments
 (0)