UNPKG

create-bawo-frontend

Version:

Scaffold React (Vite) or Next.js with Tailwind, Zustand, Prettier, optional shadcn/ui, Framer Motion, and GSAP.

163 lines (145 loc) 9.49 kB
/* eslint-disable no-console */ const { path, write, ensure, read, run } = require("../utils"); const T = require("../templates"); const { setupShadcn } = require("../setup/shadcn"); function scaffoldReact(projectDir, answers) { const deps = ["react", "react-dom"]; const dev = ["vite", "@vitejs/plugin-react", "tailwindcss@3.4.14", "postcss", "autoprefixer"]; if (answers.ts) dev.push("typescript", "@types/react", "@types/react-dom"); if (answers.redux || answers.rtkQuery) deps.push("@reduxjs/toolkit", "react-redux"); if (answers.reactQuery) deps.push("@tanstack/react-query"); if (answers.swr) deps.push("swr"); if (answers.router) deps.push("react-router-dom"); run("npm", ["i", ...deps], projectDir); run("npm", ["i", "-D", ...dev], projectDir); write(path.join(projectDir, "vite.config.ts"), ` import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()] }); `.trimStart()); const pkgPath = path.join(projectDir, "package.json"); const pkg = JSON.parse(read(pkgPath)); pkg.scripts = { ...(pkg.scripts || {}), dev: "vite", build: "vite build", preview: "vite preview", lint: 'echo "(add eslint if you want)" && exit 0', format: 'echo "(add prettier if you want)" && exit 0' }; write(pkgPath, JSON.stringify(pkg, null, 2)); write(path.join(projectDir, "index.html"), ` <!doctype html> <html lang="en"> <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Vite + React + Tailwind</title></head> <body class="min-h-screen bg-white text-gray-900"><div id="root"></div><script type="module" src="/src/main.${answers.ts ? "tsx" : "jsx"}"></script></body> </html> `.trimStart()); // Tailwind write(path.join(projectDir, "tailwind.config.cjs"), `module.exports = { content: ["./index.html","./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {} }, plugins: [] };`); write(path.join(projectDir, "postcss.config.cjs"), `module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } };`); write(path.join(projectDir, "src/styles/index.css"), `@tailwind base;\n@tailwind components;\n@tailwind utilities;`); // main.jsx(x) let main = ` import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; import "./styles/index.css"; `; if (answers.redux || answers.rtkQuery) main += `import { Provider } from "react-redux";\nimport { store } from "./store/store";\n`; if (answers.reactQuery) main += `import { QueryClient, QueryClientProvider } from "@tanstack/react-query";\nconst queryClient = new QueryClient();\n`; if (answers.context) main += `import { ThemeProvider } from "./components/demo/ContextDemo";\n`; main += ` const root = createRoot(document.getElementById("root")); root.render( <React.StrictMode>`; if (answers.redux || answers.rtkQuery) main += `\n <Provider store={store}>`; if (answers.reactQuery) main += `\n <QueryClientProvider client={queryClient}>`; if (answers.context) main += `\n <ThemeProvider>`; main += `\n <App />`; if (answers.context) main += `\n </ThemeProvider>`; if (answers.reactQuery) main += `\n </QueryClientProvider>`; if (answers.redux || answers.rtkQuery) main += `\n </Provider>`; main += `\n </React.StrictMode>\n);`; write(path.join(projectDir, "src", `main.${answers.ts ? "tsx" : "jsx"}`), main.trimStart()); // App.jsx(x) let appImports = `${answers.ts ? "import type {} from 'react';" : ""}`; const demos = []; if (answers.redux) { appImports += `\nimport ReduxDemo from "./components/demo/ReduxDemo";`; demos.push("<ReduxDemo />"); } if (answers.rtkQuery) { appImports += `\nimport RTKQueryDemo from "./components/demo/RTKQueryDemo";`; demos.push("<RTKQueryDemo />"); } if (answers.reactQuery) { appImports += `\nimport ReactQueryDemo from "./components/demo/ReactQueryDemo";`; demos.push("<ReactQueryDemo />"); } if (answers.swr) { appImports += `\nimport SWRDemo from "./components/demo/SWRDemo";`; demos.push("<SWRDemo />"); } if (answers.router) { appImports += `\nimport RouterDemo from "./components/demo/RouterDemo";`; demos.push("<RouterDemo />"); } if (answers.context) { appImports += `\nimport ContextDemo from "./components/demo/ContextDemo";`; demos.push("<ContextDemo />"); } appImports += `\nimport UseStateDemo from "./components/demo/UseStateDemo";`; demos.push("<UseStateDemo />"); write(path.join(projectDir, "src", `App.${answers.ts ? "tsx" : "jsx"}`), ` ${appImports} export default function App() { return ( <main className="p-6 max-w-4xl mx-auto"> <h1 className="text-3xl font-bold">Hello 👋 React + Vite + Tailwind (${answers.ts ? "TS" : "JS"})</h1> <p className="mt-3 text-gray-600">Edit <code className="px-1 rounded bg-gray-200">src/App.${answers.ts ? "tsx" : "jsx"}</code>.</p> <div className="space-y-4"> ${demos.join("\n ")} </div> </main> ); } `.trimStart()); // tsconfig if (answers.ts) { write(path.join(projectDir, "tsconfig.json"), JSON.stringify({ compilerOptions: { target: "ES2022", lib: ["ES2022", "DOM", "DOM.Iterable"], module: "ESNext", jsx: "react-jsx", moduleResolution: "Bundler", strict: true, skipLibCheck: true, esModuleInterop: true, noEmit: true }, include: ["src"], }, null, 2)); } // Redux & RTK Query wires if (answers.redux || answers.rtkQuery) { ensure(path.join(projectDir, "src", "store")); let storeContent = T.REDUX_STORE_JS; if (answers.rtkQuery) storeContent += `\nimport { api } from './api';\n`; const apiReducer = answers.rtkQuery ? `\n [api.reducerPath]: api.reducer,` : ""; const apiMiddleware = answers.rtkQuery ? `\n middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),` : ""; storeContent += "\n" + T.REDUX_STORE_JS_TAIL.replace("{{API_REDUCER}}", apiReducer).replace("{{API_MIDDLEWARE}}", apiMiddleware); write(path.join(projectDir, "src", "store", "store.js"), storeContent.trim()); write(path.join(projectDir, "src", "store", "counterSlice.js"), T.COUNTER_SLICE_JS); if (answers.rtkQuery) write(path.join(projectDir, "src", "store", "api.js"), T.RTK_API_JS); } // Zustand if (answers.zustand) { run("npm", ["i", "zustand"], projectDir); ensure(path.join(projectDir, "src", "stores")); const storeExt = answers.ts ? "ts" : "js"; const zustand = answers.ts ? ` import { create } from "zustand"; import { devtools, persist, createJSONStorage } from "zustand/middleware"; type Theme = "light" | "dark"; interface AppState { theme: Theme; count: number; setTheme: (t: Theme) => void; inc: () => void; dec: () => void; reset: () => void; } export const useAppStore = create<AppState>()(devtools(persist((set)=>({ theme:"light", count:0, setTheme:(t)=>set({theme:t}), inc:()=>set(s=>({count:s.count+1})), dec:()=>set(s=>({count:s.count-1})), reset:()=>set({count:0}) }),{ name:"app-store", storage: createJSONStorage(()=>localStorage)}))); `.trim() : ` import { create } from "zustand"; import { devtools, persist, createJSONStorage } from "zustand/middleware"; export const useAppStore = create()(devtools(persist((set)=>({ theme:"light", count:0, setTheme:(t)=>set({theme:t}), inc:()=>set(s=>({count:s.count+1})), dec:()=>set(s=>({count:s.count-1})), reset:()=>set({count:0}) }),{ name:"app-store", storage: createJSONStorage(()=>localStorage)}))); `.trim(); write(path.join(projectDir, "src", "stores", `useAppStore.${storeExt}`), zustand); } // Prettier if (answers.pt) { run("npm", ["i", "-D", "prettier", "prettier-plugin-tailwindcss"], projectDir); write(path.join(projectDir, ".prettierrc"), JSON.stringify({ plugins: ["prettier-plugin-tailwindcss"] }, null, 2)); write(path.join(projectDir, ".prettierignore"), "node_modules\ndist\n.next\nbuild\n"); } // shadcn if (answers.ui === "shadcn") setupShadcn(projectDir, { isVite: true }); // demos ensure(path.join(projectDir, "src", "components", "demo")); if (answers.redux) write(path.join(projectDir, "src", "components", "demo", "ReduxDemo.jsx"), T.REDUX_DEMO_JS); if (answers.rtkQuery) write(path.join(projectDir, "src", "components", "demo", "RTKQueryDemo.jsx"), T.RTK_QUERY_DEMO_JS); if (answers.reactQuery) write(path.join(projectDir, "src", "components", "demo", "ReactQueryDemo.jsx"), T.REACT_QUERY_DEMO_JS); if (answers.swr) write(path.join(projectDir, "src", "components", "demo", "SWRDemo.jsx"), T.SWR_DEMO_JS); if (answers.router) write(path.join(projectDir, "src", "components", "demo", "RouterDemo.jsx"), T.ROUTER_DEMO_JS); if (answers.context) write(path.join(projectDir, "src", "components", "demo", "ContextDemo.jsx"), T.CONTEXT_DEMO_JS); write(path.join(projectDir, "src", "components", "demo", "UseStateDemo.jsx"), T.USESTATE_DEMO_JS); if (answers.framer) { run("npm", ["i", "framer-motion"], projectDir); write(path.join(projectDir, "src", "components", "demo", `FramerDemo.${answers.ts ? "tsx" : "jsx"}`), T.FRAMER_DEMO_REACT); } if (answers.gsap) { run("npm", ["i", "gsap"], projectDir); write(path.join(projectDir, "src", "components", "demo", `GsapDemo.${answers.ts ? "tsx" : "jsx"}`), answers.ts ? T.GSAP_DEMO_REACT_TS : T.GSAP_DEMO_REACT_JS); } } module.exports = { scaffoldReact };