Skip to content

Commit a54d449

Browse files
Martin Konicekfacebook-github-bot
authored andcommitted
CLI: Add support for project templates
Summary: Currently it is not trivial for people to get started with React Native. `react-native init MyApp` just creates a simple app with a single screen. People have to spend time figuring out how to add more screens, or how to accomplish very basic tasks such as rendering a list of data or handling text input. Let's add an option: `react-native init --template navigation` - this creates a "starter" app which can be easily tweaked into the actual app the person wants to build. **Test plan (required)** - Checked that 'react-native init MyApp' still works as before: <img width="487" alt="screenshot 2017-02-02 16 56 28" src="https://cloud.githubusercontent.com/assets/346214/22559344/b2348ebe-e968-11e6-9032-d1c33216f490.png"> <img width="603" alt="screenshot 2017-02-02 16 58 04" src="https://cloud.githubusercontent.com/assets/346214/22559370/c96a2ca6-e968-11e6-91f7-7afb967920fc.png"> - Ran 'react-native init MyNavApp --template'. This prints the available templates: ``` $ react-native init MyNavApp Closes react#12170 Differential Revision: D4516241 Pulled By: mkonicek fbshipit-source-id: 8ac081157919872e92947ed64ea64fb48078614d
1 parent 75c14e3 commit a54d449

13 files changed

Lines changed: 250 additions & 27 deletions

File tree

local-cli/generator/templates.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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('./copyProjectTemplateAndReplace');
12+
const execSync = require('child_process').execSync;
13+
const fs = require('fs');
14+
const path = require('path');
15+
16+
const availableTemplates = {
17+
navigation: 'HelloNavigation',
18+
};
19+
20+
function listTemplatesAndExit(newProjectName, options) {
21+
if (options.template === true) {
22+
// Just listing templates using 'react-native init --template'.
23+
// Not creating a new app.
24+
// Print available templates and exit.
25+
const templateKeys = Object.keys(availableTemplates);
26+
if (templateKeys.length === 0) {
27+
// Just a guard, should never happen as long availableTemplates
28+
// above is defined correctly :)
29+
console.log(
30+
'There are no templates available besides ' +
31+
'the default "Hello World" one.'
32+
);
33+
} else {
34+
console.log(
35+
'The available templates are:\n' +
36+
templateKeys.join('\n') +
37+
'\nYou can use these to create an app based on a template, for example: ' +
38+
'you could run: ' +
39+
'react-native init ' + newProjectName + ' --template ' + templateKeys[0]
40+
);
41+
}
42+
// Exit 'react-native init'
43+
return true;
44+
}
45+
// Continue 'react-native init'
46+
return false;
47+
}
48+
49+
/**
50+
* @param newProjectName For example 'AwesomeApp'.
51+
* @param templateKey Template to use, for example 'navigation'.
52+
* @param yarnVersion Version of yarn available on the system, or null if
53+
* yarn is not available. For example '0.18.1'.
54+
*/
55+
function createProjectFromTemplate(destPath, newProjectName, templateKey, yarnVersion) {
56+
// Expand the basic 'HelloWorld' template
57+
copyProjectTemplateAndReplace(
58+
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld'),
59+
destPath,
60+
newProjectName
61+
);
62+
63+
if (templateKey !== undefined) {
64+
// Keep the files from the 'HelloWorld' template, and overwrite some of them
65+
// with the specified project template.
66+
// The 'HelloWorld' template contains the native files (these are used by
67+
// all templates) and every other template only contains additional JS code.
68+
// Reason:
69+
// This way we don't have to duplicate the native files in every template.
70+
// If we duplicated them we'd make RN larger and risk that people would
71+
// forget to maintain all the copies so they would go out of sync.
72+
const templateName = availableTemplates[templateKey];
73+
if (templateName) {
74+
copyProjectTemplateAndReplace(
75+
path.resolve(
76+
'node_modules', 'react-native', 'local-cli', 'templates', templateName
77+
),
78+
destPath,
79+
newProjectName
80+
);
81+
} else {
82+
throw new Error('Uknown template: ' + templateKey);
83+
}
84+
85+
// Add dependencies:
86+
87+
// dependencies.json is a special file that lists additional dependencies
88+
// that are required by this template
89+
const dependenciesJsonPath = path.resolve(
90+
'node_modules', 'react-native', 'local-cli', 'templates', templateName, 'dependencies.json'
91+
);
92+
if (fs.existsSync(dependenciesJsonPath)) {
93+
console.log('Adding dependencies for the project...');
94+
const dependencies = JSON.parse(fs.readFileSync(dependenciesJsonPath));
95+
for (let depName in dependencies) {
96+
const depVersion = dependencies[depName];
97+
const depToInstall = depName + '@' + depVersion;
98+
console.log('Adding ' + depToInstall + '...');
99+
if (yarnVersion) {
100+
execSync(`yarn add ${depToInstall}`, {stdio: 'inherit'});
101+
} else {
102+
execSync(`npm install ${depToInstall} --save --save-exact`, {stdio: 'inherit'});
103+
}
104+
}
105+
}
106+
}
107+
}
108+
109+
module.exports = {
110+
listTemplatesAndExit,
111+
createProjectFromTemplate,
112+
};

