Skip to content

Commit 5d0ae8b

Browse files
committed
feat: add support for expo example in native libraries
1 parent d4a58b0 commit 5d0ae8b

File tree

9 files changed

+96
-58
lines changed

9 files changed

+96
-58
lines changed

Diff for: packages/create-react-native-library/src/exampleApp/generateExampleApp.ts

+58-38
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import path from 'path';
33
import https from 'https';
44
import { spawn } from '../utils/spawn';
55
import sortObjectKeys from '../utils/sortObjectKeys';
6-
import type { ExampleApp } from '../input';
6+
import dedent from 'dedent';
7+
import type { TemplateConfiguration } from '../template';
78

89
const FILES_TO_DELETE = [
910
'__tests__',
@@ -35,40 +36,34 @@ const PACKAGES_TO_REMOVE = [
3536
'typescript',
3637
];
3738

38-
const PACKAGES_TO_ADD_WEB = {
39+
const PACKAGES_TO_ADD_EXPO_WEB = {
3940
'@expo/metro-runtime': '~3.2.1',
4041
'react-dom': '18.2.0',
4142
'react-native-web': '~0.18.10',
4243
};
4344

45+
const PACKAGES_TO_ADD_DEV_EXPO_NATIVE = {
46+
'expo-dev-client': '~5.0.3',
47+
};
48+
4449
export default async function generateExampleApp({
45-
type,
46-
dest,
47-
arch,
48-
project,
49-
bobVersion,
50+
config,
51+
destination,
5052
reactNativeVersion = 'latest',
5153
}: {
52-
type: ExampleApp;
53-
dest: string;
54-
arch: 'new' | 'legacy';
55-
project: {
56-
slug: string;
57-
name: string;
58-
package: string;
59-
};
60-
bobVersion: string;
61-
reactNativeVersion?: string;
54+
config: TemplateConfiguration;
55+
destination: string;
56+
reactNativeVersion: string | undefined;
6257
}) {
63-
const directory = path.join(dest, 'example');
58+
const directory = path.join(destination, 'example');
6459

6560
// `npx --package react-native-test-app@latest init --name ${projectName}Example --destination example --version ${reactNativeVersion}`
6661
const testAppArgs = [
6762
'--package',
6863
`react-native-test-app@latest`,
6964
'init',
7065
'--name',
71-
`${project.name}Example`,
66+
`${config.project.name}Example`,
7267
`--destination`,
7368
directory,
7469
...(reactNativeVersion !== 'latest'
@@ -84,9 +79,9 @@ export default async function generateExampleApp({
8479
const vanillaArgs = [
8580
`@react-native-community/cli`,
8681
'init',
87-
`${project.name}Example`,
82+
`${config.project.name}Example`,
8883
'--package-name',
89-
`${project.package}.example`,
84+
`${config.project.package}.example`,
9085
'--directory',
9186
directory,
9287
'--version',
@@ -107,7 +102,7 @@ export default async function generateExampleApp({
107102

108103
let args: string[] = [];
109104

110-
switch (type) {
105+
switch (config.example) {
111106
case 'vanilla':
112107
args = vanillaArgs;
113108
break;
@@ -131,7 +126,7 @@ export default async function generateExampleApp({
131126
// Patch the example app's package.json
132127
const pkg = await fs.readJSON(path.join(directory, 'package.json'));
133128

134-
pkg.name = `${project.slug}-example`;
129+
pkg.name = `${config.project.slug}-example`;
135130

136131
// Remove Jest config for now
137132
delete pkg.jest;
@@ -144,12 +139,12 @@ export default async function generateExampleApp({
144139
const SCRIPTS_TO_ADD = {
145140
'build:android':
146141
'react-native build-android --extra-params "--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a"',
147-
'build:ios': `react-native build-ios --scheme ${project.name}Example --mode Debug --extra-params "-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"`,
142+
'build:ios': `react-native build-ios --scheme ${config.project.name}Example --mode Debug --extra-params "-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"`,
148143
};
149144

150-
if (type === 'vanilla') {
145+
if (config.example === 'vanilla') {
151146
Object.assign(scripts, SCRIPTS_TO_ADD);
152-
} else if (type === 'test-app') {
147+
} else if (config.example === 'test-app') {
153148
// `react-native-test-app` doesn't bundle application by default in 'Release' mode and also `bundle` command doesn't create a directory.
154149
// `mkdist` script should be removed after stable React Native major contains this fix: https://github.com/facebook/react-native/pull/45182.
155150

@@ -173,9 +168,9 @@ export default async function generateExampleApp({
173168
const app = await fs.readJSON(path.join(directory, 'app.json'));
174169

175170
app.android = app.android || {};
176-
app.android.package = `${project.package}.example`;
171+
app.android.package = `${config.project.package}.example`;
177172
app.ios = app.ios || {};
178-
app.ios.bundleIdentifier = `${project.package}.example`;
173+
app.ios.bundleIdentifier = `${config.project.package}.example`;
179174

180175
await fs.writeJSON(path.join(directory, 'app.json'), app, {
181176
spaces: 2,
@@ -188,12 +183,12 @@ export default async function generateExampleApp({
188183
});
189184

190185
const PACKAGES_TO_ADD_DEV = {
191-
'react-native-builder-bob': `^${bobVersion}`,
186+
'react-native-builder-bob': `^${config.bob.version}`,
192187
};
193188

194189
Object.assign(devDependencies, PACKAGES_TO_ADD_DEV);
195190

196-
if (type === 'expo') {
191+
if (config.example === 'expo') {
197192
const sdkVersion = dependencies.expo.split('.')[0].replace(/[^\d]/, '');
198193

199194
let bundledNativeModules: Record<string, string>;
@@ -222,18 +217,43 @@ export default async function generateExampleApp({
222217
bundledNativeModules = {};
223218
}
224219

225-
Object.entries(PACKAGES_TO_ADD_WEB).forEach(([name, version]) => {
226-
dependencies[name] = bundledNativeModules[name] || version;
227-
});
220+
if (config.project.native) {
221+
Object.entries(PACKAGES_TO_ADD_DEV_EXPO_NATIVE).forEach(
222+
([name, version]) => {
223+
devDependencies[name] = bundledNativeModules[name] || version;
224+
}
225+
);
226+
227+
scripts.start = 'expo start --dev-client';
228+
scripts.android = 'expo run:android';
229+
scripts.ios = 'expo run:ios';
228230

229-
scripts.web = 'expo start --web';
231+
delete scripts.web;
232+
233+
await fs.writeFile(
234+
path.join(directory, '.gitignore'),
235+
dedent`
236+
# These folders are generated with prebuild (CNG)
237+
android/
238+
ios/
239+
`
240+
);
241+
} else {
242+
Object.entries(PACKAGES_TO_ADD_EXPO_WEB).forEach(([name, version]) => {
243+
dependencies[name] = bundledNativeModules[name] || version;
244+
});
245+
246+
scripts.web = 'expo start --web';
247+
}
230248

231249
const app = await fs.readJSON(path.join(directory, 'app.json'));
232250

251+
app.expo.name = `${config.project.name} Example`;
252+
app.expo.slug = `${config.project.slug}-example`;
233253
app.expo.android = app.expo.android || {};
234-
app.expo.android.package = `${project.package}.example`;
254+
app.expo.android.package = `${config.project.package}.example`;
235255
app.expo.ios = app.expo.ios || {};
236-
app.expo.ios.bundleIdentifier = `${project.package}.example`;
256+
app.expo.ios.bundleIdentifier = `${config.project.package}.example`;
237257

238258
await fs.writeJSON(path.join(directory, 'app.json'), app, {
239259
spaces: 2,
@@ -250,7 +270,7 @@ export default async function generateExampleApp({
250270
spaces: 2,
251271
});
252272

253-
if (type !== 'expo') {
273+
if (config.example !== 'expo') {
254274
let gradleProperties = await fs.readFile(
255275
path.join(directory, 'android', 'gradle.properties'),
256276
'utf8'
@@ -264,7 +284,7 @@ export default async function generateExampleApp({
264284
);
265285

266286
// If the library is on new architecture, enable new arch for iOS and Android
267-
if (arch === 'new') {
287+
if (config.project.arch === 'new') {
268288
// iOS
269289
// Add ENV['RCT_NEW_ARCH_ENABLED'] = 1 on top of example/ios/Podfile
270290
const podfile = await fs.readFile(

Diff for: packages/create-react-native-library/src/index.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,8 @@ async function create(_argv: yargs.Arguments<Args>) {
8888
spinner.text = 'Generating example app';
8989

9090
await generateExampleApp({
91-
type: config.example,
92-
dest: folder,
93-
arch: config.project.arch,
94-
project: config.project,
95-
bobVersion,
91+
config,
92+
destination: folder,
9693
reactNativeVersion: answers.reactNativeVersion,
9794
});
9895
}

Diff for: packages/create-react-native-library/src/input.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,25 @@ const LANGUAGE_CHOICES: {
5757
const EXAMPLE_CHOICES = (
5858
[
5959
{
60-
title: 'Vanilla',
60+
title: 'App with Expo CLI',
61+
value: 'expo',
62+
description: 'managed expo app for easier upgrades',
63+
disabled: false,
64+
},
65+
{
66+
title: 'App with Community CLI',
6167
value: 'vanilla',
6268
description: "provides access to app's native code",
6369
disabled: false,
6470
},
6571
{
66-
title: 'Test app',
72+
title: 'React Native Test App by Microsoft',
6773
value: 'test-app',
6874
description: "app's native code is abstracted away",
6975
// The test app is disabled for now until proper
7076
// Codegen spec shipping is implemented
7177
disabled: !process.env.CRNL_ENABLE_TEST_APP,
7278
},
73-
{
74-
title: 'Expo',
75-
value: 'expo',
76-
description: 'managed expo project with web support',
77-
disabled: false,
78-
},
7979
] as const
8080
).filter((choice) => !choice.disabled);
8181

@@ -291,10 +291,15 @@ export async function createQuestions({
291291
message: 'What type of example app do you want to create?',
292292
choices: (_, values) => {
293293
return EXAMPLE_CHOICES.filter((choice) => {
294-
if (values.type) {
295-
return values.type === 'library'
296-
? choice.value === 'expo'
297-
: choice.value !== 'expo';
294+
if (values.type === 'library') {
295+
return choice.value === 'expo';
296+
}
297+
298+
if (
299+
values.type === 'legacy-module' ||
300+
values.type === 'legacy-view'
301+
) {
302+
return choice.value !== 'expo';
298303
}
299304

300305
return true;

Diff for: packages/create-react-native-library/src/template.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ const EXAMPLE_MODULE_NEW_FILES = path.resolve(
5454
'../templates/example-module-new'
5555
);
5656
const EXAMPLE_VIEW_FILES = path.resolve(__dirname, '../templates/example-view');
57+
const EXAMPLE_EXPO_FILES = path.resolve(__dirname, '../templates/example-expo');
5758

5859
const JS_FILES = path.resolve(__dirname, '../templates/js-library');
59-
const EXPO_FILES = path.resolve(__dirname, '../templates/expo-library');
6060
const CPP_FILES = path.resolve(__dirname, '../templates/cpp-library');
6161
const NATIVE_COMMON_FILES = path.resolve(
6262
__dirname,
@@ -186,14 +186,18 @@ export async function applyTemplates(
186186

187187
if (answers.languages === 'js') {
188188
await applyTemplate(config, JS_FILES, folder);
189-
await applyTemplate(config, EXPO_FILES, folder);
189+
await applyTemplate(config, EXAMPLE_EXPO_FILES, folder);
190190
} else {
191191
await applyTemplate(config, NATIVE_COMMON_FILES, folder);
192192

193193
if (config.example !== 'none') {
194194
await applyTemplate(config, NATIVE_COMMON_EXAMPLE_FILES, folder);
195195
}
196196

197+
if (config.example === 'expo') {
198+
await applyTemplate(config, EXAMPLE_EXPO_FILES, folder);
199+
}
200+
197201
if (config.project.module) {
198202
await applyTemplate(
199203
config,

Diff for: packages/create-react-native-library/templates/native-common-example/example/react-native.config.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
const path = require('path');
2+
<% if (example !== 'expo') { -%>
23
const pkg = require('../package.json');
4+
<% } -%>
35
<% if (example === 'test-app') { -%>
46
const { configureProjects } = require('react-native-test-app');
57
<% } -%>
8+
<% if (example === 'expo') { -%>
9+
10+
// FIXME: `__dirname` is not set correctly in Expo
11+
// When building the, the cwc is `example/ios`
12+
// So we override `__dirname` based on that
13+
// https://github.com/expo/expo/pull/33532
14+
__dirname = path.resolve(process.cwd(), '..');
15+
16+
const pkg = require(path.join(__dirname, '..', 'package.json'));
17+
<% } -%>
618

719
module.exports = {
820
<% if (example === 'test-app') { -%>
@@ -15,7 +27,7 @@ module.exports = {
1527
automaticPodsInstallation: true,
1628
},
1729
}),
18-
<% } else { -%>
30+
<% } else if (example === 'vanila') { -%>
1931
project: {
2032
ios: {
2133
automaticPodsInstallation: true,

0 commit comments

Comments
 (0)