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 https://github.com/facebook/react-native/pull/12170 Differential Revision: D4516241 Pulled By: mkonicek fbshipit-source-id: 8ac081157919872e92947ed64ea64fb48078614d
This commit is contained in:
parent
75c14e3674
commit
a54d449e94
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const copyProjectTemplateAndReplace = require('./copyProjectTemplateAndReplace');
|
||||||
|
const execSync = require('child_process').execSync;
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const availableTemplates = {
|
||||||
|
navigation: 'HelloNavigation',
|
||||||
|
};
|
||||||
|
|
||||||
|
function listTemplatesAndExit(newProjectName, options) {
|
||||||
|
if (options.template === true) {
|
||||||
|
// Just listing templates using 'react-native init --template'.
|
||||||
|
// Not creating a new app.
|
||||||
|
// Print available templates and exit.
|
||||||
|
const templateKeys = Object.keys(availableTemplates);
|
||||||
|
if (templateKeys.length === 0) {
|
||||||
|
// Just a guard, should never happen as long availableTemplates
|
||||||
|
// above is defined correctly :)
|
||||||
|
console.log(
|
||||||
|
'There are no templates available besides ' +
|
||||||
|
'the default "Hello World" one.'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
'The available templates are:\n' +
|
||||||
|
templateKeys.join('\n') +
|
||||||
|
'\nYou can use these to create an app based on a template, for example: ' +
|
||||||
|
'you could run: ' +
|
||||||
|
'react-native init ' + newProjectName + ' --template ' + templateKeys[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Exit 'react-native init'
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Continue 'react-native init'
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param newProjectName For example 'AwesomeApp'.
|
||||||
|
* @param templateKey Template to use, for example 'navigation'.
|
||||||
|
* @param yarnVersion Version of yarn available on the system, or null if
|
||||||
|
* yarn is not available. For example '0.18.1'.
|
||||||
|
*/
|
||||||
|
function createProjectFromTemplate(destPath, newProjectName, templateKey, yarnVersion) {
|
||||||
|
// Expand the basic 'HelloWorld' template
|
||||||
|
copyProjectTemplateAndReplace(
|
||||||
|
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld'),
|
||||||
|
destPath,
|
||||||
|
newProjectName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (templateKey !== undefined) {
|
||||||
|
// Keep the files from the 'HelloWorld' template, and overwrite some of them
|
||||||
|
// with the specified project template.
|
||||||
|
// The 'HelloWorld' template contains the native files (these are used by
|
||||||
|
// all templates) and every other template only contains additional JS code.
|
||||||
|
// Reason:
|
||||||
|
// This way we don't have to duplicate the native files in every template.
|
||||||
|
// If we duplicated them we'd make RN larger and risk that people would
|
||||||
|
// forget to maintain all the copies so they would go out of sync.
|
||||||
|
const templateName = availableTemplates[templateKey];
|
||||||
|
if (templateName) {
|
||||||
|
copyProjectTemplateAndReplace(
|
||||||
|
path.resolve(
|
||||||
|
'node_modules', 'react-native', 'local-cli', 'templates', templateName
|
||||||
|
),
|
||||||
|
destPath,
|
||||||
|
newProjectName
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error('Uknown template: ' + templateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add dependencies:
|
||||||
|
|
||||||
|
// dependencies.json is a special file that lists additional dependencies
|
||||||
|
// that are required by this template
|
||||||
|
const dependenciesJsonPath = path.resolve(
|
||||||
|
'node_modules', 'react-native', 'local-cli', 'templates', templateName, 'dependencies.json'
|
||||||
|
);
|
||||||
|
if (fs.existsSync(dependenciesJsonPath)) {
|
||||||
|
console.log('Adding dependencies for the project...');
|
||||||
|
const dependencies = JSON.parse(fs.readFileSync(dependenciesJsonPath));
|
||||||
|
for (let depName in dependencies) {
|
||||||
|
const depVersion = dependencies[depName];
|
||||||
|
const depToInstall = depName + '@' + depVersion;
|
||||||
|
console.log('Adding ' + depToInstall + '...');
|
||||||
|
if (yarnVersion) {
|
||||||
|
execSync(`yarn add ${depToInstall}`, {stdio: 'inherit'});
|
||||||
|
} else {
|
||||||
|
execSync(`npm install ${depToInstall} --save --save-exact`, {stdio: 'inherit'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
listTemplatesAndExit,
|
||||||
|
createProjectFromTemplate,
|
||||||
|
};
|
|
@ -8,7 +8,10 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const copyProjectTemplateAndReplace = require('../generator/copyProjectTemplateAndReplace');
|
const {
|
||||||
|
listTemplatesAndExit,
|
||||||
|
createProjectFromTemplate,
|
||||||
|
} = require('../generator/templates');
|
||||||
const execSync = require('child_process').execSync;
|
const execSync = require('child_process').execSync;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const minimist = require('minimist');
|
const minimist = require('minimist');
|
||||||
|
@ -23,15 +26,15 @@ const yarn = require('../util/yarn');
|
||||||
* @param projectDir Templates will be copied here.
|
* @param projectDir Templates will be copied here.
|
||||||
* @param argsOrName Project name or full list of custom arguments
|
* @param argsOrName Project name or full list of custom arguments
|
||||||
* for the generator.
|
* for the generator.
|
||||||
|
* @param options Command line options passed from the react-native-cli directly.
|
||||||
|
* E.g. `{ version: '0.43.0', template: 'navigation' }`
|
||||||
*/
|
*/
|
||||||
function init(projectDir, argsOrName) {
|
function init(projectDir, argsOrName) {
|
||||||
console.log('Setting up new React Native app in ' + projectDir);
|
|
||||||
|
|
||||||
const args = Array.isArray(argsOrName)
|
const args = Array.isArray(argsOrName)
|
||||||
? argsOrName // argsOrName was e.g. ['AwesomeApp', '--verbose']
|
? argsOrName // argsOrName was e.g. ['AwesomeApp', '--verbose']
|
||||||
: [argsOrName].concat(process.argv.slice(4)); // argsOrName was e.g. 'AwesomeApp'
|
: [argsOrName].concat(process.argv.slice(4)); // argsOrName was e.g. 'AwesomeApp'
|
||||||
|
|
||||||
// args array is e.g. ['AwesomeApp', '--verbose']
|
// args array is e.g. ['AwesomeApp', '--verbose', '--template', 'navigation']
|
||||||
if (!args || args.length === 0) {
|
if (!args || args.length === 0) {
|
||||||
console.error('react-native init requires a project name.');
|
console.error('react-native init requires a project name.');
|
||||||
return;
|
return;
|
||||||
|
@ -40,7 +43,14 @@ function init(projectDir, argsOrName) {
|
||||||
const newProjectName = args[0];
|
const newProjectName = args[0];
|
||||||
const options = minimist(args);
|
const options = minimist(args);
|
||||||
|
|
||||||
generateProject(projectDir, newProjectName, options);
|
if (listTemplatesAndExit(newProjectName, options)) {
|
||||||
|
// Just listing templates using 'react-native init --template'
|
||||||
|
// Not creating a new app.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log('Setting up new React Native app in ' + projectDir);
|
||||||
|
generateProject(projectDir, newProjectName, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,11 +77,7 @@ function generateProject(destinationRoot, newProjectName, options) {
|
||||||
yarn.getYarnVersionIfAvailable() &&
|
yarn.getYarnVersionIfAvailable() &&
|
||||||
yarn.isGlobalCliUsingYarn(destinationRoot);
|
yarn.isGlobalCliUsingYarn(destinationRoot);
|
||||||
|
|
||||||
copyProjectTemplateAndReplace(
|
createProjectFromTemplate(destinationRoot, newProjectName, options.template, yarnVersion);
|
||||||
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld'),
|
|
||||||
destinationRoot,
|
|
||||||
newProjectName
|
|
||||||
);
|
|
||||||
|
|
||||||
if (yarnVersion) {
|
if (yarnVersion) {
|
||||||
console.log('Adding React...');
|
console.log('Adding React...');
|
||||||
|
|
|
@ -193,7 +193,7 @@ function runOnAllDevices(args, cmd, packageName, adbPath){
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.bold(
|
console.log(chalk.bold(
|
||||||
`Building and installing the app on the device (cd android && ${cmd} ${gradleArgs.join(' ')}...`
|
`Building and installing the app on the device (cd android && ${cmd} ${gradleArgs.join(' ')})...`
|
||||||
));
|
));
|
||||||
|
|
||||||
child_process.execFileSync(cmd, gradleArgs, {
|
child_process.execFileSync(cmd, gradleArgs, {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"react-navigation": "1.0.0-beta.1"
|
||||||
|
}
|
|
@ -2,4 +2,4 @@ import { AppRegistry } from 'react-native';
|
||||||
|
|
||||||
import MainNavigator from './views/MainNavigator';
|
import MainNavigator from './views/MainNavigator';
|
||||||
|
|
||||||
AppRegistry.registerComponent('ChatExample', () => MainNavigator);
|
AppRegistry.registerComponent('HelloWorld', () => MainNavigator);
|
||||||
|
|
|
@ -2,4 +2,4 @@ import { AppRegistry } from 'react-native';
|
||||||
|
|
||||||
import MainNavigator from './views/MainNavigator';
|
import MainNavigator from './views/MainNavigator';
|
||||||
|
|
||||||
AppRegistry.registerComponent('ChatExample', () => MainNavigator);
|
AppRegistry.registerComponent('HelloWorld', () => MainNavigator);
|
||||||
|
|
|
@ -7,18 +7,18 @@ import {
|
||||||
import { TabNavigator } from 'react-navigation';
|
import { TabNavigator } from 'react-navigation';
|
||||||
|
|
||||||
import ChatListScreen from './chat/ChatListScreen';
|
import ChatListScreen from './chat/ChatListScreen';
|
||||||
import FriendListScreen from './friends/FriendListScreen';
|
import WelcomeScreen from './welcome/WelcomeScreen';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Screen with tabs shown on app startup.
|
* Screen with tabs shown on app startup.
|
||||||
*/
|
*/
|
||||||
const HomeScreenTabNavigator = TabNavigator({
|
const HomeScreenTabNavigator = TabNavigator({
|
||||||
|
Welcome: {
|
||||||
|
screen: WelcomeScreen,
|
||||||
|
},
|
||||||
Chats: {
|
Chats: {
|
||||||
screen: ChatListScreen,
|
screen: ChatListScreen,
|
||||||
},
|
},
|
||||||
Friends: {
|
|
||||||
screen: FriendListScreen,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default HomeScreenTabNavigator;
|
export default HomeScreenTabNavigator;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import ListItem from '../../components/ListItem';
|
||||||
export default class ChatListScreen extends Component {
|
export default class ChatListScreen extends Component {
|
||||||
|
|
||||||
static navigationOptions = {
|
static navigationOptions = {
|
||||||
title: 'Chats',
|
title: 'Friends',
|
||||||
header: {
|
header: {
|
||||||
visible: Platform.OS === 'ios',
|
visible: Platform.OS === 'ios',
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,11 +8,12 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
import ListItem from '../../components/ListItem';
|
import ListItem from '../../components/ListItem';
|
||||||
|
import WelcomeText from './WelcomeText';
|
||||||
|
|
||||||
export default class FriendListScreen extends Component {
|
export default class WelcomeScreen extends Component {
|
||||||
|
|
||||||
static navigationOptions = {
|
static navigationOptions = {
|
||||||
title: 'Friends',
|
title: 'Welcome',
|
||||||
header: {
|
header: {
|
||||||
visible: Platform.OS === 'ios',
|
visible: Platform.OS === 'ios',
|
||||||
},
|
},
|
||||||
|
@ -20,7 +21,7 @@ export default class FriendListScreen extends Component {
|
||||||
icon: ({ tintColor }) => (
|
icon: ({ tintColor }) => (
|
||||||
<Image
|
<Image
|
||||||
// Using react-native-vector-icons works here too
|
// Using react-native-vector-icons works here too
|
||||||
source={require('./friend-icon.png')}
|
source={require('./welcome-icon.png')}
|
||||||
style={[styles.icon, {tintColor: tintColor}]}
|
style={[styles.icon, {tintColor: tintColor}]}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -29,9 +30,7 @@ export default class FriendListScreen extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<WelcomeText />
|
||||||
<Text>A list of friends here.</Text>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import {
|
||||||
|
AppRegistry,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
export default class WelcomeText extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.welcome}>
|
||||||
|
Welcome to React Native!
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
This app shows the basics of navigating between a few screens,
|
||||||
|
working with ListView and handling text input.
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
Modify any files to get started. For example try changing the
|
||||||
|
file views/welcome/WelcomeText.android.js.
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
Press Cmd+R to reload,{'\n'}
|
||||||
|
Cmd+D or shake for dev menu.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
welcome: {
|
||||||
|
fontSize: 20,
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: 16,
|
||||||
|
},
|
||||||
|
instructions: {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#333333',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import {
|
||||||
|
AppRegistry,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
export default class WelcomeText extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.welcome}>
|
||||||
|
Welcome to React Native!
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
This app shows the basics of navigating between a few screens,
|
||||||
|
working with ListView and handling text input.
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
Modify any files to get started. For example try changing the
|
||||||
|
file{'\n'}views/welcome/WelcomeText.ios.js.
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.instructions}>
|
||||||
|
Press Cmd+R to reload,{'\n'}
|
||||||
|
Cmd+D or shake for dev menu.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
welcome: {
|
||||||
|
fontSize: 20,
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: 16,
|
||||||
|
},
|
||||||
|
instructions: {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#333333',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
});
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
@ -48,6 +48,7 @@ var semver = require('semver');
|
||||||
* if you are in a RN app folder
|
* if you are in a RN app folder
|
||||||
* init - to create a new project and npm install it
|
* init - to create a new project and npm install it
|
||||||
* --verbose - to print logs while init
|
* --verbose - to print logs while init
|
||||||
|
* --template - name of the template to use, e.g. --template navigation
|
||||||
* --version <alternative react-native package> - override default (https://registry.npmjs.org/react-native@latest),
|
* --version <alternative react-native package> - override default (https://registry.npmjs.org/react-native@latest),
|
||||||
* package to install, examples:
|
* package to install, examples:
|
||||||
* - "0.22.0-rc1" - A new app will be created using a specific version of React Native from npm repo
|
* - "0.22.0-rc1" - A new app will be created using a specific version of React Native from npm repo
|
||||||
|
@ -129,7 +130,8 @@ if (cli) {
|
||||||
' Options:',
|
' Options:',
|
||||||
'',
|
'',
|
||||||
' -h, --help output usage information',
|
' -h, --help output usage information',
|
||||||
' -v, --version output the version number',
|
' -v, --version use a specific version of React Native',
|
||||||
|
' --template use an app template. Use --template to see available templates.',
|
||||||
'',
|
'',
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
@ -264,8 +266,7 @@ function getInstallPackage(rnPackage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(root, projectName, options) {
|
function run(root, projectName, options) {
|
||||||
// E.g. '0.38' or '/path/to/archive.tgz'
|
const rnPackage = options.version; // e.g. '0.38' or '/path/to/archive.tgz'
|
||||||
const rnPackage = options.version;
|
|
||||||
const forceNpmClient = options.npm;
|
const forceNpmClient = options.npm;
|
||||||
const yarnVersion = (!forceNpmClient) && getYarnVersionIfAvailable();
|
const yarnVersion = (!forceNpmClient) && getYarnVersionIfAvailable();
|
||||||
var installCommand;
|
var installCommand;
|
||||||
|
|
Loading…
Reference in New Issue