From aaba90f36cc58c3c55a4ab41741e84e34d79588d Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 8 Dec 2024 22:51:58 +0100 Subject: [PATCH] feat: add support for expo example in native libraries --- .../src/exampleApp/generateExampleApp.ts | 86 +++++++++++-------- .../create-react-native-library/src/index.ts | 7 +- .../create-react-native-library/src/input.ts | 22 +++-- .../src/template.ts | 8 +- .../example/babel.config.js | 0 .../example/index.js | 0 .../example/metro.config.js | 0 .../example/tsconfig.json | 0 8 files changed, 66 insertions(+), 57 deletions(-) rename packages/create-react-native-library/templates/{expo-library => example-expo}/example/babel.config.js (100%) rename packages/create-react-native-library/templates/{expo-library => example-expo}/example/index.js (100%) rename packages/create-react-native-library/templates/{expo-library => example-expo}/example/metro.config.js (100%) rename packages/create-react-native-library/templates/{expo-library => example-expo}/example/tsconfig.json (100%) diff --git a/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts b/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts index 1fb4d76a1..cff5391b9 100644 --- a/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts +++ b/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts @@ -3,7 +3,7 @@ import path from 'path'; import https from 'https'; import { spawn } from '../utils/spawn'; import sortObjectKeys from '../utils/sortObjectKeys'; -import type { ExampleApp } from '../input'; +import type { TemplateConfiguration } from '../template'; const FILES_TO_DELETE = [ '__tests__', @@ -35,32 +35,26 @@ const PACKAGES_TO_REMOVE = [ 'typescript', ]; -const PACKAGES_TO_ADD_WEB = { +const PACKAGES_TO_ADD_EXPO_WEB = { '@expo/metro-runtime': '~3.2.1', 'react-dom': '18.2.0', 'react-native-web': '~0.18.10', }; +const PACKAGES_TO_ADD_DEV_EXPO_NATIVE = { + 'expo-dev-client': '~5.0.3', +}; + export default async function generateExampleApp({ - type, - dest, - arch, - project, - bobVersion, + config, + destination, reactNativeVersion = 'latest', }: { - type: ExampleApp; - dest: string; - arch: 'new' | 'legacy'; - project: { - slug: string; - name: string; - package: string; - }; - bobVersion: string; - reactNativeVersion?: string; + config: TemplateConfiguration; + destination: string; + reactNativeVersion: string | undefined; }) { - const directory = path.join(dest, 'example'); + const directory = path.join(destination, 'example'); // `npx --package react-native-test-app@latest init --name ${projectName}Example --destination example --version ${reactNativeVersion}` const testAppArgs = [ @@ -68,7 +62,7 @@ export default async function generateExampleApp({ `react-native-test-app@latest`, 'init', '--name', - `${project.name}Example`, + `${config.project.name}Example`, `--destination`, directory, ...(reactNativeVersion !== 'latest' @@ -84,9 +78,9 @@ export default async function generateExampleApp({ const vanillaArgs = [ `@react-native-community/cli`, 'init', - `${project.name}Example`, + `${config.project.name}Example`, '--package-name', - `${project.package}.example`, + `${config.project.package}.example`, '--directory', directory, '--version', @@ -107,7 +101,7 @@ export default async function generateExampleApp({ let args: string[] = []; - switch (type) { + switch (config.example) { case 'vanilla': args = vanillaArgs; break; @@ -131,7 +125,7 @@ export default async function generateExampleApp({ // Patch the example app's package.json const pkg = await fs.readJSON(path.join(directory, 'package.json')); - pkg.name = `${project.slug}-example`; + pkg.name = `${config.project.slug}-example`; // Remove Jest config for now delete pkg.jest; @@ -144,12 +138,12 @@ export default async function generateExampleApp({ const SCRIPTS_TO_ADD = { 'build:android': 'react-native build-android --extra-params "--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a"', - '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"`, + '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"`, }; - if (type === 'vanilla') { + if (config.example === 'vanilla') { Object.assign(scripts, SCRIPTS_TO_ADD); - } else if (type === 'test-app') { + } else if (config.example === 'test-app') { // `react-native-test-app` doesn't bundle application by default in 'Release' mode and also `bundle` command doesn't create a directory. // `mkdist` script should be removed after stable React Native major contains this fix: https://github.com/facebook/react-native/pull/45182. @@ -173,9 +167,9 @@ export default async function generateExampleApp({ const app = await fs.readJSON(path.join(directory, 'app.json')); app.android = app.android || {}; - app.android.package = `${project.package}.example`; + app.android.package = `${config.project.package}.example`; app.ios = app.ios || {}; - app.ios.bundleIdentifier = `${project.package}.example`; + app.ios.bundleIdentifier = `${config.project.package}.example`; await fs.writeJSON(path.join(directory, 'app.json'), app, { spaces: 2, @@ -188,12 +182,12 @@ export default async function generateExampleApp({ }); const PACKAGES_TO_ADD_DEV = { - 'react-native-builder-bob': `^${bobVersion}`, + 'react-native-builder-bob': `^${config.bob.version}`, }; Object.assign(devDependencies, PACKAGES_TO_ADD_DEV); - if (type === 'expo') { + if (config.example === 'expo') { const sdkVersion = dependencies.expo.split('.')[0].replace(/[^\d]/, ''); let bundledNativeModules: Record; @@ -222,18 +216,34 @@ export default async function generateExampleApp({ bundledNativeModules = {}; } - Object.entries(PACKAGES_TO_ADD_WEB).forEach(([name, version]) => { - dependencies[name] = bundledNativeModules[name] || version; - }); + if (config.project.native) { + Object.entries(PACKAGES_TO_ADD_DEV_EXPO_NATIVE).forEach( + ([name, version]) => { + devDependencies[name] = bundledNativeModules[name] || version; + } + ); - scripts.web = 'expo start --web'; + scripts.start = 'expo start --dev-client'; + scripts.android = 'expo run:android'; + scripts.ios = 'expo run:ios'; + + delete scripts.web; + } else { + Object.entries(PACKAGES_TO_ADD_EXPO_WEB).forEach(([name, version]) => { + dependencies[name] = bundledNativeModules[name] || version; + }); + + scripts.web = 'expo start --web'; + } const app = await fs.readJSON(path.join(directory, 'app.json')); + app.expo.name = `${config.project.name} Example`; + app.expo.slug = `${config.project.slug}-example`; app.expo.android = app.expo.android || {}; - app.expo.android.package = `${project.package}.example`; + app.expo.android.package = `${config.project.package}.example`; app.expo.ios = app.expo.ios || {}; - app.expo.ios.bundleIdentifier = `${project.package}.example`; + app.expo.ios.bundleIdentifier = `${config.project.package}.example`; await fs.writeJSON(path.join(directory, 'app.json'), app, { spaces: 2, @@ -250,7 +260,7 @@ export default async function generateExampleApp({ spaces: 2, }); - if (type !== 'expo') { + if (config.example !== 'expo') { let gradleProperties = await fs.readFile( path.join(directory, 'android', 'gradle.properties'), 'utf8' @@ -264,7 +274,7 @@ export default async function generateExampleApp({ ); // If the library is on new architecture, enable new arch for iOS and Android - if (arch === 'new') { + if (config.project.arch === 'new') { // iOS // Add ENV['RCT_NEW_ARCH_ENABLED'] = 1 on top of example/ios/Podfile const podfile = await fs.readFile( diff --git a/packages/create-react-native-library/src/index.ts b/packages/create-react-native-library/src/index.ts index f01eed082..6084ded45 100644 --- a/packages/create-react-native-library/src/index.ts +++ b/packages/create-react-native-library/src/index.ts @@ -88,11 +88,8 @@ async function create(_argv: yargs.Arguments) { spinner.text = 'Generating example app'; await generateExampleApp({ - type: config.example, - dest: folder, - arch: config.project.arch, - project: config.project, - bobVersion, + config, + destination: folder, reactNativeVersion: answers.reactNativeVersion, }); } diff --git a/packages/create-react-native-library/src/input.ts b/packages/create-react-native-library/src/input.ts index 6b02c9a21..f6e924668 100644 --- a/packages/create-react-native-library/src/input.ts +++ b/packages/create-react-native-library/src/input.ts @@ -57,25 +57,25 @@ const LANGUAGE_CHOICES: { const EXAMPLE_CHOICES = ( [ { - title: 'Vanilla', + title: 'App with Expo CLI', + value: 'expo', + description: 'managed expo app for easier upgrades', + disabled: false, + }, + { + title: 'App with Community CLI', value: 'vanilla', description: "provides access to app's native code", disabled: false, }, { - title: 'Test app', + title: 'React Native Test App by Microsoft', value: 'test-app', description: "app's native code is abstracted away", // The test app is disabled for now until proper // Codegen spec shipping is implemented disabled: !process.env.CRNL_ENABLE_TEST_APP, }, - { - title: 'Expo', - value: 'expo', - description: 'managed expo project with web support', - disabled: false, - }, ] as const ).filter((choice) => !choice.disabled); @@ -291,10 +291,8 @@ export async function createQuestions({ message: 'What type of example app do you want to create?', choices: (_, values) => { return EXAMPLE_CHOICES.filter((choice) => { - if (values.type) { - return values.type === 'library' - ? choice.value === 'expo' - : choice.value !== 'expo'; + if (values.type === 'library') { + return choice.value === 'expo'; } return true; diff --git a/packages/create-react-native-library/src/template.ts b/packages/create-react-native-library/src/template.ts index dd7507a12..bdffa3df0 100644 --- a/packages/create-react-native-library/src/template.ts +++ b/packages/create-react-native-library/src/template.ts @@ -54,9 +54,9 @@ const EXAMPLE_MODULE_NEW_FILES = path.resolve( '../templates/example-module-new' ); const EXAMPLE_VIEW_FILES = path.resolve(__dirname, '../templates/example-view'); +const EXAMPLE_EXPO_FILES = path.resolve(__dirname, '../templates/example-expo'); const JS_FILES = path.resolve(__dirname, '../templates/js-library'); -const EXPO_FILES = path.resolve(__dirname, '../templates/expo-library'); const CPP_FILES = path.resolve(__dirname, '../templates/cpp-library'); const NATIVE_COMMON_FILES = path.resolve( __dirname, @@ -186,7 +186,7 @@ export async function applyTemplates( if (answers.languages === 'js') { await applyTemplate(config, JS_FILES, folder); - await applyTemplate(config, EXPO_FILES, folder); + await applyTemplate(config, EXAMPLE_EXPO_FILES, folder); } else { await applyTemplate(config, NATIVE_COMMON_FILES, folder); @@ -194,6 +194,10 @@ export async function applyTemplates( await applyTemplate(config, NATIVE_COMMON_EXAMPLE_FILES, folder); } + if (config.example === 'expo') { + await applyTemplate(config, EXAMPLE_EXPO_FILES, folder); + } + if (config.project.module) { await applyTemplate( config, diff --git a/packages/create-react-native-library/templates/expo-library/example/babel.config.js b/packages/create-react-native-library/templates/example-expo/example/babel.config.js similarity index 100% rename from packages/create-react-native-library/templates/expo-library/example/babel.config.js rename to packages/create-react-native-library/templates/example-expo/example/babel.config.js diff --git a/packages/create-react-native-library/templates/expo-library/example/index.js b/packages/create-react-native-library/templates/example-expo/example/index.js similarity index 100% rename from packages/create-react-native-library/templates/expo-library/example/index.js rename to packages/create-react-native-library/templates/example-expo/example/index.js diff --git a/packages/create-react-native-library/templates/expo-library/example/metro.config.js b/packages/create-react-native-library/templates/example-expo/example/metro.config.js similarity index 100% rename from packages/create-react-native-library/templates/expo-library/example/metro.config.js rename to packages/create-react-native-library/templates/example-expo/example/metro.config.js diff --git a/packages/create-react-native-library/templates/expo-library/example/tsconfig.json b/packages/create-react-native-library/templates/example-expo/example/tsconfig.json similarity index 100% rename from packages/create-react-native-library/templates/expo-library/example/tsconfig.json rename to packages/create-react-native-library/templates/example-expo/example/tsconfig.json