Permalink
Browse files

Use yarn when available

Summary:
**Motivation**

`react-native init` takes minutes even on a fast network. Yarn makes it much quicker.

When yarn is not installed on the system:

<img width="440" alt="screenshot 2016-10-31 22 21 19" src="https://cloud.githubusercontent.com/assets/346214/19873897/7bad5662-9fb9-11e6-85fb-ad4879949dad.png">

When yarn is installed:

<img width="441" alt="screenshot 2016-10-31 22 02 20" src="https://cloud.githubusercontent.com/assets/346214/19873898/7baf4c56-9fb9-11e6-96b3-007f93d2438a.png">

Also added the option `react-native init AwesomeApp --npm` as a fallback in case yarn is not stable enough for some people (I saw some Github issues that yarn hangs for example; for me it works great though).

**Test plan**
1. Publish to Sinopia: https://github.com/facebook/react-native/tree/master/react-native-cli
2. `react-native init AwesomeApp`

***Tested the following setups***

- New CLI - uses yarn, new react-native - uses yarn
- Old CLI (1.0.0) - doesn't use yarn, new react-native - uses yarn
-
Closes #10626

Differential Revision: D4110883

Pulled By: bestander

fbshipit-source-id: 8a3427e2bc9158cf5fadd8ddf5b31e4b50ce6453
  • Loading branch information...
1 parent 5105c09 commit 94711bfb06e46a7742dbddf5b1b6c48ecf345495 @mkonicek mkonicek committed with Facebook Github Bot Nov 1, 2016
Showing with 123 additions and 32 deletions.
  1. +52 −7 local-cli/generator/index.js
  2. +70 −24 react-native-cli/index.js
  3. +1 −1 react-native-cli/package.json
