diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 536c258ad421..f61146a877b6 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -228,10 +228,12 @@ Bun can also execute `"scripts"` from your `package.json`. Add the following scr "start": "bun run index.ts" // [!code ++] }, // [!code ++] "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "@types/react": "^19" }, "peerDependencies": { - "typescript": "^6" + "typescript": "^6", + "react": "^19" } } ``` diff --git a/src/runtime/cli/init_command.rs b/src/runtime/cli/init_command.rs index 5a1ce6ebeedc..dee8ff3b839c 100644 --- a/src/runtime/cli/init_command.rs +++ b/src/runtime/cli/init_command.rs @@ -736,8 +736,27 @@ impl InitCommand { true }; - need_run_bun_install = - needs_dependencies || needs_dev_dependencies || needs_typescript_dependency; + let needs_react_dependency = !minimal + && template == Template::Blank + && 'brk: { + for key in [ + b"dependencies".as_slice(), + b"devDependencies", + b"peerDependencies", + ] { + if let Some(deps) = object.get(key) { + if deps.has_any_property_named(&[b"react", b"@types/react"]) { + break 'brk false; + } + } + } + true + }; + + need_run_bun_install = needs_dependencies + || needs_dev_dependencies + || needs_typescript_dependency + || needs_react_dependency; if needs_dependencies { let mut dependencies_object = object.get(b"dependencies").unwrap_or_else(|| { @@ -770,15 +789,35 @@ impl InitCommand { object.put(&bump, b"devDependencies", obj)?; } - if needs_typescript_dependency { + if needs_react_dependency { + let mut obj = object.get(b"devDependencies").unwrap_or_else(|| { + bun_ast::Expr::init(bun_ast::E::Object::default(), bun_ast::Loc::EMPTY) + }); + obj.data + .e_object_mut() + .unwrap() + .put_string(&bump, b"@types/react", b"^19")?; + object.put(&bump, b"devDependencies", obj)?; + } + + if needs_typescript_dependency || needs_react_dependency { let mut peer_dependencies = object.get(b"peerDependencies").unwrap_or_else(|| { bun_ast::Expr::init(bun_ast::E::Object::default(), bun_ast::Loc::EMPTY) }); - peer_dependencies.data.e_object_mut().unwrap().put_string( - &bump, - b"typescript", - b"^6", - )?; + if needs_typescript_dependency { + peer_dependencies.data.e_object_mut().unwrap().put_string( + &bump, + b"typescript", + b"^6", + )?; + } + if needs_react_dependency { + peer_dependencies + .data + .e_object_mut() + .unwrap() + .put_string(&bump, b"react", b"^19")?; + } object.put(&bump, b"peerDependencies", peer_dependencies)?; } } diff --git a/test/cli/init/init.test.ts b/test/cli/init/init.test.ts index 6862cb9936af..5538840a1b09 100644 --- a/test/cli/init/init.test.ts +++ b/test/cli/init/init.test.ts @@ -47,9 +47,11 @@ const initEnv = { ...bunEnv, BUN_AGENT_RULE_DISABLED: "1" }; "private": true, "devDependencies": { "@types/bun": "latest", + "@types/react": "^19", }, "peerDependencies": { "typescript": "^6", + "react": "^19", }, }); const readme = fs.readFileSync(path.join(temp, "README.md"), "utf8"); @@ -222,10 +224,12 @@ const initEnv = { ...bunEnv, BUN_AGENT_RULE_DISABLED: "1" }; { "devDependencies": { "@types/bun": "latest", + "@types/react": "^19", }, "module": "index.ts", "name": "my edited package.json", "peerDependencies": { + "react": "^19", "typescript": "^6", }, "private": true, @@ -237,6 +241,80 @@ const initEnv = { ...bunEnv, BUN_AGENT_RULE_DISABLED: "1" }; ); }); + // https://github.com/oven-sh/bun/issues/5056 + // The blank scaffold's tsconfig sets `"jsx": "react-jsx"`, so a bare `.tsx` + // file must both run and typecheck without any manual `bun add`. + test("bun init -y: a .tsx file runs and typechecks out of the box", async () => { + await using temp = tempDir("bun-init-tsx-works", {}); + + await using init = Bun.spawn({ + cmd: [bunExe(), "init", "-y"], + cwd: temp, + stdio: ["ignore", "pipe", "pipe"], + env: initEnv, + }); + const [, , initExit] = await Promise.all([init.stdout.text(), init.stderr.text(), init.exited]); + expect(initExit).toBe(0); + + const pkg = JSON.parse(fs.readFileSync(path.join(temp, "package.json"), "utf8")); + expect(pkg.peerDependencies).toHaveProperty("react"); + expect(pkg.devDependencies).toHaveProperty("@types/react"); + expect(fs.existsSync(path.join(temp, "node_modules/react"))).toBe(true); + expect(fs.existsSync(path.join(temp, "node_modules/@types/react"))).toBe(true); + + const tsx = `function Component(props: { message: string }): any { + return ( +
+