Skip to content

Commit bcc469c

Browse files
authored
Support Yarn (#898)
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.
1 parent b9c9aed commit bcc469c

File tree

8 files changed

+110
-36
lines changed

8 files changed

+110
-36
lines changed

.travis.yml

+3
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ cache:
99
- packages/create-react-app/node_modules
1010
- packages/react-scripts/node_modules
1111
script: tasks/e2e.sh
12+
env:
13+
- USE_YARN=no
14+
- USE_YARN=yes

packages/create-react-app/index.js

+40-12
Original file line numberDiff line numberDiff line change
@@ -101,26 +101,54 @@ function createApp(name, verbose, version) {
101101
process.chdir(root);
102102

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

107107
run(root, appName, version, verbose, originalDirectory);
108108
}
109109

110-
function run(root, appName, version, verbose, originalDirectory) {
111-
var installPackage = getInstallPackage(version);
112-
var packageName = getPackageName(installPackage);
110+
function install(packageToInstall, verbose, callback) {
113111
var args = [
114-
'install',
115-
verbose && '--verbose',
116-
'--save-dev',
117-
'--save-exact',
118-
installPackage,
119-
].filter(function(e) { return e; });
120-
var proc = spawn('npm', args, {stdio: 'inherit'});
112+
'add',
113+
'--dev',
114+
'--exact',
115+
packageToInstall,
116+
];
117+
var proc = spawn('yarn', args, {stdio: 'inherit'});
118+
119+
var yarnExists = true;
120+
proc.on('error', function (err) {
121+
if (err.code === 'ENOENT') {
122+
yarnExists = false;
123+
}
124+
});
121125
proc.on('close', function (code) {
126+
if (yarnExists) {
127+
callback(code, 'yarn', args);
128+
return;
129+
}
130+
// No Yarn installed, continuing with npm.
131+
args = [
132+
'install',
133+
verbose && '--verbose',
134+
'--save-dev',
135+
'--save-exact',
136+
packageToInstall,
137+
].filter(function(e) { return e; });
138+
var npmProc = spawn('npm', args, {stdio: 'inherit'});
139+
npmProc.on('close', function (code) {
140+
callback(code, 'npm', args);
141+
});
142+
});
143+
}
144+
145+
function run(root, appName, version, verbose, originalDirectory) {
146+
var packageToInstall = getInstallPackage(version);
147+
var packageName = getPackageName(packageToInstall);
148+
149+
install(packageToInstall, verbose, function (code, command, args) {
122150
if (code !== 0) {
123-
console.error('`npm ' + args.join(' ') + '` failed');
151+
console.error('`' + command + ' ' + args.join(' ') + '` failed');
124152
return;
125153
}
126154

packages/react-scripts/config/paths.js

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ module.exports = {
4343
appIndexJs: resolveApp('src/index.js'),
4444
appPackageJson: resolveApp('package.json'),
4545
appSrc: resolveApp('src'),
46+
yarnLockFile: resolveApp('yarn.lock'),
4647
testsSetup: resolveApp('src/setupTests.js'),
4748
appNodeModules: resolveApp('node_modules'),
4849
ownNodeModules: resolveApp('node_modules'),
@@ -62,6 +63,7 @@ module.exports = {
6263
appIndexJs: resolveApp('src/index.js'),
6364
appPackageJson: resolveApp('package.json'),
6465
appSrc: resolveApp('src'),
66+
yarnLockFile: resolveApp('yarn.lock'),
6567
testsSetup: resolveApp('src/setupTests.js'),
6668
appNodeModules: resolveApp('node_modules'),
6769
// this is empty with npm3 but node resolution searches higher anyway:
@@ -79,6 +81,7 @@ if (__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1)
7981
appIndexJs: resolveOwn('../template/src/index.js'),
8082
appPackageJson: resolveOwn('../package.json'),
8183
appSrc: resolveOwn('../template/src'),
84+
yarnLockFile: resolveOwn('../template/yarn.lock'),
8285
testsSetup: resolveOwn('../template/src/setupTests.js'),
8386
appNodeModules: resolveOwn('../node_modules'),
8487
ownNodeModules: resolveOwn('../node_modules'),

packages/react-scripts/scripts/build.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require('dotenv').config({silent: true});
2121
var chalk = require('chalk');
2222
var fs = require('fs-extra');
2323
var path = require('path');
24+
var pathExists = require('path-exists');
2425
var filesize = require('filesize');
2526
var gzipSize = require('gzip-size').sync;
2627
var rimrafSync = require('rimraf').sync;
@@ -31,6 +32,8 @@ var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
3132
var recursive = require('recursive-readdir');
3233
var stripAnsi = require('strip-ansi');
3334

35+
var useYarn = pathExists.sync(paths.yarnLockFile);
36+
3437
// Warn and crash if required files are missing
3538
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
3639
process.exit(1);
@@ -161,7 +164,11 @@ function build(previousSizeMap) {
161164
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
162165
console.log('To publish it at ' + chalk.green(homepagePath) + ', run:');
163166
console.log();
164-
console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages');
167+
if (useYarn) {
168+
console.log(' ' + chalk.cyan('yarn') + ' add gh-pages');
169+
} else {
170+
console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages');
171+
}
165172
console.log();
166173
console.log('Add the following script in your ' + chalk.cyan('package.json') + '.');
167174
console.log();
@@ -173,7 +180,7 @@ function build(previousSizeMap) {
173180
console.log();
174181
console.log('Then run:');
175182
console.log();
176-
console.log(' ' + chalk.cyan('npm') + ' run deploy');
183+
console.log(' ' + chalk.cyan(useYarn ? 'yarn' : 'npm') + ' run deploy');
177184
console.log();
178185
} else if (publicPath !== '/') {
179186
// "homepage": "http://mywebsite.com/project"
@@ -200,7 +207,11 @@ function build(previousSizeMap) {
200207
console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
201208
console.log('You may also serve it locally with a static server:')
202209
console.log();
203-
console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server');
210+
if (useYarn) {
211+
console.log(' ' + chalk.cyan('yarn') + ' global add pushstate-server');
212+
} else {
213+
console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server');
214+
}
204215
console.log(' ' + chalk.cyan('pushstate-server') + ' build');
205216
console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000');
206217
console.log();

packages/react-scripts/scripts/eject.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
var createJestConfig = require('../utils/createJestConfig');
1111
var fs = require('fs');
1212
var path = require('path');
13+
var pathExists = require('path-exists');
14+
var paths = require('../config/paths');
1315
var prompt = require('react-dev-utils/prompt');
1416
var rimrafSync = require('rimraf').sync;
1517
var spawnSync = require('cross-spawn').sync;
@@ -143,9 +145,15 @@ prompt(
143145
);
144146
console.log();
145147

146-
console.log(cyan('Running npm install...'));
147-
rimrafSync(ownPath);
148-
spawnSync('npm', ['install'], {stdio: 'inherit'});
148+
if (pathExists.sync(paths.yarnLockFile)) {
149+
console.log(cyan('Running yarn...'));
150+
rimrafSync(ownPath);
151+
spawnSync('yarn', [], {stdio: 'inherit'});
152+
} else {
153+
console.log(cyan('Running npm install...'));
154+
rimrafSync(ownPath);
155+
spawnSync('npm', ['install'], {stdio: 'inherit'});
156+
}
149157
console.log(green('Ejected successfully!'));
150158
console.log();
151159

packages/react-scripts/scripts/init.js

+28-17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = function(appPath, appName, verbose, originalDirectory) {
1717
var ownPackageName = require(path.join(__dirname, '..', 'package.json')).name;
1818
var ownPath = path.join(appPath, 'node_modules', ownPackageName);
1919
var appPackage = require(path.join(appPath, 'package.json'));
20+
var useYarn = pathExists.sync(path.join(appPath, 'yarn.lock'));
2021

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

61-
// Run another npm install for react and react-dom
62-
console.log('Installing react and react-dom from npm...');
62+
// Run yarn or npm for react and react-dom
63+
// TODO: having to do two npm/yarn installs is bad, can we avoid it?
64+
var command;
65+
var args;
66+
67+
if (useYarn) {
68+
command = 'yarn';
69+
args = ['add'];
70+
} else {
71+
command = 'npm';
72+
args = [
73+
'install',
74+
'--save',
75+
verbose && '--verbose'
76+
].filter(function(e) { return e; });
77+
}
78+
args.push('react', 'react-dom');
79+
80+
console.log('Installing react and react-dom using ' + command + '...');
6381
console.log();
64-
// TODO: having to do two npm installs is bad, can we avoid it?
65-
var args = [
66-
'install',
67-
'react',
68-
'react-dom',
69-
'--save',
70-
verbose && '--verbose'
71-
].filter(function(e) { return e; });
72-
var proc = spawn('npm', args, {stdio: 'inherit'});
82+
83+
var proc = spawn(command, args, {stdio: 'inherit'});
7384
proc.on('close', function (code) {
7485
if (code !== 0) {
75-
console.error('`npm ' + args.join(' ') + '` failed');
86+
console.error('`' + command + ' ' + args.join(' ') + '` failed');
7687
return;
7788
}
7889

@@ -91,23 +102,23 @@ module.exports = function(appPath, appName, verbose, originalDirectory) {
91102
console.log('Success! Created ' + appName + ' at ' + appPath);
92103
console.log('Inside that directory, you can run several commands:');
93104
console.log();
94-
console.log(chalk.cyan(' npm start'));
105+
console.log(chalk.cyan(' ' + command + ' start'));
95106
console.log(' Starts the development server.');
96107
console.log();
97-
console.log(chalk.cyan(' npm run build'));
108+
console.log(chalk.cyan(' ' + command + ' run build'));
98109
console.log(' Bundles the app into static files for production.');
99110
console.log();
100-
console.log(chalk.cyan(' npm test'));
111+
console.log(chalk.cyan(' ' + command + ' test'));
101112
console.log(' Starts the test runner.');
102113
console.log();
103-
console.log(chalk.cyan(' npm run eject'));
114+
console.log(chalk.cyan(' ' + command + ' run eject'));
104115
console.log(' Removes this tool and copies build dependencies, configuration files');
105116
console.log(' and scripts into the app directory. If you do this, you can’t go back!');
106117
console.log();
107118
console.log('We suggest that you begin by typing:');
108119
console.log();
109120
console.log(chalk.cyan(' cd'), cdpath);
110-
console.log(' ' + chalk.cyan('npm start'));
121+
console.log(' ' + chalk.cyan(command + ' start'));
111122
if (readmeExists) {
112123
console.log();
113124
console.log(chalk.yellow('You had a `README.md` file, we renamed it to `README.old.md`'));

packages/react-scripts/scripts/start.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
2828
var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
2929
var openBrowser = require('react-dev-utils/openBrowser');
3030
var prompt = require('react-dev-utils/prompt');
31+
var pathExists = require('path-exists');
3132
var config = require('../config/webpack.config.dev');
3233
var paths = require('../config/paths');
3334

35+
var useYarn = pathExists.sync(paths.yarnLockFile);
36+
var cli = useYarn ? 'yarn' : 'npm';
37+
3438
// Warn and crash if required files are missing
3539
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
3640
process.exit(1);
@@ -85,7 +89,7 @@ function setupCompiler(host, port, protocol) {
8589
console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/'));
8690
console.log();
8791
console.log('Note that the development build is not optimized.');
88-
console.log('To create a production build, use ' + chalk.cyan('npm run build') + '.');
92+
console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.');
8993
console.log();
9094
}
9195

tasks/e2e.sh

+6
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ set -x
5353
cd ..
5454
root_path=$PWD
5555

56+
if [ "$USE_YARN" = "yes" ]
57+
then
58+
# Install Yarn so that the test can use it to install packages.
59+
npm install -g yarn
60+
fi
61+
5662
npm install
5763

5864
# Lint own code

0 commit comments

Comments
 (0)