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';
|
||||
|
||||
const copyProjectTemplateAndReplace = require('../generator/copyProjectTemplateAndReplace');
|
||||
const {
|
||||
listTemplatesAndExit,
|
||||
createProjectFromTemplate,
|
||||
} = require('../generator/templates');
|
||||
const execSync = require('child_process').execSync;
|
||||
const fs = require('fs');
|
||||
const minimist = require('minimist');
|
||||
|
@ -23,15 +26,15 @@ const yarn = require('../util/yarn');
|
|||
* @param projectDir Templates will be copied here.
|
||||
* @param argsOrName Project name or full list of custom arguments
|
||||
* 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) {
|
||||
console.log('Setting up new React Native app in ' + projectDir);
|
||||
|
||||
const args = Array.isArray(argsOrName)
|
||||
? argsOrName // argsOrName was e.g. ['AwesomeApp', '--verbose']
|
||||
: [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) {
|
||||
console.error('react-native init requires a project name.');
|
||||
return;
|
||||
|
@ -40,7 +43,14 @@ function init(projectDir, argsOrName) {
|
|||
const newProjectName = args[0];
|
||||
const options = minimist(args);
|
||||
|
||||
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.isGlobalCliUsingYarn(destinationRoot);
|
||||
|
||||
copyProjectTemplateAndReplace(
|
||||
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld'),
|
||||
destinationRoot,
|
||||
newProjectName
|
||||
);
|
||||
createProjectFromTemplate(destinationRoot, newProjectName, options.template, yarnVersion);
|
||||
|
||||
if (yarnVersion) {
|
||||
console.log('Adding React...');
|
||||
|
|
|
@ -193,7 +193,7 @@ function runOnAllDevices(args, cmd, packageName, adbPath){
|
|||
}
|
||||
|
||||
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, {
|
||||
|
|
|
@ -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';
|
||||
|
||||
AppRegistry.registerComponent('ChatExample', () => MainNavigator);
|
||||
AppRegistry.registerComponent('HelloWorld', () => MainNavigator);
|
||||
|
|
|
@ -2,4 +2,4 @@ import { AppRegistry } from 'react-native';
|
|||
|
||||
import MainNavigator from './views/MainNavigator';
|
||||
|
||||
AppRegistry.registerComponent('ChatExample', () => MainNavigator);
|
||||
AppRegistry.registerComponent('HelloWorld', () => MainNavigator);
|
||||
|
|
|
@ -7,18 +7,18 @@ import {
|
|||
import { TabNavigator } from 'react-navigation';
|
||||
|
||||
import ChatListScreen from './chat/ChatListScreen';
|
||||
import FriendListScreen from './friends/FriendListScreen';
|
||||
import WelcomeScreen from './welcome/WelcomeScreen';
|
||||
|
||||
/**
|
||||
* Screen with tabs shown on app startup.
|
||||
*/
|
||||
const HomeScreenTabNavigator = TabNavigator({
|
||||
Welcome: {
|
||||
screen: WelcomeScreen,
|
||||
},
|
||||
Chats: {
|
||||
screen: ChatListScreen,
|
||||
},
|
||||
Friends: {
|
||||
screen: FriendListScreen,
|
||||
},
|
||||
});
|
||||
|
||||
export default HomeScreenTabNavigator;
|
||||
|
|
|
@ -10,7 +10,7 @@ import ListItem from '../../components/ListItem';
|
|||
export default class ChatListScreen extends Component {
|
||||
|
||||
static navigationOptions = {
|
||||
title: 'Chats',
|
||||
title: 'Friends',
|
||||
header: {
|
||||
visible: Platform.OS === 'ios',
|
||||
},
|
||||
|
|
|
@ -8,11 +8,12 @@ import {
|
|||
} from 'react-native';
|
||||
|
||||
import ListItem from '../../components/ListItem';
|
||||
import WelcomeText from './WelcomeText';
|
||||
|
||||
export default class FriendListScreen extends Component {
|
||||
export default class WelcomeScreen extends Component {
|
||||
|
||||
static navigationOptions = {
|
||||
title: 'Friends',
|
||||
title: 'Welcome',
|
||||
header: {
|
||||
visible: Platform.OS === 'ios',
|
||||
},
|
||||
|
@ -20,7 +21,7 @@ export default class FriendListScreen extends Component {
|
|||
icon: ({ tintColor }) => (
|
||||
<Image
|
||||
// Using react-native-vector-icons works here too
|
||||
source={require('./friend-icon.png')}
|
||||
source={require('./welcome-icon.png')}
|
||||
style={[styles.icon, {tintColor: tintColor}]}
|
||||
/>
|
||||
),
|
||||
|
@ -29,9 +30,7 @@ export default class FriendListScreen extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>A list of friends here.</Text>
|
||||
</View>
|
||||
<WelcomeText />
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
* init - to create a new project and npm install it
|
||||
* --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),
|
||||
* package to install, examples:
|
||||
* - "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:',
|
||||
'',
|
||||
' -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'));
|
||||
process.exit(0);
|
||||
|
@ -264,8 +266,7 @@ function getInstallPackage(rnPackage) {
|
|||
}
|
||||
|
||||
function run(root, projectName, options) {
|
||||
// E.g. '0.38' or '/path/to/archive.tgz'
|
||||
const rnPackage = options.version;
|
||||
const rnPackage = options.version; // e.g. '0.38' or '/path/to/archive.tgz'
|
||||
const forceNpmClient = options.npm;
|
||||
const yarnVersion = (!forceNpmClient) && getYarnVersionIfAvailable();
|
||||
var installCommand;
|
||||
|
|
Loading…
Reference in New Issue