diff --git a/packages/create-react-app-template-typescript/package.json b/packages/create-react-app-template-typescript/package.json new file mode 100644 index 00000000000..831af5533da --- /dev/null +++ b/packages/create-react-app-template-typescript/package.json @@ -0,0 +1,27 @@ +{ + "name": "create-react-app-template-typescript", + "version": "1.0.0", + "description": "Base TypeScript template for Create React App.", + "repository": "facebook/create-react-app", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "bugs": { + "url": "https://github.com/facebook/create-react-app/issues" + }, + "files": [ + "public", + "src", + "gitignore", + "README.md", + "utils" + ], + "peerDependencies": { + "@types/node": "^10.12.24", + "@types/react": "^16.8.6", + "@types/react-dom": "^16.8.2", + "@types/jest": "^24.0.9", + "typescript": "^3.3.3" + } +} diff --git a/packages/react-scripts/template-typescript/README.md b/packages/create-react-app-template-typescript/template/README.md similarity index 100% rename from packages/react-scripts/template-typescript/README.md rename to packages/create-react-app-template-typescript/template/README.md diff --git a/packages/react-scripts/template-typescript/gitignore b/packages/create-react-app-template-typescript/template/gitignore similarity index 100% rename from packages/react-scripts/template-typescript/gitignore rename to packages/create-react-app-template-typescript/template/gitignore diff --git a/packages/react-scripts/template-typescript/public/favicon.ico b/packages/create-react-app-template-typescript/template/public/favicon.ico similarity index 100% rename from packages/react-scripts/template-typescript/public/favicon.ico rename to packages/create-react-app-template-typescript/template/public/favicon.ico diff --git a/packages/react-scripts/template-typescript/public/index.html b/packages/create-react-app-template-typescript/template/public/index.html similarity index 100% rename from packages/react-scripts/template-typescript/public/index.html rename to packages/create-react-app-template-typescript/template/public/index.html diff --git a/packages/react-scripts/template-typescript/public/manifest.json b/packages/create-react-app-template-typescript/template/public/manifest.json similarity index 100% rename from packages/react-scripts/template-typescript/public/manifest.json rename to packages/create-react-app-template-typescript/template/public/manifest.json diff --git a/packages/react-scripts/template-typescript/src/App.css b/packages/create-react-app-template-typescript/template/src/App.css similarity index 100% rename from packages/react-scripts/template-typescript/src/App.css rename to packages/create-react-app-template-typescript/template/src/App.css diff --git a/packages/react-scripts/template-typescript/src/App.test.tsx b/packages/create-react-app-template-typescript/template/src/App.test.tsx similarity index 100% rename from packages/react-scripts/template-typescript/src/App.test.tsx rename to packages/create-react-app-template-typescript/template/src/App.test.tsx diff --git a/packages/react-scripts/template-typescript/src/App.tsx b/packages/create-react-app-template-typescript/template/src/App.tsx similarity index 100% rename from packages/react-scripts/template-typescript/src/App.tsx rename to packages/create-react-app-template-typescript/template/src/App.tsx diff --git a/packages/react-scripts/template-typescript/src/index.css b/packages/create-react-app-template-typescript/template/src/index.css similarity index 100% rename from packages/react-scripts/template-typescript/src/index.css rename to packages/create-react-app-template-typescript/template/src/index.css diff --git a/packages/react-scripts/template-typescript/src/index.tsx b/packages/create-react-app-template-typescript/template/src/index.tsx similarity index 100% rename from packages/react-scripts/template-typescript/src/index.tsx rename to packages/create-react-app-template-typescript/template/src/index.tsx diff --git a/packages/react-scripts/template-typescript/src/logo.svg b/packages/create-react-app-template-typescript/template/src/logo.svg similarity index 100% rename from packages/react-scripts/template-typescript/src/logo.svg rename to packages/create-react-app-template-typescript/template/src/logo.svg diff --git a/packages/react-scripts/template-typescript/src/serviceWorker.ts b/packages/create-react-app-template-typescript/template/src/serviceWorker.ts similarity index 100% rename from packages/react-scripts/template-typescript/src/serviceWorker.ts rename to packages/create-react-app-template-typescript/template/src/serviceWorker.ts diff --git a/packages/create-react-app-template/package.json b/packages/create-react-app-template/package.json new file mode 100644 index 00000000000..d72a4c5ff63 --- /dev/null +++ b/packages/create-react-app-template/package.json @@ -0,0 +1,22 @@ +{ + "name": "create-react-app-template", + "version": "1.0.0", + "description": "Base template for Create React App.", + "repository": "facebook/create-react-app", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "bugs": { + "url": "https://github.com/facebook/create-react-app/issues" + }, + "files": [ + "public", + "src", + "gitignore", + "README.md", + "utils" + ], + "peerDependencies": { + } +} diff --git a/packages/react-scripts/template/README.md b/packages/create-react-app-template/template/README.md similarity index 100% rename from packages/react-scripts/template/README.md rename to packages/create-react-app-template/template/README.md diff --git a/packages/react-scripts/template/gitignore b/packages/create-react-app-template/template/gitignore similarity index 100% rename from packages/react-scripts/template/gitignore rename to packages/create-react-app-template/template/gitignore diff --git a/packages/react-scripts/template/public/favicon.ico b/packages/create-react-app-template/template/public/favicon.ico similarity index 100% rename from packages/react-scripts/template/public/favicon.ico rename to packages/create-react-app-template/template/public/favicon.ico diff --git a/packages/react-scripts/template/public/index.html b/packages/create-react-app-template/template/public/index.html similarity index 100% rename from packages/react-scripts/template/public/index.html rename to packages/create-react-app-template/template/public/index.html diff --git a/packages/react-scripts/template/public/manifest.json b/packages/create-react-app-template/template/public/manifest.json similarity index 100% rename from packages/react-scripts/template/public/manifest.json rename to packages/create-react-app-template/template/public/manifest.json diff --git a/packages/react-scripts/template/src/App.css b/packages/create-react-app-template/template/src/App.css similarity index 100% rename from packages/react-scripts/template/src/App.css rename to packages/create-react-app-template/template/src/App.css diff --git a/packages/react-scripts/template/src/App.js b/packages/create-react-app-template/template/src/App.js similarity index 100% rename from packages/react-scripts/template/src/App.js rename to packages/create-react-app-template/template/src/App.js diff --git a/packages/react-scripts/template/src/App.test.js b/packages/create-react-app-template/template/src/App.test.js similarity index 100% rename from packages/react-scripts/template/src/App.test.js rename to packages/create-react-app-template/template/src/App.test.js diff --git a/packages/create-react-app-template/template/src/index.css b/packages/create-react-app-template/template/src/index.css new file mode 100644 index 00000000000..e2bd8f36a01 --- /dev/null +++ b/packages/create-react-app-template/template/src/index.css @@ -0,0 +1,14 @@ +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/packages/react-scripts/template/src/index.js b/packages/create-react-app-template/template/src/index.js similarity index 100% rename from packages/react-scripts/template/src/index.js rename to packages/create-react-app-template/template/src/index.js diff --git a/packages/react-scripts/template/src/logo.svg b/packages/create-react-app-template/template/src/logo.svg similarity index 100% rename from packages/react-scripts/template/src/logo.svg rename to packages/create-react-app-template/template/src/logo.svg diff --git a/packages/react-scripts/template/src/serviceWorker.js b/packages/create-react-app-template/template/src/serviceWorker.js similarity index 100% rename from packages/react-scripts/template/src/serviceWorker.js rename to packages/create-react-app-template/template/src/serviceWorker.js diff --git a/packages/create-react-app/createReactApp.js b/packages/create-react-app/createReactApp.js index 9ea51a87f5c..8d094e3b398 100755 --- a/packages/create-react-app/createReactApp.js +++ b/packages/create-react-app/createReactApp.js @@ -71,13 +71,17 @@ const program = new commander.Command(packageJson.name) }) .option('--verbose', 'print additional logs') .option('--info', 'print environment debug info') + .option( + '--template ', + 'specify the initial template for your project' + ) .option( '--scripts-version ', 'use a non-standard version of react-scripts' ) .option('--use-npm') .option('--use-pnp') - .option('--typescript') + .option('--typescript') // Deprecated in favor of `--template` .allowUnknownOption() .on('--help', () => { console.log(` Only ${chalk.green('')} is required.`); @@ -165,22 +169,14 @@ function printValidationResults(results) { } } -const hiddenProgram = new commander.Command() - .option( - '--internal-testing-template ', - '(internal usage only, DO NOT RELY ON THIS) ' + - 'use a non-standard application template' - ) - .parse(process.argv); - createApp( projectName, program.verbose, program.scriptsVersion, program.useNpm, program.usePnp, - program.typescript, - hiddenProgram.internalTestingTemplate + program.template, + program.typescript ); function createApp( @@ -189,8 +185,8 @@ function createApp( version, useNpm, usePnp, - useTypescript, - template + templateName, + useTypeScript ) { const root = path.resolve(name); const appName = path.basename(root); @@ -284,16 +280,32 @@ function createApp( } } + if (useTypeScript) { + console.log( + chalk.yellow( + 'The --typescript option has been deprecated and will be removed in a future release.' + ) + ); + console.log( + chalk.yellow( + `In future, please use ${chalk.cyan('--template typescript')}.` + ) + ); + console.log(); + if (!templateName) { + templateName = 'typescript'; + } + } + run( root, appName, version, verbose, originalDirectory, - template, + templateName, useYarn, - usePnp, - useTypescript + usePnp ); } @@ -306,7 +318,7 @@ function shouldUseYarn() { } } -function install(root, useYarn, usePnp, dependencies, verbose, isOnline) { +function install(root, useYarn, usePnp, packages, verbose, isOnline) { return new Promise((resolve, reject) => { let command; let args; @@ -319,7 +331,7 @@ function install(root, useYarn, usePnp, dependencies, verbose, isOnline) { if (usePnp) { args.push('--enable-pnp'); } - [].push.apply(args, dependencies); + [].push.apply(args, packages); // Explicitly set cwd() to work around issues like // https://github.com/facebook/create-react-app/issues/3326. @@ -342,7 +354,7 @@ function install(root, useYarn, usePnp, dependencies, verbose, isOnline) { '--save-exact', '--loglevel', 'error', - ].concat(dependencies); + ].concat(packages); if (usePnp) { console.log(chalk.yellow("NPM doesn't support PnP.")); @@ -376,37 +388,35 @@ function run( originalDirectory, template, useYarn, - usePnp, - useTypescript + usePnp ) { - const packageToInstall = getInstallPackage(version, originalDirectory); - const allDependencies = ['react', 'react-dom', packageToInstall]; - if (useTypescript) { - // TODO: get user's node version instead of installing latest - allDependencies.push( - '@types/node', - '@types/react', - '@types/react-dom', - '@types/jest', - 'typescript' - ); - } + const scriptsToInstall = getInstallPackage(version, originalDirectory); + const templateToInstall = getTemplateInstallPackage( + template, + originalDirectory + ); + const packagesToInstall = [ + 'react', + 'react-dom', + templateToInstall, + scriptsToInstall, + ]; console.log('Installing packages. This might take a couple of minutes.'); - getPackageName(packageToInstall) - .then(packageName => + Promise.all(packagesToInstall.map(getPackageName)) + .then(packageNames => checkIfOnline(useYarn).then(isOnline => ({ isOnline: isOnline, - packageName: packageName, + template: packageNames[2], + scripts: packageNames[3], })) ) .then(info => { const isOnline = info.isOnline; - const packageName = info.packageName; + const template = info.template; + const scripts = info.scripts; console.log( - `Installing ${chalk.cyan('react')}, ${chalk.cyan( - 'react-dom' - )}, and ${chalk.cyan(packageName)}...` + `Installing ${chalk.cyan(template)} with ${chalk.cyan(scripts)}...` ); console.log(); @@ -414,14 +424,17 @@ function run( root, useYarn, usePnp, - allDependencies, + packagesToInstall, verbose, isOnline - ).then(() => packageName); + ).then(() => ({ + template, + scripts, + })); }) - .then(async packageName => { - checkNodeVersion(packageName); - setCaretRangeForRuntimeDeps(packageName); + .then(async packageNames => { + checkNodeVersion(packageNames.scripts); + setCaretRangeForRuntimeDeps(packageNames.scripts); const pnpPath = path.resolve(process.cwd(), '.pnp.js'); @@ -432,9 +445,9 @@ function run( cwd: process.cwd(), args: nodeArgs, }, - [root, appName, verbose, originalDirectory, template], + [root, appName, verbose, originalDirectory], ` - var init = require('${packageName}/scripts/init.js'); + var init = require('${packageNames.scripts}/scripts/init.js'); init.apply(null, JSON.parse(process.argv[1])); ` ); @@ -487,6 +500,29 @@ function run( }); } +function getTemplateInstallPackage(templateName, originalDirectory) { + let templateToInstall = 'create-react-app-template'; + if (templateName) { + if (templateName.startsWith('file:')) { + // Handle local templates. + templateToInstall = `file:${path.resolve( + originalDirectory, + templateName.match(/^file:(.*)?$/)[1] + )}`; + } else if ( + templateName.includes('://') || + templateName.match(/^.+\.(tgz|tar\.gz)$/) + ) { + // Handle remote and packaged templates. + templateToInstall = templateName; + } else if (!templateName.startsWith(templateToInstall)) { + // Add prefix `create-react-app-template` to non-prefixed templateNames. + templateToInstall += `-${templateName}`; + } + } + return templateToInstall; +} + function getInstallPackage(version, originalDirectory) { let packageToInstall = 'react-scripts'; const validSemver = semver.valid(version); diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index cbee8cec84e..b87decab989 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -15,8 +15,6 @@ "config", "lib", "scripts", - "template", - "template-typescript", "utils" ], "bin": { diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index 9b473ab3d95..bcfe142bcc1 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -75,23 +75,18 @@ function tryGitInit(appPath) { } } -module.exports = function( - appPath, - appName, - verbose, - originalDirectory, - template -) { - const ownPath = path.dirname( - require.resolve(path.join(__dirname, '..', 'package.json')) - ); +module.exports = function(appPath, appName, verbose, originalDirectory) { const appPackage = require(path.join(appPath, 'package.json')); const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock')); - // Copy over some of the devDependencies - appPackage.dependencies = appPackage.dependencies || {}; + const templateName = Object.keys(appPackage.dependencies).find(name => + name.replace(/^@.+\//, '').startsWith('create-react-app-template') + ); + const templatePath = path.join(appPath, 'node_modules', templateName); + const templatePackage = require(path.join(templatePath, 'package.json')); - const useTypeScript = appPackage.dependencies['typescript'] != null; + // Copy over some of the dependencies + appPackage.dependencies = appPackage.dependencies || {}; // Setup the script rules appPackage.scripts = { @@ -122,15 +117,13 @@ module.exports = function( ); } - // Copy the files for the user - const templatePath = template - ? path.resolve(originalDirectory, template) - : path.join(ownPath, useTypeScript ? 'template-typescript' : 'template'); - if (fs.existsSync(templatePath)) { - fs.copySync(templatePath, appPath); + // Copy template files + const templateDir = path.join(templatePath, 'template'); + if (fs.existsSync(templateDir)) { + fs.copySync(templateDir, appPath); } else { console.error( - `Could not locate supplied template: ${chalk.green(templatePath)}` + `Could not locate supplied template: ${chalk.green(templateDir)}` ); return; } @@ -156,6 +149,7 @@ module.exports = function( let command; let args; + let proc; if (useYarn) { command = 'yarnpkg'; @@ -164,38 +158,65 @@ module.exports = function( command = 'npm'; args = ['install', '--save', verbose && '--verbose'].filter(e => e); } - args.push('react', 'react-dom'); - // Install additional template dependencies, if present - const templateDependenciesPath = path.join( - appPath, - '.template.dependencies.json' - ); - if (fs.existsSync(templateDependenciesPath)) { - const templateDependencies = require(templateDependenciesPath).dependencies; - args = args.concat( - Object.keys(templateDependencies).map(key => { - return `${key}@${templateDependencies[key]}`; - }) - ); - fs.unlinkSync(templateDependenciesPath); - } + const templateDependencies = templatePackage.peerDependencies; + const packagesToInstall = Object.keys(templateDependencies) + .filter(key => !key.includes('react-scripts')) + .map(key => `${key}@${templateDependencies[key]}`); + args = args.concat(packagesToInstall); - // Install react and react-dom for backward compatibility with old CRA cli - // which doesn't install react and react-dom along with react-scripts - // or template is presetend (via --internal-testing-template) - if (!isReactInstalled(appPackage) || template) { - console.log(`Installing react and react-dom using ${command}...`); - console.log(); + console.log(); + console.log(`Installing template dependencies using ${command}...`); + console.log(); - const proc = spawn.sync(command, args, { stdio: 'inherit' }); + if (packagesToInstall) { + // Install template dependencies + proc = spawn.sync(command, args, { stdio: 'inherit' }); if (proc.status !== 0) { console.error(`\`${command} ${args.join(' ')}\` failed`); return; } } - if (useTypeScript) { + const installedScriptsName = Object.keys(appPackage.dependencies).find(key => + key.includes('react-scripts') + ); + const installedScriptsVersion = appPackage.dependencies[installedScriptsName]; + const templateScriptsName = Object.keys(templateDependencies).find(key => + key.includes('react-scripts') + ); + const templateScriptsVersion = templateDependencies[templateScriptsName]; + if ( + !!templateScriptsName && + (installedScriptsName !== templateScriptsName || + installedScriptsVersion !== templateScriptsVersion) + ) { + console.log(); + console.log( + chalk.yellow( + `The template requested \`${templateScriptsName}@${templateScriptsVersion}\`, however \`${installedScriptsName}@${installedScriptsVersion}\` has been installed.` + ) + ); + console.log(); + console.log( + chalk.yellow( + `This may cause unexpected behaviour. Please check with the template's author for more information.` + ) + ); + } + + // Remove template package + console.log(); + console.log(`Removing unnecessary template files using ${command}...`); + console.log(); + + proc = spawn.sync(command, ['remove', templateName], { stdio: 'inherit' }); + if (proc.status !== 0) { + console.error(`\`${command} ${args.join(' ')}\` failed`); + return; + } + + if (appPackage.dependencies['typescript'] != null) { verifyTypeScriptSetup(); } @@ -257,12 +278,3 @@ module.exports = function( console.log(); console.log('Happy hacking!'); }; - -function isReactInstalled(appPackage) { - const dependencies = appPackage.dependencies || {}; - - return ( - typeof dependencies.react !== 'undefined' && - typeof dependencies['react-dom'] !== 'undefined' - ); -} diff --git a/packages/react-scripts/template/src/index.css b/packages/react-scripts/template/src/index.css deleted file mode 100644 index cee5f348fb9..00000000000 --- a/packages/react-scripts/template/src/index.css +++ /dev/null @@ -1,14 +0,0 @@ -body { - margin: 0; - padding: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; -}