From 23fde9487880758244d3a522595dd852b1750583 Mon Sep 17 00:00:00 2001 From: Ville Immonen Date: Thu, 17 Nov 2016 22:55:00 +0200 Subject: [PATCH] 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. --- .travis.yml | 3 ++ packages/create-react-app/index.js | 52 +++++++++++++++++++------ packages/react-scripts/config/paths.js | 3 ++ packages/react-scripts/scripts/build.js | 17 ++++++-- packages/react-scripts/scripts/eject.js | 14 +++++-- packages/react-scripts/scripts/init.js | 45 +++++++++++++-------- packages/react-scripts/scripts/start.js | 6 ++- tasks/e2e.sh | 6 +++ 8 files changed, 110 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 75383b8780..ec44c3294d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/packages/create-react-app/index.js b/packages/create-react-app/index.js index d6478a1354..23d8b5e8de 100644 --- a/packages/create-react-app/index.js +++ b/packages/create-react-app/index.js @@ -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; } diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 1c154c3616..89cd2059cd 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -43,6 +43,7 @@ module.exports = { appIndexJs: resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), + yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), ownNodeModules: resolveApp('node_modules'), @@ -62,6 +63,7 @@ module.exports = { appIndexJs: resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), + yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), // 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) appIndexJs: resolveOwn('../template/src/index.js'), appPackageJson: resolveOwn('../package.json'), appSrc: resolveOwn('../template/src'), + yarnLockFile: resolveOwn('../template/yarn.lock'), testsSetup: resolveOwn('../template/src/setupTests.js'), appNodeModules: resolveOwn('../node_modules'), ownNodeModules: resolveOwn('../node_modules'), diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index d0b92f6a73..8b1cd4cc48 100644 --- a/packages/react-scripts/scripts/build.js +++ b/packages/react-scripts/scripts/build.js @@ -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; @@ -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); @@ -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(); @@ -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" @@ -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(); diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index dbd4d64e4d..7d4996665e 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -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; @@ -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(); diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index fa42f6dcee..c9a4ea14ac 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -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 || {}; @@ -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; } @@ -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`')); diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 8a115dd8e2..5e996c71dd 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -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); @@ -85,7 +89,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(); } diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 88e1fdf4e2..094fba9e22 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -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