local-cli/init/init.js

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
*/
99
'use strict';
1010

11-
const copyProjectTemplateAndReplace = require('../generator/copyProjectTemplateAndReplace');
11+
const {
12+
listTemplatesAndExit,
13+
createProjectFromTemplate,
14+
} = require('../generator/templates');
1215
const execSync = require('child_process').execSync;
1316
const fs = require('fs');
1417
const minimist = require('minimist');
@@ -23,15 +26,15 @@ const yarn = require('../util/yarn');
2326
* @param projectDir Templates will be copied here.
2427
* @param argsOrName Project name or full list of custom arguments
2528
* for the generator.
29+
* @param options Command line options passed from the react-native-cli directly.
30+
* E.g. `{ version: '0.43.0', template: 'navigation' }`
2631
*/
2732
function init(projectDir, argsOrName) {
28-
console.log('Setting up new React Native app in ' + projectDir);
29-
3033
const args = Array.isArray(argsOrName)
3134
? argsOrName // argsOrName was e.g. ['AwesomeApp', '--verbose']
3235
: [argsOrName].concat(process.argv.slice(4)); // argsOrName was e.g. 'AwesomeApp'
3336

34-
// args array is e.g. ['AwesomeApp', '--verbose']
37+
// args array is e.g. ['AwesomeApp', '--verbose', '--template', 'navigation']
3538
if (!args || args.length === 0) {
3639
console.error('react-native init requires a project name.');
3740
return;
@@ -40,7 +43,14 @@ function init(projectDir, argsOrName) {
4043
const newProjectName = args[0];
4144
const options = minimist(args);
4245

43-
generateProject(projectDir, newProjectName, options);
46+
if (listTemplatesAndExit(newProjectName, options)) {
47+
// Just listing templates using 'react-native init --template'
48+
// Not creating a new app.
49+
return;
50+
} else {
51+
console.log('Setting up new React Native app in ' + projectDir);
52+
generateProject(projectDir, newProjectName, options);
53+
}
4454
}
4555

4656
/**
@@ -67,11 +77,7 @@ function generateProject(destinationRoot, newProjectName, options) {
6777
yarn.getYarnVersionIfAvailable() &&
6878
yarn.isGlobalCliUsingYarn(destinationRoot);
6979

70-
copyProjectTemplateAndReplace(
71-
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld'),
72-
destinationRoot,
73-
newProjectName
74-
);
80+
createProjectFromTemplate(destinationRoot, newProjectName, options.template, yarnVersion);
7581

7682
if (yarnVersion) {
7783
console.log('Adding React...');

local-cli/runAndroid/runAndroid.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ function runOnAllDevices(args, cmd, packageName, adbPath){
193193
}
194194

195195
console.log(chalk.bold(
196-
`Building and installing the app on the device (cd android && ${cmd} ${gradleArgs.join(' ')}...`
196+
`Building and installing the app on the device (cd android && ${cmd} ${gradleArgs.join(' ')})...`
197197
));
198198

199199
child_process.execFileSync(cmd, gradleArgs, {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"react-navigation": "1.0.0-beta.1"
3+
}

local-cli/templates/HelloNavigation/index.android.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import { AppRegistry } from 'react-native';
22

33
import MainNavigator from './views/MainNavigator';
44

5-
AppRegistry.registerComponent('ChatExample', () => MainNavigator);
5+
AppRegistry.registerComponent('HelloWorld', () => MainNavigator);

local-cli/templates/HelloNavigation/index.ios.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import { AppRegistry } from 'react-native';
22

33
import MainNavigator from './views/MainNavigator';
44

5-
AppRegistry.registerComponent('ChatExample', () => MainNavigator);
5+
AppRegistry.registerComponent('HelloWorld', () => MainNavigator);

local-cli/templates/HelloNavigation/views/HomeScreenTabNavigator.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import {
77
import { TabNavigator } from 'react-navigation';
88

99
import ChatListScreen from './chat/ChatListScreen';
10-
import FriendListScreen from './friends/FriendListScreen';
10+
import WelcomeScreen from './welcome/WelcomeScreen';
1111

1212
/**
1313
* Screen with tabs shown on app startup.
1414
*/
1515
const HomeScreenTabNavigator = TabNavigator({
16+
Welcome: {
17+
screen: WelcomeScreen,
18+
},
1619
Chats: {
1720
screen: ChatListScreen,
1821
},
19-
Friends: {
20-
screen: FriendListScreen,
21-
},
2222
});
2323

2424
export default HomeScreenTabNavigator;

local-cli/templates/HelloNavigation/views/chat/ChatListScreen.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import ListItem from '../../components/ListItem';
1010
export default class ChatListScreen extends Component {
1111

1212
static navigationOptions = {
13-
title: 'Chats',
13+
title: 'Friends',
1414
header: {
1515
visible: Platform.OS === 'ios',
1616
},

local-cli/templates/HelloNavigation/views/friends/FriendListScreen.js renamed to local-cli/templates/HelloNavigation/views/welcome/WelcomeScreen.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ import {
88
} from 'react-native';
99

1010
import ListItem from '../../components/ListItem';
11+
import WelcomeText from './WelcomeText';
1112

12-
export default class FriendListScreen extends Component {
13+
export default class WelcomeScreen extends Component {
1314

1415
static navigationOptions = {
15-
title: 'Friends',
16+
title: 'Welcome',
1617
header: {
1718
visible: Platform.OS === 'ios',
1819
},
1920
tabBar: {
2021
icon: ({ tintColor }) => (
2122
<Image
2223
// Using react-native-vector-icons works here too
23-
source={require('./friend-icon.png')}
24+
source={require('./welcome-icon.png')}
2425
style={[styles.icon, {tintColor: tintColor}]}
2526
/>
2627
),
@@ -29,9 +30,7 @@ export default class FriendListScreen extends Component {
2930

3031
render() {
3132
return (
32-
<View style={styles.container}>
33-
<Text>A list of friends here.</Text>
34-
</View>
33+
<WelcomeText />
3534
);
3635
}
3736
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { Component } from 'react';
2+
import {
3+
AppRegistry,
4+
StyleSheet,
5+
Text,
6+
View
7+
} from 'react-native';
8+
9+
export default class WelcomeText extends Component {
10+
render() {
11+
return (
12+
<View style={styles.container}>
13+
<Text style={styles.welcome}>
14+
Welcome to React Native!
15+
</Text>
16+
<Text style={styles.instructions}>
17+
This app shows the basics of navigating between a few screens,
18+
working with ListView and handling text input.
19+
</Text>
20+
<Text style={styles.instructions}>
21+
Modify any files to get started. For example try changing the
22+
file views/welcome/WelcomeText.android.js.
23+
</Text>
24+
<Text style={styles.instructions}>
25+
Press Cmd+R to reload,{'\n'}
26+
Cmd+D or shake for dev menu.
27+
</Text>
28+
</View>
29+
);
30+
}
31+
}
32+
33+
const styles = StyleSheet.create({
34+
container: {
35+
flex: 1,
36+
justifyContent: 'center',
37+
alignItems: 'center',
38+
backgroundColor: 'white',
39+
padding: 20,
40+
},
41+
welcome: {
42+
fontSize: 20,
43+
textAlign: 'center',
44+
margin: 16,
45+
},
46+
instructions: {
47+
textAlign: 'center',
48+
color: '#333333',
49+
marginBottom: 12,
50+
},
51+
});

0 commit comments

Comments
 (0)