From 97dc7ee8a57fd4494228bca99248c10391515183 Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 7 Aug 2026 21:43:25 +0800 Subject: [PATCH] feat(create-rstack): add React app templates --- packages/create-rstack/README.md | 10 ++- packages/create-rstack/src/index.ts | 51 +++++++---- .../AGENTS.md | 0 .../template-app-react-js/package.json | 19 ++++ .../template-app-react-js/rstack.config.js | 11 +++ .../src/App.css} | 0 .../template-app-react-js/src/App.jsx | 12 +++ .../template-app-react-js/src/index.jsx | 10 +++ .../AGENTS.md | 0 .../template-app-react-ts/package.json | 23 +++++ .../template-app-react-ts/rstack.config.ts | 10 +++ .../src/App.css} | 0 .../template-app-react-ts/src/App.tsx | 12 +++ .../template-app-react-ts/src/env.d.ts | 9 ++ .../template-app-react-ts/src/index.tsx | 13 +++ .../template-app-react-ts/tsconfig.json | 23 +++++ .../template-app-vanilla-js/AGENTS.md | 12 +++ .../package.json | 2 +- .../rstack.config.js | 0 .../template-app-vanilla-js/src/index.css | 26 ++++++ .../src/index.js | 2 +- .../template-app-vanilla-ts/AGENTS.md | 12 +++ .../package.json | 2 +- .../rstack.config.ts | 0 .../template-app-vanilla-ts/src/index.css | 26 ++++++ .../src/index.ts | 2 +- .../tsconfig.json | 0 packages/create-rstack/tests/create.test.ts | 86 +++++++++++-------- 28 files changed, 309 insertions(+), 64 deletions(-) rename packages/create-rstack/{template-app-js => template-app-react-js}/AGENTS.md (100%) create mode 100644 packages/create-rstack/template-app-react-js/package.json create mode 100644 packages/create-rstack/template-app-react-js/rstack.config.js rename packages/create-rstack/{template-app-js/src/index.css => template-app-react-js/src/App.css} (100%) create mode 100644 packages/create-rstack/template-app-react-js/src/App.jsx create mode 100644 packages/create-rstack/template-app-react-js/src/index.jsx rename packages/create-rstack/{template-app-ts => template-app-react-ts}/AGENTS.md (100%) create mode 100644 packages/create-rstack/template-app-react-ts/package.json create mode 100644 packages/create-rstack/template-app-react-ts/rstack.config.ts rename packages/create-rstack/{template-app-ts/src/index.css => template-app-react-ts/src/App.css} (100%) create mode 100644 packages/create-rstack/template-app-react-ts/src/App.tsx create mode 100644 packages/create-rstack/template-app-react-ts/src/env.d.ts create mode 100644 packages/create-rstack/template-app-react-ts/src/index.tsx create mode 100644 packages/create-rstack/template-app-react-ts/tsconfig.json create mode 100644 packages/create-rstack/template-app-vanilla-js/AGENTS.md rename packages/create-rstack/{template-app-js => template-app-vanilla-js}/package.json (86%) rename packages/create-rstack/{template-app-js => template-app-vanilla-js}/rstack.config.js (100%) create mode 100644 packages/create-rstack/template-app-vanilla-js/src/index.css rename packages/create-rstack/{template-app-js => template-app-vanilla-js}/src/index.js (88%) create mode 100644 packages/create-rstack/template-app-vanilla-ts/AGENTS.md rename packages/create-rstack/{template-app-ts => template-app-vanilla-ts}/package.json (88%) rename packages/create-rstack/{template-app-ts => template-app-vanilla-ts}/rstack.config.ts (100%) create mode 100644 packages/create-rstack/template-app-vanilla-ts/src/index.css rename packages/create-rstack/{template-app-ts => template-app-vanilla-ts}/src/index.ts (88%) rename packages/create-rstack/{template-app-ts => template-app-vanilla-ts}/tsconfig.json (100%) diff --git a/packages/create-rstack/README.md b/packages/create-rstack/README.md index 6b2da8f1..453221e0 100644 --- a/packages/create-rstack/README.md +++ b/packages/create-rstack/README.md @@ -13,16 +13,18 @@ npm create rstack@latest Using CLI flags: ```bash -npx create-rstack --dir my-project --template app-ts +npx create-rstack --dir my-project --template app-vanilla-ts # Using abbreviations -npx create-rstack -d my-project -t app-ts +npx create-rstack -d my-project -t app-vanilla-ts ``` ## Templates -- `app-js` - JavaScript application -- `app-ts` - TypeScript application +- `app-vanilla-js` - JavaScript Vanilla application +- `app-vanilla-ts` - TypeScript Vanilla application +- `app-react-js` - JavaScript React application +- `app-react-ts` - TypeScript React application - `lib-js` - JavaScript Node.js library - `lib-ts` - TypeScript Node.js library diff --git a/packages/create-rstack/src/index.ts b/packages/create-rstack/src/index.ts index 9f7b851e..8590df3f 100644 --- a/packages/create-rstack/src/index.ts +++ b/packages/create-rstack/src/index.ts @@ -1,15 +1,18 @@ -import { - type Argv, - checkCancel, - create, - type ESLintTemplateName, - type RslintTemplateName, - select, -} from '@rstackjs/create-toolkit'; +import { type Argv, checkCancel, create, select } from '@rstackjs/create-toolkit'; import path from 'node:path'; const getTemplateName = async ({ template }: Argv): Promise => { if (typeof template === 'string') { + if (template === 'app' || template.startsWith('app-')) { + const [, framework = 'vanilla', language = 'js'] = template.split('-'); + + if (framework === 'js' || framework === 'ts') { + return `app-vanilla-${framework}`; + } + + return `app-${framework}-${language}`; + } + const [type, language = 'js'] = template.split('-'); return `${type}-${language}`; } @@ -24,6 +27,19 @@ const getTemplateName = async ({ template }: Argv): Promise => { }), ); + const framework = + projectType === 'app' + ? checkCancel( + await select({ + message: 'Select framework', + options: [ + { value: 'vanilla', label: 'Vanilla' }, + { value: 'react', label: 'React' }, + ], + }), + ) + : undefined; + const language = checkCancel( await select({ message: 'Select language', @@ -34,21 +50,20 @@ const getTemplateName = async ({ template }: Argv): Promise => { }), ); - return `${projectType}-${language}`; + return framework ? `${projectType}-${framework}-${language}` : `${projectType}-${language}`; }; -const mapESLintTemplate = (templateName: string): ESLintTemplateName => - templateName.endsWith('-ts') ? 'vanilla-ts' : 'vanilla-js'; - -const mapRslintTemplate = (templateName: string): RslintTemplateName => - templateName.endsWith('-ts') ? 'vanilla-ts' : 'vanilla-js'; - await create({ root: path.join(import.meta.dirname, '..'), name: 'rstack', - templates: ['app-js', 'app-ts', 'lib-js', 'lib-ts'], + templates: [ + 'app-vanilla-js', + 'app-vanilla-ts', + 'app-react-js', + 'app-react-ts', + 'lib-js', + 'lib-ts', + ], builtinTools: [], getTemplateName, - mapESLintTemplate, - mapRslintTemplate, }); diff --git a/packages/create-rstack/template-app-js/AGENTS.md b/packages/create-rstack/template-app-react-js/AGENTS.md similarity index 100% rename from packages/create-rstack/template-app-js/AGENTS.md rename to packages/create-rstack/template-app-react-js/AGENTS.md diff --git a/packages/create-rstack/template-app-react-js/package.json b/packages/create-rstack/template-app-react-js/package.json new file mode 100644 index 00000000..399515d4 --- /dev/null +++ b/packages/create-rstack/template-app-react-js/package.json @@ -0,0 +1,19 @@ +{ + "name": "rstack-app-react-js", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "rs build", + "dev": "rs dev --open", + "preview": "rs preview" + }, + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "devDependencies": { + "@rsbuild/plugin-react": "^2.1.0", + "rstack": "^0.3.5" + } +} diff --git a/packages/create-rstack/template-app-react-js/rstack.config.js b/packages/create-rstack/template-app-react-js/rstack.config.js new file mode 100644 index 00000000..370a7f9e --- /dev/null +++ b/packages/create-rstack/template-app-react-js/rstack.config.js @@ -0,0 +1,11 @@ +// @ts-check +// Rstack configuration guide: https://rstack.rs/config +import { define } from 'rstack'; + +define.app(async () => { + const { pluginReact } = await import('@rsbuild/plugin-react'); + + return { + plugins: [pluginReact()], + }; +}); diff --git a/packages/create-rstack/template-app-js/src/index.css b/packages/create-rstack/template-app-react-js/src/App.css similarity index 100% rename from packages/create-rstack/template-app-js/src/index.css rename to packages/create-rstack/template-app-react-js/src/App.css diff --git a/packages/create-rstack/template-app-react-js/src/App.jsx b/packages/create-rstack/template-app-react-js/src/App.jsx new file mode 100644 index 00000000..c23095d2 --- /dev/null +++ b/packages/create-rstack/template-app-react-js/src/App.jsx @@ -0,0 +1,12 @@ +import './App.css'; + +const App = () => { + return ( +
+