@@ -8,11 +8,40 @@
*/
'use strict';
+var execSync = require('child_process').execSync;
var fs = require('fs');
var path = require('path');
+var semver = require('semver')
var utils = require('../generator-utils');
var yeoman = require('yeoman-generator');
+// Use Yarn if available, it's much faster than the npm client.
+// Return the version of yarn installed on the system, null if yarn is not available.
+function getYarnVersionIfAvailable() {
+ let yarnVersion;
+ try {
+ // execSync returns a Buffer -> convert to string
+ if (process.platform.startsWith('win')) {
+ yarnVersion = (execSync('yarn --version').toString() || '').trim();
+ } else {
+ yarnVersion = (execSync('yarn --version 2>/dev/null').toString() || '').trim();
+ }
+ } catch (error) {
+ return null;
+ }
+ // yarn < 0.16 has a 'missing manifest' bug
+ try {
+ if (semver.gte(yarnVersion, '0.16.0')) {
+ return yarnVersion;
+ } else {
+ return null;
+ }
+ } catch (error) {
+ console.error('Cannot parse yarn version: ' + yarnVersion);
+ return null;
+ }
+}
+
module.exports = yeoman.generators.NamedBase.extend({
constructor: function() {
yeoman.generators.NamedBase.apply(this, arguments);
@@ -36,6 +65,12 @@ module.exports = yeoman.generators.NamedBase.extend({
type: Boolean,
defaults: false
});
+ // Temporary option until yarn becomes stable.
+ this.option('npm', {
+ desc: 'Use the npm client, even if yarn is available.',
+ type: Boolean,
+ defaults: false
+ });
// this passes command line arguments down to the composed generators
var args = {args: arguments[0], options: this.options};
@@ -112,28 +147,38 @@ module.exports = yeoman.generators.NamedBase.extend({
return;
}
- this.npmInstall(`react@${reactVersion}`, { '--save': true, '--save-exact': true });
+ const yarnVersion = (!this.options['npm']) && getYarnVersionIfAvailable();
+
+ console.log('Installing React...');
+ if (yarnVersion) {
+ execSync(`yarn add react@${reactVersion}`);
+ } else {
+ this.npmInstall(`react@${reactVersion}`, { '--save': true, '--save-exact': true });
+ }
if (!this.options['skip-jest']) {
- this.npmInstall(`jest babel-jest babel-preset-react-native react-test-renderer@${reactVersion}`.split(' '), {
- saveDev: true,
- '--save-exact': true
- });
+ console.log('Installing Jest...');
+ if (yarnVersion) {
+ execSync(`yarn add jest babel-jest jest-react-native babel-preset-react-native react-test-renderer@${reactVersion} --dev --exact`);
@kentaromiura
kentaromiura Nov 1, 2016 Contributor

jest-react-native is not needed anymore, I've removed it here:
6d3e074#diff-0693784fbf2e944793d2c1f187991860L117

+ } else {
+ this.npmInstall(`jest babel-jest babel-preset-react-native react-test-renderer@${reactVersion}`.split(' '), {
+ saveDev: true,
+ '--save-exact': true
+ });
+ }
fs.writeFileSync(
path.join(
this.destinationRoot(),
'.babelrc'
),
'{\n"presets": ["react-native"]\n}'
);
-
this.fs.copy(
this.templatePath('__tests__'),
this.destinationPath('__tests__'),
{
nodir: false
}
);
-
var packageJSONPath = path.join(
this.destinationRoot(),
'package.json'
@@ -38,6 +38,7 @@
var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
+var execSync = require('child_process').execSync;
var spawn = require('child_process').spawn;
var chalk = require('chalk');
var prompt = require('prompt');
@@ -74,6 +75,33 @@ var REACT_NATIVE_PACKAGE_JSON_PATH = function() {
);
};
+// Use Yarn if available, it's much faster than the npm client.
+// Return the version of yarn installed on the system, null if yarn is not available.
+function getYarnVersionIfAvailable() {
+ let yarnVersion;
+ try {
+ // execSync returns a Buffer -> convert to string
+ if (process.platform.startsWith('win')) {
+ yarnVersion = (execSync('yarn --version').toString() || '').trim();
+ } else {
+ yarnVersion = (execSync('yarn --version 2>/dev/null').toString() || '').trim();
+ }
+ } catch (error) {
+ return null;
+ }
+ // yarn < 0.16 has a 'missing manifest' bug
+ try {
+ if (semver.gte(yarnVersion, '0.16.0')) {
+ return yarnVersion;
+ } else {
+ return null;
+ }
+ } catch (error) {
+ console.error('Cannot parse yarn version: ' + yarnVersion);
+ return null;
+ }
+}
+
checkForVersionArgument();
var cli;
@@ -102,8 +130,8 @@ if (cli) {
);
process.exit(1);
} else {
- if (!argv.verbose) console.log('This may take some time...');
- init(commands[1], argv.verbose, argv.version);
+ const rnPackage = argv.version;
+ init(commands[1], argv.verbose, rnPackage, argv.npm);
}
break;
default:
@@ -117,7 +145,7 @@ if (cli) {
}
}
-function validatePackageName(name) {
+function validateProjectName(name) {
if (!name.match(/^[$A-Z_][0-9A-Z_$]*$/i)) {
console.error(
'"%s" is not a valid name for a project. Please use a valid identifier ' +
@@ -137,17 +165,24 @@ function validatePackageName(name) {
}
}
-function init(name, verbose, rnPackage) {
- validatePackageName(name);
+/**
+ * @param name Project name, e.g. 'AwesomeApp'.
+ * @param verbose If true, will run 'npm install' in verbose mode (for debugging).
+ * @param rnPackage Version of React Native to install, e.g. '0.38.0'.
+ * @param forceNpmClient If true, always use the npm command line client,
+ * don't use yarn even if available.
+ */
+function init(name, verbose, rnPackage, forceNpmClient) {
+ validateProjectName(name);
if (fs.existsSync(name)) {
- createAfterConfirmation(name, verbose, rnPackage);
+ createAfterConfirmation(name, verbose, rnPackage, forceNpmClient);
} else {
- createProject(name, verbose, rnPackage);
+ createProject(name, verbose, rnPackage, forceNpmClient);
}
}
-function createAfterConfirmation(name, verbose, rnPackage) {
+function createAfterConfirmation(name, verbose, rnPackage, forceNpmClient) {
prompt.start();
var property = {
@@ -160,15 +195,15 @@ function createAfterConfirmation(name, verbose, rnPackage) {
prompt.get(property, function (err, result) {
if (result.yesno[0] === 'y') {
- createProject(name, verbose, rnPackage);
+ createProject(name, verbose, rnPackage, forceNpmClient);
} else {
console.log('Project initialization canceled');
process.exit();
}
});
}
-function createProject(name, verbose, rnPackage) {
+function createProject(name, verbose, rnPackage, forceNpmClient) {
var root = path.resolve(name);
var projectName = path.basename(root);
@@ -192,44 +227,55 @@ function createProject(name, verbose, rnPackage) {
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson));
process.chdir(root);
- console.log('Installing react-native package from npm...');
-
if (verbose) {
- runVerbose(root, projectName, rnPackage);
+ runVerbose(root, projectName, rnPackage, forceNpmClient);
} else {
- run(root, projectName, rnPackage);
+ run(root, projectName, rnPackage, forceNpmClient);
}
}
function getInstallPackage(rnPackage) {
var packageToInstall = 'react-native';
- var valideSemver = semver.valid(rnPackage);
- if (valideSemver) {
- packageToInstall += '@' + valideSemver;
+ var isValidSemver = semver.valid(rnPackage);
+ if (isValidSemver) {
+ packageToInstall += '@' + isValidSemver;
} else if (rnPackage) {
// for tar.gz or alternative paths
packageToInstall = rnPackage;
}
return packageToInstall;
}
-function run(root, projectName, rnPackage) {
- exec('npm install --save --save-exact ' + getInstallPackage(rnPackage), function(e, stdout, stderr) {
- if (e) {
+function run(root, projectName, rnPackage, forceNpmClient) {
+ const yarnVersion = (!forceNpmClient) && getYarnVersionIfAvailable();
+ let installCommand;
+ if (yarnVersion) {
+ console.log('Using yarn v' + yarnVersion);
+ console.log('Installing ' + getInstallPackage(rnPackage) + '...');
+ installCommand = 'yarn add ' + getInstallPackage(rnPackage) + ' --exact';
+ } else {
+ console.log('Installing ' + getInstallPackage(rnPackage) + ' from npm...');
+ if (!forceNpmClient) {
+ console.log('Consider installing yarn to make this faster: https://yarnpkg.com');
+ }
+ installCommand = 'npm install --save --save-exact ' + getInstallPackage(rnPackage);
+ }
+ exec(installCommand, function(err, stdout, stderr) {
+ if (err) {
console.log(stdout);
console.error(stderr);
- console.error('`npm install --save --save-exact react-native` failed');
+ console.error('Command `' + installCommand + '` failed.');
process.exit(1);
}
-
checkNodeVersion();
-
cli = require(CLI_MODULE_PATH());
cli.init(root, projectName);
});
}
-function runVerbose(root, projectName, rnPackage) {
+function runVerbose(root, projectName, rnPackage, forceNpmClient) {
+ // Use npm client, yarn doesn't support --verbose yet
+ console.log('Installing ' + getInstallPackage(rnPackage) + ' from npm. This might take a while...');
var proc = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['install', '--verbose', '--save', '--save-exact', getInstallPackage(rnPackage)], {stdio: 'inherit'});
proc.on('close', function (code) {
if (code !== 0) {
@@ -1,6 +1,6 @@
{
"name": "react-native-cli",
- "version": "1.0.0",
+ "version": "1.1.0",
"license": "BSD-3-Clause",
"description": "The React Native CLI tools",
"main": "index.js",

0 comments on commit 94711bf

Please sign in to comment.