Skip to content

Commit

Permalink
Support Yarn (facebook#898)
Browse files Browse the repository at this point in the history
In the `create-react-app` command, try to install packages using Yarn.
If Yarn is not installed, use npm instead.

In `react-scripts`, detect if the project is using Yarn by checking if
a `yarn.lock` file exists. If the project is using Yarn, display all
the instructions with Yarn commands and use Yarn to install packages
in `init` and `eject` scripts.
  • Loading branch information
fson authored and randycoulman committed May 8, 2017
1 parent 3342f17 commit 8d9e296
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 36 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ cache:
- packages/create-react-app/node_modules
- packages/react-scripts/node_modules
script: tasks/e2e.sh
env:
- USE_YARN=no
- USE_YARN=yes
52 changes: 40 additions & 12 deletions packages/create-react-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,26 +101,54 @@ function createApp(name, verbose, version) {
process.chdir(root);

console.log('Installing packages. This might take a couple minutes.');
console.log('Installing react-scripts from npm...');
console.log('Installing react-scripts...');
console.log();

run(root, appName, version, verbose, originalDirectory);
}

function run(root, appName, version, verbose, originalDirectory) {
var installPackage = getInstallPackage(version);
var packageName = getPackageName(installPackage);
function install(packageToInstall, verbose, callback) {
var args = [
'install',
verbose && '--verbose',
'--save-dev',
'--save-exact',
installPackage,
].filter(function(e) { return e; });
var proc = spawn('npm', args, {stdio: 'inherit'});
'add',
'--dev',
'--exact',
packageToInstall,
];
var proc = spawn('yarn', args, {stdio: 'inherit'});

var yarnExists = true;
proc.on('error', function (err) {
if (err.code === 'ENOENT') {
yarnExists = false;
}
});
proc.on('close', function (code) {
if (yarnExists) {
callback(code, 'yarn', args);
return;
}
// No Yarn installed, continuing with npm.
args = [
'install',
verbose && '--verbose',
'--save-dev',
'--save-exact',
packageToInstall,
].filter(function(e) { return e; });
var npmProc = spawn('npm', args, {stdio: 'inherit'});
npmProc.on('close', function (code) {
callback(code, 'npm', args);
});
});
}

function run(root, appName, version, verbose, originalDirectory) {
var packageToInstall = getInstallPackage(version);
var packageName = getPackageName(packageToInstall);

install(packageToInstall, verbose, function (code, command, args) {
if (code !== 0) {
console.error('`npm ' + args.join(' ') + '` failed');
console.error('`' + command + ' ' + args.join(' ') + '` failed');
return;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ module.exports = {
appIndexJs: resolveApp('client/index.js'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('client'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveApp('client/setupTests.js'),
appNodeModules: resolveApp('node_modules'),
ownNodeModules: resolveApp('node_modules'),
Expand All @@ -65,6 +66,7 @@ module.exports = {
appIndexJs: resolveApp('client/index.js'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('client'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveApp('client/setupTests.js'),
appNodeModules: resolveApp('node_modules'),
// this is empty with npm3 but node resolution searches higher anyway:
Expand All @@ -82,6 +84,7 @@ if (__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1)
appIndexJs: resolveOwn('../template/client/index.js'),
appPackageJson: resolveOwn('../package.json'),
appSrc: resolveOwn('../template/client'),
yarnLockFile: resolveOwn('../template/yarn.lock'),
testsSetup: resolveOwn('../template/client/setupTests.js'),
appNodeModules: resolveOwn('../node_modules'),
ownNodeModules: resolveOwn('../node_modules'),
Expand Down
17 changes: 14 additions & 3 deletions packages/react-scripts/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require('dotenv').config({silent: true});
var chalk = require('chalk');
var fs = require('fs-extra');
var path = require('path');
var pathExists = require('path-exists');
var filesize = require('filesize');
var gzipSize = require('gzip-size').sync;
var rimrafSync = require('rimraf').sync;
Expand All @@ -31,6 +32,8 @@ var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
var recursive = require('recursive-readdir');
var stripAnsi = require('strip-ansi');

var useYarn = pathExists.sync(paths.yarnLockFile);

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
Expand Down Expand Up @@ -161,7 +164,11 @@ function build(previousSizeMap) {
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
console.log('To publish it at ' + chalk.green(homepagePath) + ', run:');
console.log();
console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages');
if (useYarn) {
console.log(' ' + chalk.cyan('yarn') + ' add gh-pages');
} else {
console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages');
}
console.log();
console.log('Add the following script in your ' + chalk.cyan('package.json') + '.');
console.log();
Expand All @@ -173,7 +180,7 @@ function build(previousSizeMap) {
console.log();
console.log('Then run:');
console.log();
console.log(' ' + chalk.cyan('npm') + ' run deploy');
console.log(' ' + chalk.cyan(useYarn ? 'yarn' : 'npm') + ' run deploy');
console.log();
} else if (publicPath !== '/') {
// "homepage": "http://mywebsite.com/project"
Expand All @@ -200,7 +207,11 @@ function build(previousSizeMap) {
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
console.log('You may also serve it locally with a static server:')
console.log();
console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server');
if (useYarn) {
console.log(' ' + chalk.cyan('yarn') + ' global add pushstate-server');
} else {
console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server');
}
console.log(' ' + chalk.cyan('pushstate-server') + ' build');
console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000');
console.log();
Expand Down
14 changes: 11 additions & 3 deletions packages/react-scripts/scripts/eject.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
var createJestConfig = require('../utils/createJestConfig');
var fs = require('fs');
var path = require('path');
var pathExists = require('path-exists');
var paths = require('../config/paths');
var prompt = require('react-dev-utils/prompt');
var rimrafSync = require('rimraf').sync;
var spawnSync = require('cross-spawn').sync;
Expand Down Expand Up @@ -143,9 +145,15 @@ prompt(
);
console.log();

console.log(cyan('Running npm install...'));
rimrafSync(ownPath);
spawnSync('npm', ['install'], {stdio: 'inherit'});
if (pathExists.sync(paths.yarnLockFile)) {
console.log(cyan('Running yarn...'));
rimrafSync(ownPath);
spawnSync('yarn', [], {stdio: 'inherit'});
} else {
console.log(cyan('Running npm install...'));
rimrafSync(ownPath);
spawnSync('npm', ['install'], {stdio: 'inherit'});
}
console.log(green('Ejected successfully!'));
console.log();

Expand Down
45 changes: 28 additions & 17 deletions packages/react-scripts/scripts/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = function(appPath, appName, verbose, originalDirectory) {
var ownPackageName = require(path.join(__dirname, '..', 'package.json')).name;
var ownPath = path.join(appPath, 'node_modules', ownPackageName);
var appPackage = require(path.join(appPath, 'package.json'));
var useYarn = pathExists.sync(path.join(appPath, 'yarn.lock'));

// Copy over some of the devDependencies
appPackage.dependencies = appPackage.dependencies || {};
Expand Down Expand Up @@ -58,21 +59,31 @@ module.exports = function(appPath, appName, verbose, originalDirectory) {
}
});

// Run another npm install for react and react-dom
console.log('Installing react and react-dom from npm...');
// Run yarn or npm for react and react-dom
// TODO: having to do two npm/yarn installs is bad, can we avoid it?
var command;
var args;

if (useYarn) {
command = 'yarn';
args = ['add'];
} else {
command = 'npm';
args = [
'install',
'--save',
verbose && '--verbose'
].filter(function(e) { return e; });
}
args.push('react', 'react-dom');

console.log('Installing react and react-dom using ' + command + '...');
console.log();
// TODO: having to do two npm installs is bad, can we avoid it?
var args = [
'install',
'react',
'react-dom',
'--save',
verbose && '--verbose'
].filter(function(e) { return e; });
var proc = spawn('npm', args, {stdio: 'inherit'});

var proc = spawn(command, args, {stdio: 'inherit'});
proc.on('close', function (code) {
if (code !== 0) {
console.error('`npm ' + args.join(' ') + '` failed');
console.error('`' + command + ' ' + args.join(' ') + '` failed');
return;
}

Expand All @@ -91,23 +102,23 @@ module.exports = function(appPath, appName, verbose, originalDirectory) {
console.log('Success! Created ' + appName + ' at ' + appPath);
console.log('Inside that directory, you can run several commands:');
console.log();
console.log(chalk.cyan(' npm start'));
console.log(chalk.cyan(' ' + command + ' start'));
console.log(' Starts the development server.');
console.log();
console.log(chalk.cyan(' npm run build'));
console.log(chalk.cyan(' ' + command + ' run build'));
console.log(' Bundles the app into static files for production.');
console.log();
console.log(chalk.cyan(' npm test'));
console.log(chalk.cyan(' ' + command + ' test'));
console.log(' Starts the test runner.');
console.log();
console.log(chalk.cyan(' npm run eject'));
console.log(chalk.cyan(' ' + command + ' run eject'));
console.log(' Removes this tool and copies build dependencies, configuration files');
console.log(' and scripts into the app directory. If you do this, you can’t go back!');
console.log();
console.log('We suggest that you begin by typing:');
console.log();
console.log(chalk.cyan(' cd'), cdpath);
console.log(' ' + chalk.cyan('npm start'));
console.log(' ' + chalk.cyan(command + ' start'));
if (readmeExists) {
console.log();
console.log(chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`'));
Expand Down
6 changes: 5 additions & 1 deletion packages/react-scripts/scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
var openBrowser = require('react-dev-utils/openBrowser');
var prompt = require('react-dev-utils/prompt');
var pathExists = require('path-exists');
var config = require('../config/webpack.config.dev');
var paths = require('../config/paths');

var useYarn = pathExists.sync(paths.yarnLockFile);
var cli = useYarn ? 'yarn' : 'npm';

// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
Expand Down Expand Up @@ -87,7 +91,7 @@ function setupCompiler(host, port, protocol) {
console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/'));
console.log();
console.log('Note that the development build is not optimized.');
console.log('To create a production build, use ' + chalk.cyan('npm run build') + '.');
console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.');
console.log();
}

Expand Down
6 changes: 6 additions & 0 deletions tasks/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ set -x
cd ..
root_path=$PWD

if [ "$USE_YARN" = "yes" ]
then
# Install Yarn so that the test can use it to install packages.
npm install -g yarn
fi

npm install

# Lint own code
Expand Down

0 comments on commit 8d9e296

Please sign in to comment.