Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ yarn create react-app my-app

_[`yarn create <starter-kit-package>`](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.<br>
Inside that directory, it will generate the initial project structure and install the transitive dependencies:

Expand Down
6 changes: 6 additions & 0 deletions docusaurus/docs/adding-typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 9 additions & 1 deletion docusaurus/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/cra-template-typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
62 changes: 44 additions & 18 deletions packages/create-react-app/createReactApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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(
Expand All @@ -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';
}
}
}
Expand All @@ -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) {
Expand All @@ -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 = [
Expand Down Expand Up @@ -400,7 +426,7 @@ function run(
verbose,
originalDirectory,
template,
useYarn,
packageManager,
usePnp
) {
Promise.all([
Expand All @@ -416,7 +442,7 @@ function run(
getPackageInfo(templateToInstall),
])
.then(([packageInfo, templateInfo]) =>
checkIfOnline(useYarn).then(isOnline => ({
checkIfOnline(packageManager).then(isOnline => ({
isOnline,
packageInfo,
templateInfo,
Expand Down Expand Up @@ -460,7 +486,7 @@ function run(

return install(
root,
useYarn,
packageManager,
usePnp,
allDependencies,
verbose,
Expand Down Expand Up @@ -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);
Expand Down
31 changes: 24 additions & 7 deletions packages/react-scripts/scripts/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('');
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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.
}
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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}`);
Expand Down