Rstack with React

+

Start building amazing things with Rstack.

+
+ ); +}; + +export default App; diff --git a/packages/create-rstack/template-app-react-js/src/index.jsx b/packages/create-rstack/template-app-react-js/src/index.jsx new file mode 100644 index 00000000..65a8dbf4 --- /dev/null +++ b/packages/create-rstack/template-app-react-js/src/index.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + , +); diff --git a/packages/create-rstack/template-app-ts/AGENTS.md b/packages/create-rstack/template-app-react-ts/AGENTS.md similarity index 100% rename from packages/create-rstack/template-app-ts/AGENTS.md rename to packages/create-rstack/template-app-react-ts/AGENTS.md diff --git a/packages/create-rstack/template-app-react-ts/package.json b/packages/create-rstack/template-app-react-ts/package.json new file mode 100644 index 00000000..6e130b05 --- /dev/null +++ b/packages/create-rstack/template-app-react-ts/package.json @@ -0,0 +1,23 @@ +{ + "name": "rstack-app-react-ts", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "rs build", + "dev": "rs dev --open", + "preview": "rs preview" + }, + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "devDependencies": { + "@rsbuild/plugin-react": "^2.1.0", + "@types/node": "^24.13.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "rstack": "^0.3.5", + "typescript": "^7.0.2" + } +} diff --git a/packages/create-rstack/template-app-react-ts/rstack.config.ts b/packages/create-rstack/template-app-react-ts/rstack.config.ts new file mode 100644 index 00000000..698eb199 --- /dev/null +++ b/packages/create-rstack/template-app-react-ts/rstack.config.ts @@ -0,0 +1,10 @@ +// Rstack configuration guide: https://rstack.rs/config +import { define } from 'rstack'; + +define.app(async () => { + const { pluginReact } = await import('@rsbuild/plugin-react'); + + return { + plugins: [pluginReact()], + }; +}); diff --git a/packages/create-rstack/template-app-ts/src/index.css b/packages/create-rstack/template-app-react-ts/src/App.css similarity index 100% rename from packages/create-rstack/template-app-ts/src/index.css rename to packages/create-rstack/template-app-react-ts/src/App.css diff --git a/packages/create-rstack/template-app-react-ts/src/App.tsx b/packages/create-rstack/template-app-react-ts/src/App.tsx new file mode 100644 index 00000000..c23095d2 --- /dev/null +++ b/packages/create-rstack/template-app-react-ts/src/App.tsx @@ -0,0 +1,12 @@ +import './App.css'; + +const App = () => { + return ( +
+

Rstack with React

+

Start building amazing things with Rstack.

+
+ ); +}; + +export default App; diff --git a/packages/create-rstack/template-app-react-ts/src/env.d.ts b/packages/create-rstack/template-app-react-ts/src/env.d.ts new file mode 100644 index 00000000..011b5e7b --- /dev/null +++ b/packages/create-rstack/template-app-react-ts/src/env.d.ts @@ -0,0 +1,9 @@ +/** + * Imports the SVG file as a React component. + * @requires [@rsbuild/plugin-svgr](https://npmjs.com/package/@rsbuild/plugin-svgr) + */ +declare module '*.svg?react' { + import type React from 'react'; + const ReactComponent: React.FunctionComponent>; + export default ReactComponent; +} diff --git a/packages/create-rstack/template-app-react-ts/src/index.tsx b/packages/create-rstack/template-app-react-ts/src/index.tsx new file mode 100644 index 00000000..55f29bff --- /dev/null +++ b/packages/create-rstack/template-app-react-ts/src/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const rootEl = document.getElementById('root'); +if (rootEl) { + const root = ReactDOM.createRoot(rootEl); + root.render( + + + , + ); +} diff --git a/packages/create-rstack/template-app-react-ts/tsconfig.json b/packages/create-rstack/template-app-react-ts/tsconfig.json new file mode 100644 index 00000000..dd3c6e37 --- /dev/null +++ b/packages/create-rstack/template-app-react-ts/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "lib": ["DOM", "ES2020"], + "jsx": "react-jsx", + "target": "ES2020", + "noEmit": true, + "skipLibCheck": true, + "types": ["rstack/types", "node"], + "useDefineForClassFields": true, + + /* modules */ + "moduleDetection": "force", + "moduleResolution": "bundler", + "verbatimModuleSyntax": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + + /* type checking */ + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["src"] +} diff --git a/packages/create-rstack/template-app-vanilla-js/AGENTS.md b/packages/create-rstack/template-app-vanilla-js/AGENTS.md new file mode 100644 index 00000000..fb378511 --- /dev/null +++ b/packages/create-rstack/template-app-vanilla-js/AGENTS.md @@ -0,0 +1,12 @@ +# AGENTS.md + +## Commands + +- `{{ packageManager }} run dev` - Start the development server +- `{{ packageManager }} run build` - Build the app for production +- `{{ packageManager }} run preview` - Preview the production build locally + +## Docs + +- Rsbuild: https://rsbuild.rs/llms.txt +- Rspack: https://rspack.rs/llms.txt diff --git a/packages/create-rstack/template-app-js/package.json b/packages/create-rstack/template-app-vanilla-js/package.json similarity index 86% rename from packages/create-rstack/template-app-js/package.json rename to packages/create-rstack/template-app-vanilla-js/package.json index 0247d794..6ca855dd 100644 --- a/packages/create-rstack/template-app-js/package.json +++ b/packages/create-rstack/template-app-vanilla-js/package.json @@ -1,5 +1,5 @@ { - "name": "rstack-app-js", + "name": "rstack-app-vanilla-js", "version": "1.0.0", "private": true, "type": "module", diff --git a/packages/create-rstack/template-app-js/rstack.config.js b/packages/create-rstack/template-app-vanilla-js/rstack.config.js similarity index 100% rename from packages/create-rstack/template-app-js/rstack.config.js rename to packages/create-rstack/template-app-vanilla-js/rstack.config.js diff --git a/packages/create-rstack/template-app-vanilla-js/src/index.css b/packages/create-rstack/template-app-vanilla-js/src/index.css new file mode 100644 index 00000000..164c0a6a --- /dev/null +++ b/packages/create-rstack/template-app-vanilla-js/src/index.css @@ -0,0 +1,26 @@ +body { + margin: 0; + color: #fff; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + background-image: linear-gradient(to bottom, #020917, #101725); +} + +.content { + display: flex; + min-height: 100vh; + line-height: 1.1; + text-align: center; + flex-direction: column; + justify-content: center; +} + +.content h1 { + font-size: 3.6rem; + font-weight: 700; +} + +.content p { + font-size: 1.2rem; + font-weight: 400; + opacity: 0.5; +} diff --git a/packages/create-rstack/template-app-js/src/index.js b/packages/create-rstack/template-app-vanilla-js/src/index.js similarity index 88% rename from packages/create-rstack/template-app-js/src/index.js rename to packages/create-rstack/template-app-vanilla-js/src/index.js index a1546df9..5d84d19d 100644 --- a/packages/create-rstack/template-app-js/src/index.js +++ b/packages/create-rstack/template-app-vanilla-js/src/index.js @@ -5,7 +5,7 @@ const rootElement = document.querySelector('#root'); if (rootElement) { rootElement.innerHTML = `
-

