Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for expo example in native libraries #725

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import path from 'path';
import https from 'https';
import { spawn } from '../utils/spawn';
import sortObjectKeys from '../utils/sortObjectKeys';
import type { ExampleApp } from '../input';
import dedent from 'dedent';
import type { TemplateConfiguration } from '../template';

const FILES_TO_DELETE = [
'__tests__',
Expand Down Expand Up @@ -35,40 +36,34 @@ 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 = [
'--package',
`react-native-test-app@latest`,
'init',
'--name',
`${project.name}Example`,
`${config.project.name}Example`,
`--destination`,
directory,
...(reactNativeVersion !== 'latest'
Expand All @@ -84,9 +79,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',
Expand All @@ -107,7 +102,7 @@ export default async function generateExampleApp({

let args: string[] = [];

switch (type) {
switch (config.example) {
case 'vanilla':
args = vanillaArgs;
break;
Expand All @@ -131,7 +126,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;
Expand All @@ -144,12 +139,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.

Expand All @@ -173,9 +168,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,
Expand All @@ -188,12 +183,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<string, string>;
Expand Down Expand Up @@ -222,18 +217,43 @@ 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.start = 'expo start --dev-client';
scripts.android = 'expo run:android';
scripts.ios = 'expo run:ios';

scripts.web = 'expo start --web';
delete scripts.web;

await fs.writeFile(
path.join(directory, '.gitignore'),
dedent`
# These folders are generated with prebuild (CNG)
android/
ios/
`
);
} 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,
Expand All @@ -250,7 +270,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'
Expand All @@ -264,7 +284,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(
Expand Down
7 changes: 2 additions & 5 deletions packages/create-react-native-library/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,8 @@ async function create(_argv: yargs.Arguments<Args>) {
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,
});
}
Expand Down
29 changes: 17 additions & 12 deletions packages/create-react-native-library/src/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -291,10 +291,15 @@ 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';
}

if (
values.type === 'legacy-module' ||
values.type === 'legacy-view'
) {
return choice.value !== 'expo';
}

return true;
Expand Down
8 changes: 6 additions & 2 deletions packages/create-react-native-library/src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -186,14 +186,18 @@ 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);

if (config.example !== 'none') {
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
const path = require('path');
<% if (example !== 'expo') { -%>
const pkg = require('../package.json');
<% } -%>
<% if (example === 'test-app') { -%>
const { configureProjects } = require('react-native-test-app');
<% } -%>
<% if (example === 'expo') { -%>

// FIXME: `__dirname` is not set correctly in Expo
// When building the, the cwc is `example/ios`
// So we override `__dirname` based on that
// https://github.com/expo/expo/pull/33532
__dirname = path.resolve(process.cwd(), '..');

const pkg = require(path.join(__dirname, '..', 'package.json'));
<% } -%>

module.exports = {
<% if (example === 'test-app') { -%>
Expand All @@ -15,7 +27,7 @@ module.exports = {
automaticPodsInstallation: true,
},
}),
<% } else { -%>
<% } else if (example === 'vanila') { -%>
project: {
ios: {
automaticPodsInstallation: true,
Expand Down
Loading