diff --git a/README.md b/README.md index 4672c9f993d..a74bd03ed0e 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,12 @@ yarn create react-app my-app _[`yarn create `](https://yarnpkg.com/lang/en/docs/cli/create/) is available in Yarn 0.25+_ +### Bun + +```sh +bunx create-react-app my-app +``` + It will create a directory called `my-app` inside the current folder.
Inside that directory, it will generate the initial project structure and install the transitive dependencies: diff --git a/docusaurus/docs/adding-typescript.md b/docusaurus/docs/adding-typescript.md index d94bccc4920..63759fb7044 100644 --- a/docusaurus/docs/adding-typescript.md +++ b/docusaurus/docs/adding-typescript.md @@ -21,6 +21,12 @@ or yarn create react-app my-app --template typescript ``` +or + +```sh +bunx create-react-app my-app --template typescript +``` + > If you've previously installed `create-react-app` globally via `npm install -g create-react-app`, we recommend you uninstall the package using `npm uninstall -g create-react-app` or `yarn global remove create-react-app` to ensure that `npx` always uses the latest version. > > Global installs of `create-react-app` are no longer supported. diff --git a/docusaurus/docs/getting-started.md b/docusaurus/docs/getting-started.md index ef7da3006f0..ba0fa0e73a4 100644 --- a/docusaurus/docs/getting-started.md +++ b/docusaurus/docs/getting-started.md @@ -62,6 +62,12 @@ yarn create react-app my-app _`yarn create` is available in Yarn 0.25+_ +### Bun + +```sh +bunx create-react-app my-app +``` + ### Selecting a template You can now optionally start a new app from a template by appending `--template [template-name]` to the creation command. @@ -90,13 +96,15 @@ If you already have a project and would like to add TypeScript, see our [Adding ### Selecting a package manager -When you create a new app, the CLI will use [npm](https://docs.npmjs.com) or [Yarn](https://yarnpkg.com/) to install dependencies, depending on which tool you use to run `create-react-app`. For example: +When you create a new app, the CLI will use [npm](https://docs.npmjs.com), [Yarn](https://yarnpkg.com/), or [Bun](https://bun.sh/package-manager) to install dependencies, depending on which tool you use to run `create-react-app`. For example: ```sh # Run this to use npm npx create-react-app my-app # Or run this to use yarn yarn create react-app my-app +# Or run this to use bun +bunx create-react-app my-app ``` ## Output diff --git a/packages/cra-template-typescript/README.md b/packages/cra-template-typescript/README.md index e7197568d18..eb5845fb245 100644 --- a/packages/cra-template-typescript/README.md +++ b/packages/cra-template-typescript/README.md @@ -8,10 +8,10 @@ For example: ```sh npx create-react-app my-app --template typescript - # or - yarn create react-app my-app --template typescript +# or +bunx create-react-app my-app --template typescript ``` For more information, please refer to: diff --git a/packages/create-react-app/createReactApp.js b/packages/create-react-app/createReactApp.js index b3c7ca7c1d6..16d3924d125 100755 --- a/packages/create-react-app/createReactApp.js +++ b/packages/create-react-app/createReactApp.js @@ -49,8 +49,15 @@ const validateProjectName = require('validate-npm-package-name'); const packageJson = require('./package.json'); -function isUsingYarn() { - return (process.env.npm_config_user_agent || '').indexOf('yarn') === 0; +function getPackageManager() { + const pm_user_agent = process.env.npm_config_user_agent || ''; + if (pm_user_agent.indexOf('yarn') === 0) { + return 'yarn'; + } + if (pm_user_agent.indexOf('bun') === 0) { + return 'bun'; + } + return 'npm'; } let projectName; @@ -219,20 +226,23 @@ function init() { ); console.log(); } else { - const useYarn = isUsingYarn(); + let packageManager = getPackageManager(); + if (packageManager === 'yarn' && program.usePnp) { + packageManager = 'yarn-pnp'; + } createApp( projectName, program.verbose, program.scriptsVersion, program.template, - useYarn, + packageManager, program.usePnp ); } }); } -function createApp(name, verbose, version, template, useYarn, usePnp) { +function createApp(name, verbose, version, template, packageManager, usePnp) { const unsupportedNodeVersion = !semver.satisfies( // Coerce strings with metadata (i.e. `15.0.0-nightly`). semver.coerce(process.version), @@ -275,11 +285,11 @@ function createApp(name, verbose, version, template, useYarn, usePnp) { const originalDirectory = process.cwd(); process.chdir(root); - if (!useYarn && !checkThatNpmCanReadCwd()) { + if (packageManager === 'npm' && !checkThatNpmCanReadCwd()) { process.exit(1); } - if (!useYarn) { + if (packageManager === 'npm') { const npmInfo = checkNpmVersion(); if (!npmInfo.hasMinNpm) { if (npmInfo.npmVersion) { @@ -293,7 +303,7 @@ function createApp(name, verbose, version, template, useYarn, usePnp) { // Fall back to latest supported react-scripts for npm 3 version = 'react-scripts@0.9.x'; } - } else if (usePnp) { + } else if (packageManager.startsWith('yarn') && usePnp) { const yarnInfo = checkYarnVersion(); if (yarnInfo.yarnVersion) { if (!yarnInfo.hasMinYarnPnp) { @@ -304,7 +314,7 @@ function createApp(name, verbose, version, template, useYarn, usePnp) { ) ); // 1.11 had an issue with webpack-dev-middleware, so better not use PnP with it (never reached stable, but still) - usePnp = false; + packageManager = 'yarn'; } if (!yarnInfo.hasMaxYarnPnp) { console.log( @@ -313,7 +323,7 @@ function createApp(name, verbose, version, template, useYarn, usePnp) { ) ); // 2 supports PnP by default and breaks when trying to use the flag - usePnp = false; + packageManager = 'yarn'; } } } @@ -325,16 +335,23 @@ function createApp(name, verbose, version, template, useYarn, usePnp) { verbose, originalDirectory, template, - useYarn, + packageManager, usePnp ); } -function install(root, useYarn, usePnp, dependencies, verbose, isOnline) { +function install( + root, + packageManager, + usePnp, + dependencies, + verbose, + isOnline +) { return new Promise((resolve, reject) => { let command; let args; - if (useYarn) { + if (packageManager.startsWith('yarn')) { command = 'yarnpkg'; args = ['add', '--exact']; if (!isOnline) { @@ -358,6 +375,15 @@ function install(root, useYarn, usePnp, dependencies, verbose, isOnline) { console.log(chalk.yellow('Falling back to the local Yarn cache.')); console.log(); } + } else if (packageManager === 'bun') { + command = 'bun'; + args = ['add', '--exact'].concat(dependencies); + + if (usePnp) { + console.log(chalk.yellow("Bun doesn't support PnP.")); + console.log(chalk.yellow('Falling back to the regular installs.')); + console.log(); + } } else { command = 'npm'; args = [ @@ -400,7 +426,7 @@ function run( verbose, originalDirectory, template, - useYarn, + packageManager, usePnp ) { Promise.all([ @@ -416,7 +442,7 @@ function run( getPackageInfo(templateToInstall), ]) .then(([packageInfo, templateInfo]) => - checkIfOnline(useYarn).then(isOnline => ({ + checkIfOnline(packageManager).then(isOnline => ({ isOnline, packageInfo, templateInfo, @@ -460,7 +486,7 @@ function run( return install( root, - useYarn, + packageManager, usePnp, allDependencies, verbose, @@ -1051,8 +1077,8 @@ function checkThatNpmCanReadCwd() { return false; } -function checkIfOnline(useYarn) { - if (!useYarn) { +function checkIfOnline(packageManager) { + if (!packageManager.startsWith('yarn')) { // Don't ping the Yarn registry. // We'll just assume the best case. return Promise.resolve(true); diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index 16822e7a8a2..730372785c7 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -90,6 +90,7 @@ module.exports = function ( ) { const appPackage = require(path.join(appPath, 'package.json')); const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock')); + const useBun = fs.existsSync(path.join(appPath, 'bun.lockb')); if (!templateName) { console.log(''); @@ -203,6 +204,17 @@ module.exports = function ( ); } + // Update scripts for Bun users + if (useBun) { + appPackage.scripts = Object.entries(appPackage.scripts).reduce( + (acc, [key, value]) => ({ + ...acc, + [key]: value.replace(/(npm run |npm )/, 'bun run '), + }), + {} + ); + } + // Setup the eslint config appPackage.eslintConfig = { extends: 'react-app', @@ -241,14 +253,15 @@ module.exports = function ( } // modifies README.md commands based on user used package manager. - if (useYarn) { + if (useYarn || useBun) { try { const readme = fs.readFileSync(path.join(appPath, 'README.md'), 'utf8'); - fs.writeFileSync( - path.join(appPath, 'README.md'), - readme.replace(/(npm run |npm )/g, 'yarn '), - 'utf8' - ); + let newReadme = useYarn + ? readme.replace(/(npm run |npm )/g, 'yarn ') + : useBun + ? readme.replace(/(npm run |npm )/g, 'bun run ') + : readme; + fs.writeFileSync(path.join(appPath, 'README.md'), newReadme, 'utf8'); } catch (err) { // Silencing the error. As it fall backs to using default npm commands. } @@ -287,6 +300,10 @@ module.exports = function ( command = 'yarnpkg'; remove = 'remove'; args = ['add']; + } else if (useBun) { + command = 'bun'; + remove = 'remove'; + args = ['add']; } else { command = 'npm'; remove = 'uninstall'; @@ -363,7 +380,7 @@ module.exports = function ( } // Change displayed command to yarn instead of yarnpkg - const displayedCommand = useYarn ? 'yarn' : 'npm'; + const displayedCommand = useYarn ? 'yarn' : useBun ? 'bun' : 'npm'; console.log(); console.log(`Success! Created ${appName} at ${appPath}`);