Rstack

+

Vanilla Rstack

Start building amazing things with Rstack.

`; diff --git a/packages/create-rstack/template-app-vanilla-ts/AGENTS.md b/packages/create-rstack/template-app-vanilla-ts/AGENTS.md new file mode 100644 index 00000000..fb378511 --- /dev/null +++ b/packages/create-rstack/template-app-vanilla-ts/AGENTS.md @@ -0,0 +1,12 @@ +# AGENTS.md + +## Commands + +- `{{ packageManager }} run dev` - Start the development server +- `{{ packageManager }} run build` - Build the app for production +- `{{ packageManager }} run preview` - Preview the production build locally + +## Docs + +- Rsbuild: https://rsbuild.rs/llms.txt +- Rspack: https://rspack.rs/llms.txt diff --git a/packages/create-rstack/template-app-ts/package.json b/packages/create-rstack/template-app-vanilla-ts/package.json similarity index 88% rename from packages/create-rstack/template-app-ts/package.json rename to packages/create-rstack/template-app-vanilla-ts/package.json index bd79bc97..2b1fc111 100644 --- a/packages/create-rstack/template-app-ts/package.json +++ b/packages/create-rstack/template-app-vanilla-ts/package.json @@ -1,5 +1,5 @@ { - "name": "rstack-app-ts", + "name": "rstack-app-vanilla-ts", "version": "1.0.0", "private": true, "type": "module", diff --git a/packages/create-rstack/template-app-ts/rstack.config.ts b/packages/create-rstack/template-app-vanilla-ts/rstack.config.ts similarity index 100% rename from packages/create-rstack/template-app-ts/rstack.config.ts rename to packages/create-rstack/template-app-vanilla-ts/rstack.config.ts diff --git a/packages/create-rstack/template-app-vanilla-ts/src/index.css b/packages/create-rstack/template-app-vanilla-ts/src/index.css new file mode 100644 index 00000000..164c0a6a --- /dev/null +++ b/packages/create-rstack/template-app-vanilla-ts/src/index.css @@ -0,0 +1,26 @@ +body { + margin: 0; + color: #fff; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + background-image: linear-gradient(to bottom, #020917, #101725); +} + +.content { + display: flex; + min-height: 100vh; + line-height: 1.1; + text-align: center; + flex-direction: column; + justify-content: center; +} + +.content h1 { + font-size: 3.6rem; + font-weight: 700; +} + +.content p { + font-size: 1.2rem; + font-weight: 400; + opacity: 0.5; +} diff --git a/packages/create-rstack/template-app-ts/src/index.ts b/packages/create-rstack/template-app-vanilla-ts/src/index.ts similarity index 88% rename from packages/create-rstack/template-app-ts/src/index.ts rename to packages/create-rstack/template-app-vanilla-ts/src/index.ts index a1546df9..5d84d19d 100644 --- a/packages/create-rstack/template-app-ts/src/index.ts +++ b/packages/create-rstack/template-app-vanilla-ts/src/index.ts @@ -5,7 +5,7 @@ const rootElement = document.querySelector('#root'); if (rootElement) { rootElement.innerHTML = `
-

