Skip to content

Commit 11b0458

Browse files
authored
feat: allow external packages to configure react-native.config.js (#2824)
1 parent 176c5ec commit 11b0458

7 files changed

Lines changed: 271 additions & 182 deletions

File tree

Lines changed: 95 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// @ts-check
2-
import { equal, match, notEqual, ok } from "node:assert/strict";
2+
import { deepEqual, equal, match, notEqual, ok } from "node:assert/strict";
33
import * as fs from "node:fs";
44
import * as path from "node:path";
5-
import { test } from "node:test";
5+
import { describe, it } from "node:test";
6+
import { configureProjects } from "../../scripts/configure-projects.js";
67
import { readJSONFile } from "../../scripts/helpers.js";
78

89
/**
@@ -45,13 +46,20 @@ function requiresDependency(spec, projectRoot) {
4546
return Object.hasOwn(dependencies, spec);
4647
}
4748

48-
test("react-native config", async (t) => {
49+
describe("react-native config", async () => {
4950
const currentDir = process.cwd();
5051
const loadConfig = await getLoadConfig(currentDir);
5152

5253
const reactNativePath = path.join(currentDir, "node_modules", "react-native");
5354

54-
await t.test("contains Android config", () => {
55+
const shouldSkipIOS = process.platform === "win32";
56+
const shouldSkipMacOS =
57+
shouldSkipIOS || !requiresDependency("react-native-macos", currentDir);
58+
const shouldSkipWindows =
59+
process.platform !== "win32" ||
60+
!requiresDependency("react-native-windows", currentDir);
61+
62+
it("contains Android config", () => {
5563
const sourceDir = path.join(currentDir, "android");
5664
const config = loadConfig();
5765

@@ -71,97 +79,90 @@ test("react-native config", async (t) => {
7179
equal(config.project.android.packageName, "com.microsoft.reacttestapp");
7280
});
7381

74-
await t.test(
75-
"contains iOS config",
76-
{ skip: process.platform === "win32" },
77-
() => {
78-
const sourceDir = path.join(currentDir, "ios");
79-
const config = loadConfig();
80-
81-
equal(typeof config, "object");
82-
match(config.root, regexp(currentDir));
83-
match(config.reactNativePath, regexp(reactNativePath));
84-
equal(
85-
config.dependencies["react-native-test-app"].name,
86-
"react-native-test-app"
87-
);
88-
notEqual(config.platforms.ios, undefined);
89-
match(config.project.ios.sourceDir, regexp(sourceDir));
90-
91-
if (fs.existsSync("ios/Pods")) {
92-
equal(config.project.ios.xcodeProject.name, "Example.xcworkspace");
93-
ok(config.project.ios.xcodeProject.isWorkspace);
94-
} else {
95-
equal(config.project.ios.xcodeProject, null);
96-
}
82+
it("contains iOS config", { skip: shouldSkipIOS }, () => {
83+
const sourceDir = path.join(currentDir, "ios");
84+
const config = loadConfig();
85+
86+
equal(typeof config, "object");
87+
match(config.root, regexp(currentDir));
88+
match(config.reactNativePath, regexp(reactNativePath));
89+
equal(
90+
config.dependencies["react-native-test-app"].name,
91+
"react-native-test-app"
92+
);
93+
notEqual(config.platforms.ios, undefined);
94+
match(config.project.ios.sourceDir, regexp(sourceDir));
95+
96+
if (fs.existsSync("ios/Pods")) {
97+
equal(config.project.ios.xcodeProject.name, "Example.xcworkspace");
98+
ok(config.project.ios.xcodeProject.isWorkspace);
99+
} else {
100+
equal(config.project.ios.xcodeProject, null);
97101
}
98-
);
99-
100-
await t.test(
101-
"contains macOS config",
102-
{
103-
skip:
104-
process.platform === "win32" ||
105-
!requiresDependency("react-native-macos", currentDir),
106-
},
107-
() => {
108-
const sourceDir = path.join(currentDir, "macos");
109-
const config = loadConfig();
110-
111-
equal(typeof config, "object");
112-
match(config.root, regexp(currentDir));
113-
match(config.reactNativePath, regexp(reactNativePath));
114-
equal(
115-
config.dependencies["react-native-test-app"].name,
116-
"react-native-test-app"
117-
);
118-
notEqual(config.platforms.macos, undefined);
119-
match(config.project.macos.sourceDir, regexp(sourceDir));
120-
121-
if (fs.existsSync("macos/Pods")) {
122-
equal(config.project.macos.xcodeProject.name, "Example.xcworkspace");
123-
ok(config.project.macos.xcodeProject.isWorkspace);
124-
} else {
125-
equal(config.project.macos.xcodeProject, null);
126-
}
102+
});
103+
104+
it("contains macOS config", { skip: shouldSkipMacOS }, () => {
105+
const sourceDir = path.join(currentDir, "macos");
106+
const config = loadConfig();
107+
108+
equal(typeof config, "object");
109+
match(config.root, regexp(currentDir));
110+
match(config.reactNativePath, regexp(reactNativePath));
111+
equal(
112+
config.dependencies["react-native-test-app"].name,
113+
"react-native-test-app"
114+
);
115+
notEqual(config.platforms.macos, undefined);
116+
match(config.project.macos.sourceDir, regexp(sourceDir));
117+
118+
if (fs.existsSync("macos/Pods")) {
119+
equal(config.project.macos.xcodeProject.name, "Example.xcworkspace");
120+
ok(config.project.macos.xcodeProject.isWorkspace);
121+
} else {
122+
equal(config.project.macos.xcodeProject, null);
127123
}
128-
);
129-
130-
await t.test(
131-
"contains Windows config",
132-
{
133-
skip:
134-
process.platform !== "win32" ||
135-
!requiresDependency("react-native-windows", currentDir),
136-
},
137-
() => {
138-
const projectFile = path.join(
139-
"node_modules",
140-
".generated",
141-
"windows",
142-
"ReactTestApp",
143-
"ReactTestApp.vcxproj"
144-
);
145-
146-
if (!fs.existsSync(projectFile)) {
147-
console.warn(`No such file: ${projectFile}`);
148-
return;
149-
}
150-
151-
const config = loadConfig();
152-
153-
equal(typeof config, "object");
154-
match(config.root, regexp(currentDir));
155-
match(config.reactNativePath, regexp(reactNativePath));
156-
equal(
157-
config.dependencies["react-native-test-app"].name,
158-
"react-native-test-app"
159-
);
160-
equal(config.platforms.windows.npmPackageName, "react-native-windows");
161-
match(config.project.windows.folder, regexp(currentDir));
162-
match(config.project.windows.sourceDir, /windows/);
163-
match(config.project.windows.solutionFile, /Example.sln/);
164-
match(config.project.windows.project.projectFile, regexp(projectFile));
124+
});
125+
126+
it("contains Windows config", { skip: shouldSkipWindows }, () => {
127+
const projectFile = path.join(
128+
"node_modules",
129+
".generated",
130+
"windows",
131+
"ReactTestApp",
132+
"ReactTestApp.vcxproj"
133+
);
134+
135+
if (!fs.existsSync(projectFile)) {
136+
console.warn(`No such file: ${projectFile}`);
137+
return;
165138
}
166-
);
139+
140+
const config = loadConfig();
141+
142+
equal(typeof config, "object");
143+
match(config.root, regexp(currentDir));
144+
match(config.reactNativePath, regexp(reactNativePath));
145+
equal(
146+
config.dependencies["react-native-test-app"].name,
147+
"react-native-test-app"
148+
);
149+
equal(config.platforms.windows.npmPackageName, "react-native-windows");
150+
match(config.project.windows.folder, regexp(currentDir));
151+
match(config.project.windows.sourceDir, /windows/);
152+
match(config.project.windows.solutionFile, /Example.sln/);
153+
match(config.project.windows.project.projectFile, regexp(projectFile));
154+
});
155+
});
156+
157+
describe("configureProjects()", () => {
158+
const isMain = path.basename(process.cwd()) === "example";
159+
160+
// Only the main example app includes web
161+
it("returns externally provided platform config", { skip: !isMain }, () => {
162+
deepEqual(configureProjects({ web: true }), {
163+
web: {
164+
"@rnx-kit/react-native-template-web": true,
165+
},
166+
});
167+
});
167168
});

packages/app/scripts/configure-projects.js

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,9 @@
99
*/
1010
const nodefs = require("node:fs");
1111
const path = require("node:path");
12-
const {
13-
configure: configureAndroid,
14-
getAndroidPackageName,
15-
} = require("../android/template.config.mjs");
16-
const { configure: configureIOS } = require("../ios/template.config.mjs");
17-
const {
18-
configure: configureWindows,
19-
} = require("../windows/template.config.mjs");
20-
const { findNearest } = require("./helpers");
12+
const { getAndroidPackageName } = require("../android/template.config.mjs");
13+
const { findNearest } = require("./helpers.js");
14+
const { loadPlatformTemplates } = require("./template.mjs");
2115

2216
/**
2317
* Finds `react-native.config.[ts,mjs,cjs,js]`.
@@ -76,24 +70,26 @@ function findReactNativeConfig(fs = nodefs) {
7670
}
7771

7872
/**
79-
* @param {ProjectConfig} configuration
73+
* @param {ProjectConfig} projectConfig
8074
* @returns {Partial<ProjectParams>}
8175
*/
82-
function configureProjects({ android, ios, windows }, fs = nodefs) {
76+
function configureProjects(projectConfig, fs = nodefs) {
8377
const reactNativeConfig = findReactNativeConfig(fs);
8478

8579
/** @type {Partial<ProjectParams>} */
8680
const config = {};
8781

8882
const projectRoot = path.dirname(reactNativeConfig);
89-
if (android) {
90-
config.android = configureAndroid(projectRoot, android, fs);
91-
}
92-
if (ios) {
93-
config.ios = configureIOS(projectRoot, ios, fs);
94-
}
95-
if (windows) {
96-
config.windows = configureWindows(projectRoot, windows, fs);
83+
const params = {
84+
packagePath: projectRoot,
85+
testAppPath: path.dirname(__dirname),
86+
};
87+
const templates = loadPlatformTemplates(params, fs);
88+
for (const [platform, { configure }] of Object.entries(templates)) {
89+
const platformConfig = projectConfig[platform];
90+
if (platformConfig) {
91+
config[platform] = configure(projectRoot, platformConfig, fs);
92+
}
9793
}
9894

9995
return config;

0 commit comments

Comments
 (0)