Skip to content

Commit bc74dd1

Browse files
Eric Vicentifacebook-github-bot
authored andcommitted
Eject CLI command to re-create native folders
Summary: The iOS and Android native folders of a React Native app can be difficult to maintain. This introduces a new workflow for creating and maintaining the native code of your app. Now it will be possible to: 1. Remove the native iOS or Android folders 2. Create an `app.json` for your app, with at least a `name` and `displayName` 3. Run `react-native eject`, and the native code for your app will be generated Then, as usual, you can run `react-native run-ios` and `react-native run-android`, to build and launch your app For apps that don't have any native code, it will be possible to ignore the `ios` and `android` folders from version control. Eject step tested in RN app by deleting native folders. mkonicek, what is the best way to test `react-native init`? As follow-up items, we can enable the following: - Configuring app icon and launch screen from the `app.json` - Automatically run `react-native link` for native libraries - A Closes facebook#12162 Differential Revision: D4509138 Pulled By: ericvicenti fbshipit-source-id: 0ee213e68f0a3d44bfce337e3ec43e5024bacc66
1 parent 0a71f48 commit bc74dd1

File tree

6 files changed

+109
-3
lines changed

6 files changed

+109
-3
lines changed

local-cli/commands.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const documentedCommands = [
4242
require('./library/library'),
4343
require('./bundle/bundle'),
4444
require('./bundle/unbundle'),
45+
require('./eject/eject'),
4546
require('./link/link'),
4647
require('./link/unlink'),
4748
require('./install/install'),

local-cli/eject/eject.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
'use strict';
10+
11+
const copyProjectTemplateAndReplace = require('../generator/copyProjectTemplateAndReplace');
12+
const path = require('path');
13+
const fs = require('fs');
14+
15+
/**
16+
* The eject command re-creates the `android` and `ios` native folders. Because native code can be
17+
* difficult to maintain, this new script allows an `app.json` to be defined for the project, which
18+
* is used to configure the native app.
19+
*
20+
* The `app.json` config may contain the following keys:
21+
*
22+
* - `name` - The short name used for the project, should be TitleCase
23+
* - `displayName` - The app's name on the home screen
24+
*/
25+
26+
function eject() {
27+
28+
const doesIOSExist = fs.existsSync(path.resolve('ios'));
29+
const doesAndroidExist = fs.existsSync(path.resolve('android'));
30+
if (doesIOSExist && doesAndroidExist) {
31+
console.error(
32+
'Both the iOS and Android folders already exist! Please delete `ios` and/or `android` ' +
33+
'before ejecting.'
34+
);
35+
process.exit(1);
36+
}
37+
38+
let appConfig = null;
39+
try {
40+
appConfig = require(path.resolve('app.json'));
41+
} catch(e) {
42+
console.error(
43+
`Eject requires an \`app.json\` config file to be located at ` +
44+
`${path.resolve('app.json')}, and it must at least specify a \`name\` for the project ` +
45+
`name, and a \`displayName\` for the app's home screen label.`
46+
);
47+
process.exit(1);
48+
}
49+
50+
const appName = appConfig.name;
51+
if (!appName) {
52+
console.error(
53+
`App \`name\` must be defined in the \`app.json\` config file to define the project name. `+
54+
`It must not contain any spaces or dashes.`
55+
);
56+
process.exit(1);
57+
}
58+
const displayName = appConfig.displayName;
59+
if (!displayName) {
60+
console.error(
61+
`App \`displayName\` must be defined in the \`app.json\` config file, to define the label ` +
62+
`of the app on the home screen.`
63+
);
64+
process.exit(1);
65+
}
66+
67+
const templateOptions = { displayName };
68+
69+
if (!doesIOSExist) {
70+
console.log('Generating the iOS folder.');
71+
copyProjectTemplateAndReplace(
72+
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld', 'ios'),
73+
path.resolve('ios'),
74+
appName,
75+
templateOptions
76+
);
77+
}
78+
79+
if (!doesAndroidExist) {
80+
console.log('Generating the Android folder.');
81+
copyProjectTemplateAndReplace(
82+
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld', 'android'),
83+
path.resolve('android'),
84+
appName,
85+
templateOptions
86+
);
87+
}
88+
89+
}
90+
91+
module.exports = {
92+
name: 'eject',
93+
description: 'Re-create the iOS and Android folders and native code',
94+
func: eject,
95+
options: [],
96+
};

local-cli/generator/copyProjectTemplateAndReplace.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option
2727
if (!destPath) { throw new Error('Need a path to copy to'); }
2828
if (!newProjectName) { throw new Error('Need a project name'); }
2929

30+
options = options || {};
31+
3032
walk(srcPath).forEach(absoluteSrcFilePath => {
3133

3234
// 'react-native upgrade'
33-
if (options && options.upgrade) {
35+
if (options.upgrade) {
3436
// Don't upgrade these files
3537
const fileName = path.basename(absoluteSrcFilePath);
3638
// This also includes __tests__/index.*.js
@@ -44,7 +46,7 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option
4446
.replace(/helloworld/g, newProjectName.toLowerCase());
4547

4648
let contentChangedCallback = null;
47-
if (options && options.upgrade && (!options.force)) {
49+
if (options.upgrade && (!options.force)) {
4850
contentChangedCallback = (_, contentChanged) => {
4951
return upgradeFileContentChangedCallback(
5052
absoluteSrcFilePath,
@@ -57,6 +59,7 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option
5759
absoluteSrcFilePath,
5860
path.resolve(destPath, relativeRenamedPath),
5961
{
62+
'Hello App Display Name': options.displayName || newProjectName,
6063
'HelloWorld': newProjectName,
6164
'helloworld': newProjectName.toLowerCase(),
6265
},
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<resources>
2-
<string name="app_name">HelloWorld</string>
2+
<string name="app_name">Hello App Display Name</string>
33
</resources>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "HelloWorld",
3+
"displayName": "HelloWorld"
4+
}

local-cli/templates/HelloWorld/ios/HelloWorld/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<dict>
55
<key>CFBundleDevelopmentRegion</key>
66
<string>en</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>Hello App Display Name</string>
79
<key>CFBundleExecutable</key>
810
<string>$(EXECUTABLE_NAME)</string>
911
<key>CFBundleIdentifier</key>

0 commit comments

Comments
 (0)