Rstack

+

Vanilla Rstack

Start building amazing things with Rstack.

`; diff --git a/packages/create-rstack/template-app-ts/tsconfig.json b/packages/create-rstack/template-app-vanilla-ts/tsconfig.json similarity index 100% rename from packages/create-rstack/template-app-ts/tsconfig.json rename to packages/create-rstack/template-app-vanilla-ts/tsconfig.json diff --git a/packages/create-rstack/tests/create.test.ts b/packages/create-rstack/tests/create.test.ts index 0d19f3d2..ae8479f6 100644 --- a/packages/create-rstack/tests/create.test.ts +++ b/packages/create-rstack/tests/create.test.ts @@ -33,47 +33,57 @@ const createProject = async (template: string) => { }; test.each([ - { template: 'app-js', extension: 'js', hasTypeScript: false }, - { template: 'app-ts', extension: 'ts', hasTypeScript: true }, -])('creates the $template template', async ({ template, extension, hasTypeScript }) => { - const projectDirectory = await createProject(template); - const packageJson = JSON.parse( - await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), - ); + { + template: 'app-vanilla-js', + configExtension: 'js', + sourceExtension: 'js', + hasTypeScript: false, + }, + { + template: 'app-vanilla-ts', + configExtension: 'ts', + sourceExtension: 'ts', + hasTypeScript: true, + }, + { + template: 'app-react-js', + configExtension: 'js', + sourceExtension: 'jsx', + hasTypeScript: false, + }, + { + template: 'app-react-ts', + configExtension: 'ts', + sourceExtension: 'tsx', + hasTypeScript: true, + }, +])( + 'creates the $template template', + async ({ template, configExtension, sourceExtension, hasTypeScript }) => { + const projectDirectory = await createProject(template); + const packageJson = JSON.parse( + await readFile(path.join(projectDirectory, 'package.json'), 'utf8'), + ); - expect(packageJson).toMatchObject({ - name: 'my-app', - private: true, - scripts: { - build: 'rs build', - dev: 'rs dev --open', - preview: 'rs preview', - }, - devDependencies: { - rstack: '^0.3.5', - }, - }); + expect(packageJson.name).toBe('my-app'); - await expect(access(path.join(projectDirectory, 'README.md'))).resolves.toBeUndefined(); - await expect(access(path.join(projectDirectory, '.gitignore'))).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, `rstack.config.${extension}`)), - ).resolves.toBeUndefined(); - await expect( - access(path.join(projectDirectory, `src/index.${extension}`)), - ).resolves.toBeUndefined(); + await expect(access(path.join(projectDirectory, 'README.md'))).resolves.toBeUndefined(); + await expect(access(path.join(projectDirectory, '.gitignore'))).resolves.toBeUndefined(); + await expect( + access(path.join(projectDirectory, `rstack.config.${configExtension}`)), + ).resolves.toBeUndefined(); + await expect( + access(path.join(projectDirectory, `src/index.${sourceExtension}`)), + ).resolves.toBeUndefined(); - const tsconfigPath = path.join(projectDirectory, 'tsconfig.json'); - if (hasTypeScript) { - await expect(access(tsconfigPath)).resolves.toBeUndefined(); - expect(packageJson.devDependencies).toMatchObject({ - '@types/node': '^24.13.3', - typescript: '^7.0.2', - }); - } else { - await expect(access(tsconfigPath)).rejects.toThrow(); - } -}); + const tsconfigPath = path.join(projectDirectory, 'tsconfig.json'); + if (hasTypeScript) { + await expect(access(tsconfigPath)).resolves.toBeUndefined(); + } else { + await expect(access(tsconfigPath)).rejects.toThrow(); + } + }, +); test.each([ { template: 'lib-js', extension: 'js', hasTypeScript: false },