From 33a3f4d782815317bf571b67a8ed01c1b2cee870 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sat, 8 Feb 2025 00:33:23 -0800 Subject: [PATCH] wip --- src/bun.js/bindings/bindings.cpp | 5 +- ...LACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx | 2 +- test/cli/create/create-jsx.test.ts | 324 ++++++++++++++++++ .../components/Feature.jsx | 27 ++ .../components/Features.jsx | 56 +++ .../components/Footer.jsx | 35 ++ .../react-spa-no-tailwind/components/Hero.jsx | 48 +++ .../create/react-spa-no-tailwind/index.html | 12 + .../create/react-spa-no-tailwind/index.jsx | 22 ++ .../create/react-spa-no-tailwind/styles.css | 278 +++++++++++++++ test/cli/create/shadcn.tsx | 105 ++++++ test/cli/create/tailwind.tsx | 88 +++++ 12 files changed, 1000 insertions(+), 2 deletions(-) create mode 100644 test/cli/create/create-jsx.test.ts create mode 100644 test/cli/create/react-spa-no-tailwind/components/Feature.jsx create mode 100644 test/cli/create/react-spa-no-tailwind/components/Features.jsx create mode 100644 test/cli/create/react-spa-no-tailwind/components/Footer.jsx create mode 100644 test/cli/create/react-spa-no-tailwind/components/Hero.jsx create mode 100644 test/cli/create/react-spa-no-tailwind/index.html create mode 100644 test/cli/create/react-spa-no-tailwind/index.jsx create mode 100644 test/cli/create/react-spa-no-tailwind/styles.css create mode 100644 test/cli/create/shadcn.tsx create mode 100644 test/cli/create/tailwind.tsx diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index d90c1d62138707..e62d57f561e9b1 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2766,7 +2766,10 @@ unsigned char JSC__JSCell__getType(JSC__JSCell* arg0) { return arg0->type(); } void JSC__JSString__toZigString(JSC__JSString* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2) { - *arg2 = Zig::toZigString(arg0->value(arg1)); + auto value = arg0->value(arg1); + + *arg2 = Zig::toZigString(value.data.impl()); + ASSERT(value.data.impl()->refCount() > 1); } bool JSC__JSString__eql(const JSC__JSString* arg0, JSC__JSGlobalObject* obj, JSC__JSString* arg2) diff --git a/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx index a2f9a3d79e8294..89d1a4f60a857c 100644 --- a/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx +++ b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx @@ -13,7 +13,7 @@ function mount(root: HTMLElement) { } let root = document.getElementById("root"); -if (root) { +if (document.readyState === "complete") { mount(root); } else { document.addEventListener("DOMContentLoaded", () => { diff --git a/test/cli/create/create-jsx.test.ts b/test/cli/create/create-jsx.test.ts new file mode 100644 index 00000000000000..0d488195e7b0d1 --- /dev/null +++ b/test/cli/create/create-jsx.test.ts @@ -0,0 +1,324 @@ +import "bun"; +import { expect, test, describe } from "bun:test"; +import { tempDirWithFiles, bunExe, bunEnv } from "harness"; +import { cp, readdir } from "fs/promises"; +import path from "path"; +import puppeteer from "puppeteer"; +import type { Subprocess } from "bun"; +import * as vm from "vm"; +const env = { + ...bunEnv, +}; + +async function getServerUrl(process: Subprocess, all = { text: "" }) { + // Read the port number from stdout + const decoder = new TextDecoder(); + let serverUrl = ""; + all.text = ""; + + const reader = process.stdout.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const textChunk = decoder.decode(value, { stream: true }); + all.text += textChunk; + console.log(textChunk); + + if (all.text.includes("http://")) { + serverUrl = all.text.trim(); + serverUrl = serverUrl.slice(serverUrl.indexOf("http://")); + + serverUrl = serverUrl.slice(0, serverUrl.indexOf("\n")); + if (URL.canParse(serverUrl)) { + break; + } + + serverUrl = serverUrl.slice(0, serverUrl.indexOf("/n")); + serverUrl = serverUrl.slice(0, serverUrl.lastIndexOf("/")); + serverUrl = serverUrl.trim(); + + if (URL.canParse(serverUrl)) { + break; + } + } + } + reader.releaseLock(); + + if (!serverUrl) { + throw new Error("Could not find server URL in stdout"); + } + + return serverUrl; +} + +async function checkBuildOutput(dir: string) { + const distDir = path.join(dir, "dist"); + const files = await readdir(distDir); + expect(files.some(f => f.endsWith(".js"))).toBe(true); + expect(files.some(f => f.endsWith(".html"))).toBe(true); + expect(files.some(f => f.endsWith(".css"))).toBe(true); +} + +describe("react spa (no tailwind)", async () => { + const dir = tempDirWithFiles("react-spa-no-tailwind", { + "README.md": "Hello, world!", + }); + + await cp(path.join(__dirname, "react-spa-no-tailwind"), dir, { + recursive: true, + force: true, + }); + + test("dev server", async () => { + await using process = Bun.spawn([bunExe(), "create", "./index.jsx"], { + cwd: dir, + env: env, + stdout: "pipe", + stdin: "ignore", + }); + const all = { text: "" }; + const serverUrl = await getServerUrl(process, all); + console.log(serverUrl); + + const browser = await puppeteer.launch({ + headless: true, + }); + try { + const page = await browser.newPage(); + await page.goto(serverUrl, { waitUntil: "networkidle0" }); + + const content = await page.evaluate(() => document.documentElement.innerHTML); + + expect(normalizeHTML(content)).toMatchInlineSnapshot(); + + expect( + all.text + .replace(/v\d+\.\d+\.\d+(?:\s*\([a-f0-9]+\))?/g, "v*.*.*") // Handle version with git hash + .replace(/\[\d+\.?\d*m?s\]/g, "[*ms]") + .replace(/@\d+\.\d+\.\d+/g, "@*.*.*") + .replace(/\d+\.\d+\s*ms/g, "*.** ms") + .replace(/^\s+/gm, "") // Remove leading spaces + .replace(/installed react(-dom)?@\d+\.\d+\.\d+/g, "installed react$1@*.*.*") // Handle react versions + .trim(), + ).toMatchInlineSnapshot(` +"create index.build.ts build +create index.css css +create index.html html +create index.client.tsx bun +create package.json npm +๐Ÿ“ฆ Auto-installing 3 detected dependencies +$ bun --only-missing install classnames react-dom@19 react@19 +bun add v*.*.* +installed classnames@*.*.* +installed react-dom@*.*.* +installed react@*.*.* +4 packages installed [*ms] +-------------------------------- +โœจ React project configured +Development - frontend dev server with hot reload +bun dev +Production - build optimized assets +bun run build +Happy bunning! ๐Ÿ‡ +Bun v*.*.* dev server ready in *.** ms +url: ${serverUrl}/" +`); + } finally { + await browser.close(); + process.kill(); + } + }); + + test("build", async () => { + const process = Bun.spawn([bunExe(), "run", "build"], { + cwd: dir, + env: env, + stdout: "pipe", + }); + + await process.exited; + await checkBuildOutput(dir); + }); +}); + +describe("react spa (tailwind)", async () => { + const dir = tempDirWithFiles("react-spa-tailwind", { + "index.tsx": await Bun.file(path.join(__dirname, "tailwind.tsx")).text(), + }); + + test.only("dev server", async () => { + const process = Bun.spawn([bunExe(), "create", "./index.tsx"], { + cwd: dir, + env: env, + stdout: "pipe", + stdin: "ignore", + }); + const all = { text: "" }; + const serverUrl = await getServerUrl(process, all); + console.log(serverUrl); + + const browser = await puppeteer.launch({ + headless: true, + }); + try { + const page = await browser.newPage(); + await page.goto(serverUrl, { waitUntil: "networkidle0" }); + + // Check that React root exists and has Tailwind classes + const root = await page.$("#root"); + expect(root).toBeTruthy(); + + const content = await page.evaluate(() => document.documentElement.outerHTML); + expect(normalizeHTML(content)).toMatchInlineSnapshot(` + " + + + index | Powered by Bun + + + +

bun create for React

Start a React dev server instantly from a single component file

bun create ./MyComponent.tsx

Zero Config

Just write your React component and run. No setup needed.

Auto Dependencies

Automatically detects and installs required npm packages.

Tool Detection

Recognizes Tailwind, animations, and UI libraries automatically.

How it Works

1

Create Component

Write your React component in a .tsx file

2

Run Command

Execute bun create with your file path

3

Start Developing

Dev server starts instantly with hot reload

+ " + `); + + expect( + all.text + .replace(/v\d+\.\d+\.\d+(?:\s*\([a-f0-9]+\))?/g, "v*.*.*") + .replace(/\[\d+\.?\d*m?s\]/g, "[*ms]") + .replace(/@\d+\.\d+\.\d+/g, "@*.*.*") + .replace(/\d+\.\d+\s*ms/g, "*.** ms") + .replace(/^\s+/gm, "") + .replace(/installed (react(-dom)?|tailwindcss)@\d+\.\d+\.\d+/g, "installed $1@*.*.*") + .trim(), + ).toMatchInlineSnapshot(` + "create index.build.ts build + create index.css css + create index.html html + create index.client.tsx bun + create bunfig.toml bun + create package.json npm + ๐Ÿ“ฆ Auto-installing 4 detected dependencies + $ bun --only-missing install tailwindcss bun-plugin-tailwind react-dom@19 react@19 + bun add v*.*.* + installed tailwindcss@*.*.* + installed bun-plugin-tailwind@*.*.* + installed react-dom@*.*.* + installed react@*.*.* + 7 packages installed [*ms] + -------------------------------- + โœจ React + Tailwind project configured + Development - frontend dev server with hot reload + bun dev + Production - build optimized assets + bun run build + Happy bunning! ๐Ÿ‡ + Bun v*.*.* dev server ready in *.** ms + url: http://localhost:3002/" + `); + } finally { + await browser.close(); + process.kill(); + } + }); + + test("build", async () => { + const process = Bun.spawn([bunExe(), "run", "build"], { + cwd: dir, + env: env, + stdout: "pipe", + }); + + await process.exited; + await checkBuildOutput(dir); + }); +}); + +test("shadcn/ui", async () => { + const dir = tempDirWithFiles("shadcn-ui", { + "index.tsx": await Bun.file(path.join(__dirname, "shadcn.tsx")).text(), + }); + + test("dev server", async () => { + const process = Bun.spawn([bunExe(), "create", "./index.tsx"], { + cwd: dir, + env: env, + stdout: "pipe", + stdin: "ignore", + }); + const all = { text: "" }; + const serverUrl = await getServerUrl(process, all); + console.log(serverUrl); + + const browser = await puppeteer.launch({ + headless: true, + }); + try { + const page = await browser.newPage(); + await page.goto(serverUrl, { waitUntil: "networkidle0" }); + + // Check that React root exists and has Shadcn components + const root = await page.$("#root"); + expect(root).toBeTruthy(); + + const content = await page.evaluate(() => document.documentElement.innerHTML); + expect(content).toContain("shadcn"); // Basic check for Shadcn classes + + // Check for components.json + const componentsJson = await Bun.file(path.join(dir, "components.json")).exists(); + expect(componentsJson).toBe(true); + + expect( + all.text + .replace(/v\d+\.\d+\.\d+(?:\s*\([a-f0-9]+\))?/g, "v*.*.*") + .replace(/\[\d+\.?\d*m?s\]/g, "[*ms]") + .replace(/@\d+\.\d+\.\d+/g, "@*.*.*") + .replace(/\d+\.\d+\s*ms/g, "*.** ms") + .replace(/^\s+/gm, "") + .replace( + /installed (react(-dom)?|@radix-ui\/.*|tailwindcss|class-variance-authority|clsx|lucide-react|tailwind-merge)@\d+\.\d+\.\d+/g, + "installed $1@*.*.*", + ) + .trim(), + ).toMatchInlineSnapshot(); + } finally { + await browser.close(); + process.kill(); + } + }); + + test("build", async () => { + const process = Bun.spawn([bunExe(), "run", "build"], { + cwd: dir, + env: env, + stdout: "pipe", + }); + + await process.exited; + await checkBuildOutput(dir); + }); +}); + +function normalizeHTML(html: string) { + return html + .split("\n") + .map(line => { + // First trim the line + const trimmed = line.trim(); + if (!trimmed) return ""; + + // Replace chunk hashes in stylesheet and script tags + return trimmed.replace( + /<(link rel="stylesheet" crossorigin="" href|script type="module" crossorigin="" src)="\/chunk-[a-zA-Z0-9]+\.(css|js)("><\/script>|">)/g, + (_, tagStart, ext) => { + if (ext === "css") { + return `<${tagStart}="/chunk-[HASH].css">`; + } + return `<${tagStart}="/chunk-[HASH].js">`; + }, + ); + }) + .filter(Boolean) + .join("\n") + .trim(); +} diff --git a/test/cli/create/react-spa-no-tailwind/components/Feature.jsx b/test/cli/create/react-spa-no-tailwind/components/Feature.jsx new file mode 100644 index 00000000000000..0d1cd5fa9e2a60 --- /dev/null +++ b/test/cli/create/react-spa-no-tailwind/components/Feature.jsx @@ -0,0 +1,27 @@ +import React from "react"; +import classNames from "classnames"; + +export default function Feature({ icon, title, description, highlight }) { + return ( +
+
{icon}
+

{title}

+

+ {highlight ? ( + <> + {description.split(highlight).map((part, i, arr) => ( + + {part} + {i < arr.length - 1 && ( + {highlight} + )} + + ))} + + ) : ( + description + )} +

+
+ ); +} diff --git a/test/cli/create/react-spa-no-tailwind/components/Features.jsx b/test/cli/create/react-spa-no-tailwind/components/Features.jsx new file mode 100644 index 00000000000000..3b5e492ae58b29 --- /dev/null +++ b/test/cli/create/react-spa-no-tailwind/components/Features.jsx @@ -0,0 +1,56 @@ +import React from "react"; +import Feature from "./Feature"; + +const FEATURES = [ + { + icon: "โšก๏ธ", + title: "Lightning Fast", + description: + "Built from scratch in Zig, Bun is focused on performance and developer experience", + highlight: "Zig", + }, + { + icon: "๐ŸŽฏ", + title: "All-in-One", + description: + "Bundler, test runner, and npm-compatible package manager in a single tool", + }, + { + icon: "๐Ÿš€", + title: "JavaScript Runtime", + description: "Drop-in replacement for Node.js with 3x faster startup time", + highlight: "3x faster", + }, + { + icon: "๐Ÿ“ฆ", + title: "Package Management", + description: + "Native package manager that can install dependencies up to 30x faster than npm", + highlight: "30x faster", + }, + { + icon: "๐Ÿงช", + title: "Testing Made Simple", + description: + "Built-in test runner with Jest-compatible API and snapshot testing", + }, + { + icon: "๐Ÿ”ฅ", + title: "Hot Reloading", + description: + "Lightning-fast hot module replacement (HMR) for rapid development", + }, +]; + +export default function Features() { + return ( +
+

Why Choose Bun?

+
+ {FEATURES.map((feature, index) => ( + + ))} +
+
+ ); +} diff --git a/test/cli/create/react-spa-no-tailwind/components/Footer.jsx b/test/cli/create/react-spa-no-tailwind/components/Footer.jsx new file mode 100644 index 00000000000000..20d63f7966f4ea --- /dev/null +++ b/test/cli/create/react-spa-no-tailwind/components/Footer.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import classNames from "classnames"; + +const LINKS = [ + { text: "Documentation", url: "https://bun.sh/docs" }, + { text: "GitHub", url: "https://github.com/oven-sh/bun" }, + { text: "Discord", url: "https://bun.sh/discord" }, + { text: "Blog", url: "https://bun.sh/blog" }, +]; + +export default function Footer() { + return ( +
+
+
+ ๐ŸฅŸ + Built with Bun +
+ +
+
+ ); +} diff --git a/test/cli/create/react-spa-no-tailwind/components/Hero.jsx b/test/cli/create/react-spa-no-tailwind/components/Hero.jsx new file mode 100644 index 00000000000000..27bd415b7dca08 --- /dev/null +++ b/test/cli/create/react-spa-no-tailwind/components/Hero.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import classNames from "classnames"; + +export default function Hero() { + return ( +
+
๐ŸฅŸ
+

+ Welcome to Bun +

+

+ The all-in-one JavaScript runtime & toolkit designed for speed +

+
+ + Get Started + + + View on GitHub + +
+
+
+ 3x + Bun Bun Bun +
+
+ 0.5s + Average Install Time +
+
+ Extremely + Node.js Compatible +
+
+
+ ); +} diff --git a/test/cli/create/react-spa-no-tailwind/index.html b/test/cli/create/react-spa-no-tailwind/index.html new file mode 100644 index 00000000000000..78c0dcd8701944 --- /dev/null +++ b/test/cli/create/react-spa-no-tailwind/index.html @@ -0,0 +1,12 @@ + + + + + + Bun - The Modern JavaScript Runtime + + +
+ + + diff --git a/test/cli/create/react-spa-no-tailwind/index.jsx b/test/cli/create/react-spa-no-tailwind/index.jsx new file mode 100644 index 00000000000000..7f38d302b0cf14 --- /dev/null +++ b/test/cli/create/react-spa-no-tailwind/index.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import classNames from "classnames"; +import "./styles.css"; + +import Hero from "./components/Hero"; +import Features from "./components/Features"; +import Footer from "./components/Footer"; + +function App() { + return ( +
+
+ + +
+
+
+ ); +} + +export default App; diff --git a/test/cli/create/react-spa-no-tailwind/styles.css b/test/cli/create/react-spa-no-tailwind/styles.css new file mode 100644 index 00000000000000..8a143902f0dc6d --- /dev/null +++ b/test/cli/create/react-spa-no-tailwind/styles.css @@ -0,0 +1,278 @@ +:root { + --primary-color: #fbf0ff; + --accent-color: #7c3aed; + --text-color: #1a1a1a; + --secondary-color: #4c1d95; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-700: #374151; + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, sans-serif; + background: var(--primary-color); + color: var(--text-color); + line-height: 1.6; +} + +.app { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + flex: 1; +} + +/* Hero Section */ +.hero { + margin: 4rem 0; + text-align: center; +} + +.logo { + font-size: 5rem; + margin-bottom: 1rem; + display: inline-block; +} + +.animate-bounce { + animation: bounce 2s infinite; +} + +@keyframes bounce { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-20px); + } +} + +h1 { + font-size: 3.5rem; + margin-bottom: 1rem; + background: linear-gradient( + 120deg, + var(--accent-color), + var(--secondary-color) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.gradient-text { + background: linear-gradient( + 120deg, + var(--accent-color), + var(--secondary-color) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.description { + font-size: 1.5rem; + margin-bottom: 2rem; + color: var(--gray-700); + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +/* CTA Buttons */ +.cta-buttons { + display: flex; + gap: 1rem; + justify-content: center; + margin-bottom: 3rem; +} + +.button { + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 600; + text-decoration: none; + transition: transform 0.2s, box-shadow 0.2s; +} + +.button:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.button.primary { + background: var(--accent-color); + color: white; +} + +.button.secondary { + background: white; + color: var(--accent-color); + border: 2px solid var(--accent-color); +} + +/* Stats */ +.stats { + display: flex; + justify-content: center; + gap: 3rem; + margin-top: 3rem; +} + +.stat { + text-align: center; +} + +.stat-value { + font-size: 2.5rem; + font-weight: bold; + color: var(--accent-color); + display: block; +} + +.stat-label { + color: var(--gray-700); + font-size: 1rem; +} + +/* Features Section */ +.features-section { + padding: 4rem 0; +} + +.features-section h2 { + text-align: center; + font-size: 2.5rem; + margin-bottom: 3rem; + color: var(--accent-color); +} + +.features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; +} + +.feature { + padding: 2rem; + background: white; + border-radius: 12px; + box-shadow: var(--shadow-sm); + transition: all 0.3s ease; +} + +.feature:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-md); +} + +.feature-icon { + font-size: 2.5rem; + margin-bottom: 1rem; +} + +.feature h3 { + color: var(--accent-color); + margin-bottom: 1rem; + font-size: 1.5rem; +} + +.highlight { + background: linear-gradient(120deg, #7c3aed20 0%, #7c3aed10 100%); + padding: 0.2em 0.4em; + border-radius: 4px; + font-weight: bold; +} + +/* Footer */ +.footer { + background: white; + padding: 2rem 0; + margin-top: 4rem; + border-top: 1px solid var(--gray-200); +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.footer-logo { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.logo-small { + font-size: 1.5rem; +} + +.footer-text { + font-weight: 500; + color: var(--gray-700); +} + +.footer-links { + display: flex; + gap: 2rem; +} + +.footer-link { + color: var(--gray-700); + text-decoration: none; + transition: color 0.2s; +} + +.footer-link:hover { + color: var(--accent-color); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .container { + padding: 1rem; + } + + h1 { + font-size: 2.5rem; + } + + .description { + font-size: 1.2rem; + } + + .stats { + flex-direction: column; + gap: 2rem; + } + + .footer-content { + flex-direction: column; + gap: 1rem; + text-align: center; + } + + .footer-links { + flex-wrap: wrap; + justify-content: center; + } +} diff --git a/test/cli/create/shadcn.tsx b/test/cli/create/shadcn.tsx new file mode 100644 index 00000000000000..71eb1f1faae060 --- /dev/null +++ b/test/cli/create/shadcn.tsx @@ -0,0 +1,105 @@ +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Card } from "@/components/ui/card"; +import { CheckCircle } from "lucide-react"; + +export default function LandingPage() { + const features = [ + { + title: "Auto Dependencies", + description: + "Automatically detects and installs required dependencies for your component", + }, + { + title: "Tool Detection", + description: + "Seamlessly integrates with Tailwind CSS, shadcn/ui, and other popular tools", + }, + { + title: "Zero Config", + description: + "No setup required. Start developing instantly with hot reload enabled", + }, + ]; + + return ( +
+
+ {/* Hero Section */} +
+ + New in Bun 1.2.3 + +

+ From Component to App in + Seconds +

+

+ Start a complete dev server from a single React component. No config + needed. +

+ +
+ + +
+
+ + {/* Code Preview */} + +
+
+              
+                $ bun create ./MyComponent.tsx
+                
+ ๐Ÿ“ฆ Installing dependencies... +
+ ๐Ÿ” Detected Tailwind CSS +
+ ๐ŸŽจ Detected shadcn/ui +
โœจ Dev server running at http://localhost:3000 +
+
+
+
+ + {/* Features Grid */} +
+ {features.map((feature, index) => ( + +
+ +

{feature.title}

+
+

{feature.description}

+
+ ))} +
+ + {/* CTA Section */} +
+ +

+ Ready to streamline your React development? +

+

+ Get started with Bun's powerful component development workflow + today. +

+ +
+
+
+
+ ); +} + diff --git a/test/cli/create/tailwind.tsx b/test/cli/create/tailwind.tsx new file mode 100644 index 00000000000000..ca2a550b2ffa1d --- /dev/null +++ b/test/cli/create/tailwind.tsx @@ -0,0 +1,88 @@ +export default function LandingPage() { + let copied = false; + const handleCopy = () => { + navigator.clipboard.writeText("bun create ./MyComponent.tsx"); + }; + + return ( +
+
+
+

+ bun create for React +

+

Start a React dev server instantly from a single component file

+ +
+ bun create ./MyComponent.tsx + +
+
+ +
+
+

Zero Config

+

Just write your React component and run. No setup needed.

+
+ +
+

Auto Dependencies

+

Automatically detects and installs required npm packages.

+
+ +
+

Tool Detection

+

Recognizes Tailwind, animations, and UI libraries automatically.

+
+
+ +
+

How it Works

+
+
+
1
+
+

Create Component

+

Write your React component in a .tsx file

+
+
+
+
2
+
+

Run Command

+

Execute bun create with your file path

+
+
+
+
3
+
+

Start Developing

+

Dev server starts instantly with hot reload

+
+
+
+
+ +
+

Ready to Try?

+ +
+
+
+ ); +}