diff --git a/.changeset/rsbuild-nitro-deployments.md b/.changeset/rsbuild-nitro-deployments.md new file mode 100644 index 00000000..39c7eefc --- /dev/null +++ b/.changeset/rsbuild-nitro-deployments.md @@ -0,0 +1,10 @@ +--- +'@tanstack/create': minor +'@tanstack/cli': minor +--- + +Support Nitro, Railway, Render, Vercel, and Netlify deployment adapters in React and Solid Rsbuild Start projects using Nitro's experimental Rsbuild integration. Rsbuild deployment projects require Node.js >=22.22 and Rsbuild >=2.3. Cloudflare remains Vite-only until its Nitro development runtime is compatible with Workerd. + +Use Nitro 3.0.261103-rc for both Vite and Rsbuild deployment adapters. All projects using this Nitro release require Node.js >=22.22. + +Preserve all engine requirements when merging package overlays so subsequent add-ons cannot lower a deployment adapter's Node.js minimum. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 06e85322..459dde8a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -117,6 +117,7 @@ Templates (`.ejs` files) have access to: | `provider` | Wraps app in `__root.tsx` | Basic context providers | | `root-provider` | Wraps app, exports context | Providers with shared state | | `vite-plugin` | `vite.config.ts` | Vite plugins | +| `rsbuild-plugin` | `rsbuild.config.ts` | Rsbuild deployment adapter plugins | | `devtools` | After app in `__root.tsx` | Developer tools | | `header-user` | Header component | Auth UI, user menus | | `layout` | Layout wrapper | Dashboard layouts | diff --git a/docs/cli-reference.md b/docs/cli-reference.md index fdc1a3ae..4d9d185a 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -43,6 +43,7 @@ tanstack create my-app --blank --deployment cloudflare -y tanstack create my-app --add-ons clerk,drizzle,tanstack-query tanstack create my-app --router-only --toolchain eslint --no-examples tanstack create my-app --build-tool rsbuild -y +tanstack create my-app --build-tool rsbuild --deployment nitro -y tanstack create my-app --framework Solid --build-tool rsbuild --router-only --blank --toolchain biome -y tanstack create my-app --template https://example.com/template.json tanstack create my-app --template ecommerce @@ -58,6 +59,12 @@ Explicit add-ons and deployment adapters can add their own required files and dependencies. Add `-y` to use defaults for every remaining option; when the target is non-empty, also pass `--force` or the command exits without writing. +Rsbuild Start projects support five deployment adapters through Nitro's +experimental Rsbuild integration: `nitro`, `railway`, `render`, `vercel`, +and `netlify`. Cloudflare remains Vite-only until Nitro's Rsbuild development +runtime works with Workerd. Generated deployment projects require Node.js +>=22.22 and Rsbuild >=2.3. Router-only projects do not support deployment adapters. + ### Programmatic generation Use `@tanstack/create/worker` in Cloudflare Workers and other edge SSR runtimes. It does not import the generated template manifest at module startup. Instead, provide a loader for the framework and add-on chunks your Worker supports. diff --git a/packages/cli/skills/create-app-scaffold/references/bundlers.md b/packages/cli/skills/create-app-scaffold/references/bundlers.md index 15bbedb7..941dca43 100644 --- a/packages/cli/skills/create-app-scaffold/references/bundlers.md +++ b/packages/cli/skills/create-app-scaffold/references/bundlers.md @@ -15,10 +15,21 @@ npx @tanstack/cli create app --framework solid --build-tool rsbuild -y ``` Rsbuild supports full TanStack Start and router-only projects, including the -standard and `--blank` presets, built-in example pages, Tailwind, ESLint, and -Biome. It does not currently support templates, deployment adapters, or -catalog business/example add-ons. `tanstack add` is limited to ESLint/Biome in -Rsbuild projects. +standard and `--blank` presets, built-in example pages, Tailwind, ESLint, +Biome, and Prettier. Templates and catalog business/example add-ons are not +supported. + +Full Start projects support `--deployment nitro`, `railway`, `render`, +`vercel`, and `netlify` through the experimental `nitro/rsbuild` +integration. These projects require Node.js >=22.22 and Rsbuild >=2.3. +Cloudflare remains Vite-only until Nitro's Rsbuild development runtime works +with Workerd. +Router-only projects do not support deployment adapters. Existing Rsbuild +Start projects can add one deployment adapter with `tanstack add`. + +```bash +npx @tanstack/cli create app --build-tool rsbuild --deployment nitro -y +``` Use the build-tool-aware catalog view before constructing an Rsbuild command: diff --git a/packages/cli/skills/create-app-scaffold/references/deployment-providers.md b/packages/cli/skills/create-app-scaffold/references/deployment-providers.md index f283df8e..f2fca644 100644 --- a/packages/cli/skills/create-app-scaffold/references/deployment-providers.md +++ b/packages/cli/skills/create-app-scaffold/references/deployment-providers.md @@ -20,3 +20,12 @@ npx @tanstack/cli create app --deployment vercel -y ``` Deployment providers are exclusive; choose one per scaffold. + +## Rsbuild + +The `nitro`, `railway`, `render`, `vercel`, and `netlify` deployment ids are +available for full TanStack Start projects with `--build-tool rsbuild`. +Cloudflare remains Vite-only until Nitro's Rsbuild development runtime works +with Workerd. Supported Rsbuild adapters use Nitro's experimental integration +and require Node.js >=22.22 and Rsbuild >=2.3. Router-only projects do not +support deployment adapters. diff --git a/packages/cli/src/command-line.ts b/packages/cli/src/command-line.ts index a61c7587..7f5ac959 100644 --- a/packages/cli/src/command-line.ts +++ b/packages/cli/src/command-line.ts @@ -337,17 +337,11 @@ export function validateLegacyCreateFlags(cliOptions: CliOptions): { 'Rsbuild does not currently support --starter, --template, or --template-id.', } } - if (cliOptions.deployment) { - return { - warnings, - error: 'Rsbuild does not currently support --deployment.', - } - } if (cliOptions.addOns) { return { warnings, error: - 'Rsbuild currently supports toolchains only. Use --toolchain eslint or --toolchain biome.', + 'Rsbuild does not support --add-ons. Use --toolchain or --deployment.', } } } @@ -524,12 +518,9 @@ export async function normalizeOptions( ) { throw new Error('Rsbuild does not currently support templates.') } - if (cliOptions.deployment || opts?.forcedDeployment) { - throw new Error('Rsbuild does not currently support deployments.') - } if (forcedAddOns?.length || cliOptions.addOns) { throw new Error( - 'Rsbuild currently supports toolchains only. Use --toolchain eslint or --toolchain biome.', + 'Rsbuild does not support --add-ons. Use --toolchain or --deployment.', ) } } diff --git a/packages/cli/src/options.ts b/packages/cli/src/options.ts index a12a2428..223b7352 100644 --- a/packages/cli/src/options.ts +++ b/packages/cli/src/options.ts @@ -89,9 +89,6 @@ export async function promptForCreateOptions( if (forcedAddOns.length > 0) { throw new Error('Rsbuild does not currently support forced add-ons.') } - if (forcedDeployment) { - throw new Error('Rsbuild does not currently support deployments.') - } } const projectLocation = resolveProjectLocation({ @@ -178,7 +175,7 @@ export async function promptForCreateOptions( // Deployment selection let deployment: string | undefined - if (routerOnly || options.bundler === 'rsbuild') { + if (routerOnly) { deployment = undefined } else if (cliOptions.deployment) { deployment = cliOptions.deployment @@ -189,6 +186,7 @@ export async function promptForCreateOptions( options.framework, cliOptions.deployment, forcedDeployment, + options.bundler, ) } else { deployment = forcedDeployment @@ -319,18 +317,24 @@ export async function promptForAddOns(): Promise> { const bundler = resolveBundler(framework, config.bundler).id if (bundler === 'rsbuild') { - const toolchains = getAllAddOns(framework, config.mode!, bundler).filter( - (addOn) => addOn.type === 'toolchain', + const supportedAddOns = getAllAddOns( + framework, + config.mode!, + bundler, + ).filter( + (addOn) => + addOn.type === 'toolchain' || + (!config.routerOnly && addOn.type === 'deployment'), ) const configuredAddOnIds = new Set( config.chosenAddOns.map((addOn) => addOn.toLowerCase()), ) const configuredExclusiveGroups = new Set( - toolchains + supportedAddOns .filter((addOn) => configuredAddOnIds.has(addOn.id.toLowerCase())) .flatMap((addOn) => addOn.exclusive ?? []), ) - const unavailableToolchains = toolchains + const unavailableAddOns = supportedAddOns .filter( (addOn) => configuredAddOnIds.has(addOn.id.toLowerCase()) || @@ -340,20 +344,28 @@ export async function promptForAddOns(): Promise> { ) .map((addOn) => addOn.id) - if (unavailableToolchains.length === toolchains.length) { - return [] - } - - for (const addOn of await selectAddOns( - framework, - config.mode!, - 'toolchain', - 'Which toolchain would you like to add?', - unavailableToolchains, - false, - bundler, - )) { - addOns.add(addOn) + for (const type of ['toolchain', 'deployment']) { + if ( + !supportedAddOns.some( + (addOn) => + addOn.type === type && !unavailableAddOns.includes(addOn.id), + ) + ) { + continue + } + for (const addOn of await selectAddOns( + framework, + config.mode!, + type, + type === 'toolchain' + ? 'Which toolchain would you like to add?' + : 'Which deployment adapter would you like to add?', + unavailableAddOns, + false, + bundler, + )) { + addOns.add(addOn) + } } return Array.from(addOns) } diff --git a/packages/cli/src/ui-prompts.ts b/packages/cli/src/ui-prompts.ts index 95857fd9..ffed0753 100644 --- a/packages/cli/src/ui-prompts.ts +++ b/packages/cli/src/ui-prompts.ts @@ -415,12 +415,12 @@ export async function selectDeployment( framework: Framework, deployment?: string, forcedDeployment?: string, + bundlerId?: string, ): Promise { const deployments = new Set() let initialValue: string | undefined = undefined for (const addOn of orderAddOnsForPartnerPlacement( - framework - .getAddOns() + getAllAddOns(framework, 'file-router', bundlerId) .filter((addOn) => addOn.type === 'deployment') .sort((a, b) => a.name.localeCompare(b.name)), 'deployment', diff --git a/packages/cli/tests-e2e/rsbuild-smoke.spec.ts b/packages/cli/tests-e2e/rsbuild-smoke.spec.ts index bc983a0f..990c7120 100644 --- a/packages/cli/tests-e2e/rsbuild-smoke.spec.ts +++ b/packages/cli/tests-e2e/rsbuild-smoke.spec.ts @@ -15,14 +15,21 @@ import { const execFileAsync = promisify(execFile) -const scenarios = [ +const scenarios: Array<{ + framework: 'react' | 'solid' + routerOnly: boolean + blank: boolean + deployment?: string +}> = [ { framework: 'react', routerOnly: false, blank: false }, { framework: 'solid', routerOnly: false, blank: false }, { framework: 'react', routerOnly: true, blank: true }, { framework: 'solid', routerOnly: true, blank: true }, -] as const + { framework: 'react', routerOnly: false, blank: false, deployment: 'nitro' }, + { framework: 'solid', routerOnly: false, blank: false, deployment: 'nitro' }, +] -test('@matrix lists only toolchains for Rsbuild', async () => { +test('@matrix lists toolchains and deployment adapters for Rsbuild', async () => { const { stdout } = await execFileAsync( process.execPath, [ @@ -40,10 +47,14 @@ test('@matrix lists only toolchains for Rsbuild', async () => { const addOns = JSON.parse(stdout) as Array<{ type: string }> expect(addOns.length).toBeGreaterThan(0) - expect(addOns.every((addOn) => addOn.type === 'toolchain')).toBe(true) + expect( + addOns.every((addOn) => ['toolchain', 'deployment'].includes(addOn.type)), + ).toBe(true) + expect(addOns.filter((addOn) => addOn.type === 'deployment')).toHaveLength(5) + expect(addOns).not.toContainEqual(expect.objectContaining({ id: 'cloudflare' })) }) -test('@matrix rejects an Rsbuild deployment before creating a directory', async () => { +test('@matrix rejects an Rsbuild business add-on before creating a directory', async () => { const rootDir = await mkdtemp(join(tmpdir(), 'tanstack-rsbuild-conflict-')) const appName = 'invalid-rsbuild-app' @@ -58,8 +69,8 @@ test('@matrix rejects an Rsbuild deployment before creating a directory', async appName, '--build-tool', 'rsbuild', - '--deployment', - 'cloudflare', + '--add-ons', + 'shadcn', '--yes', '--no-install', '--no-git', @@ -75,7 +86,7 @@ test('@matrix rejects an Rsbuild deployment before creating a directory', async | undefined expect( `${commandError?.stdout ?? ''}\n${commandError?.stderr ?? ''}`, - ).toContain('Rsbuild does not currently support --deployment') + ).toContain('Rsbuild does not support --add-ons') await expect(access(join(rootDir, appName))).rejects.toThrow() } finally { await rm(rootDir, { recursive: true, force: true }) @@ -84,13 +95,14 @@ test('@matrix rejects an Rsbuild deployment before creating a directory', async test.describe('@matrix Rsbuild smoke matrix', () => { for (const scenario of scenarios) { - test(`@matrix ${scenario.framework} routerOnly=${scenario.routerOnly} blank=${scenario.blank}`, async ({ + test(`@matrix ${scenario.framework} routerOnly=${scenario.routerOnly} blank=${scenario.blank} deployment=${scenario.deployment ?? 'none'}`, async ({ page, }) => { const fixture = await createAppFixture({ - appName: `${scenario.framework}-rsbuild-${scenario.routerOnly ? 'router' : 'start'}-smoke`, + appName: `${scenario.framework}-rsbuild-${scenario.deployment ?? (scenario.routerOnly ? 'router' : 'start')}-smoke`, framework: scenario.framework, bundler: 'rsbuild', + deployment: scenario.deployment, routerOnly: scenario.routerOnly, blank: scenario.blank, packageManager: 'pnpm', diff --git a/packages/cli/tests/command-line.test.ts b/packages/cli/tests/command-line.test.ts index fb20fdae..444579fa 100644 --- a/packages/cli/tests/command-line.test.ts +++ b/packages/cli/tests/command-line.test.ts @@ -691,7 +691,7 @@ describe('normalizeOptions', () => { buildTool: 'rsbuild', addOns: ['form'], }), - ).rejects.toThrow('Rsbuild currently supports toolchains only') + ).rejects.toThrow('Rsbuild does not support --add-ons') }) it('keeps explicitly selected integrations in the blank preset', async () => { @@ -827,8 +827,7 @@ describe('validateLegacyCreateFlags', () => { [{ starter: 'blog' }, '--starter'], [{ template: 'blog' }, '--template'], [{ templateId: 'blog' }, '--template-id'], - [{ deployment: 'cloudflare' }, '--deployment'], - [{ addOns: ['form'] }, 'toolchains only'], + [{ addOns: ['form'] }, 'does not support --add-ons'], ])( 'rejects unsupported Rsbuild combinations containing %s', (flags, text) => { @@ -841,6 +840,15 @@ describe('validateLegacyCreateFlags', () => { }, ) + it.each(['nitro', 'railway', 'render', 'vercel', 'netlify'])( + 'allows Rsbuild with the %s deployment flag', + (deployment) => { + expect( + validateLegacyCreateFlags({ buildTool: 'rsbuild', deployment }).error, + ).toBeUndefined() + }, + ) + it('allows Rsbuild with a toolchain', () => { const result = validateLegacyCreateFlags({ buildTool: 'rsbuild', diff --git a/packages/cli/tests/options.test.ts b/packages/cli/tests/options.test.ts index 35de7586..2bd3b572 100644 --- a/packages/cli/tests/options.test.ts +++ b/packages/cli/tests/options.test.ts @@ -50,6 +50,28 @@ beforeEach(() => { type: 'add-on', modes: ['file-router'], }, + { + id: 'cloudflare', + name: 'Cloudflare', + type: 'deployment', + modes: ['file-router'], + exclusive: ['deploy'], + integrations: [{ type: 'vite-plugin' }], + }, + { + id: 'nitro', + name: 'Nitro', + type: 'deployment', + modes: ['file-router'], + exclusive: ['deploy'], + integrations: [ + { + type: 'rsbuild-plugin', + import: "import { nitro } from 'nitro/rsbuild'", + code: "nitro({ preset: 'node-server' })", + }, + ], + }, { id: 'biome', type: 'toolchain', @@ -232,14 +254,18 @@ describe('promptForCreateOptions', () => { expect(options?.bundler).toBe('rsbuild') expect(options?.chosenAddOns.map((addOn) => addOn.id)).toEqual(['biome']) expect(prompts.selectTemplate).not.toHaveBeenCalled() - expect(prompts.selectDeployment).not.toHaveBeenCalled() + expect(prompts.selectDeployment).toHaveBeenCalledWith( + expect.anything(), + undefined, + undefined, + 'rsbuild', + ) expect(prompts.selectAddOns).not.toHaveBeenCalled() }) it.each([ [{ template: 'blog' }, '--starter, --template, or --template-id'], - [{ deployment: 'cloudflare' }, '--deployment'], - [{ addOns: ['react-query'] }, 'toolchains only'], + [{ addOns: ['react-query'] }, 'does not support --add-ons'], ])( 'rejects explicit %s after Rsbuild is selected interactively', async (flags, error) => { @@ -285,6 +311,42 @@ describe('promptForCreateOptions', () => { }, ) + it.each([false, true])( + 'keeps an Rsbuild deployment (forced=%s)', + async (forced) => { + setBasicSpies() + vi.mocked(prompts.selectBundler).mockResolvedValue('rsbuild') + const options = await promptForCreateOptions( + { + ...baseCliOptions, + blank: true, + deployment: forced ? undefined : 'nitro', + }, + forced ? { forcedDeployment: 'nitro' } : {}, + ) + expect(options?.chosenAddOns.map((addOn) => addOn.id)).toEqual(['nitro']) + }, + ) + + it.each([false, true])( + 'rejects the Vite-only Cloudflare deployment for Rsbuild (forced=%s)', + async (forced) => { + setBasicSpies() + vi.mocked(prompts.selectBundler).mockResolvedValue('rsbuild') + await expect( + promptForCreateOptions( + { + ...baseCliOptions, + buildTool: 'rsbuild', + blank: true, + deployment: forced ? undefined : 'cloudflare', + }, + forced ? { forcedDeployment: 'cloudflare' } : {}, + ), + ).rejects.toThrow('"cloudflare" (Cloudflare) is not supported') + }, + ) + it('keeps toolchain selection available for blank Rsbuild projects', async () => { setBasicSpies() vi.mocked(prompts.selectBundler).mockResolvedValue('rsbuild') @@ -438,6 +500,29 @@ describe('promptForCreateOptions', () => { }) describe('promptForAddOns', () => { + it('offers a deployment to an existing Rsbuild Start app', async () => { + vi.mocked(prompts.selectAddOns).mockReset() + vi.mocked(prompts.selectAddOns).mockResolvedValue(['nitro']) + vi.spyOn(create, 'readConfigFile').mockResolvedValue({ + projectName: 'test', + framework: 'react', + bundler: 'rsbuild', + mode: 'file-router', + chosenAddOns: ['eslint'], + version: 1, + }) + await expect(promptForAddOns()).resolves.toEqual(['nitro']) + expect(prompts.selectAddOns).toHaveBeenCalledWith( + expect.anything(), + 'file-router', + 'deployment', + expect.any(String), + ['biome', 'eslint'], + false, + 'rsbuild', + ) + }) + it('does not offer a second exclusive Rsbuild toolchain', async () => { vi.mocked(prompts.selectAddOns).mockClear() vi.spyOn(create, 'readConfigFile').mockResolvedValue({ @@ -445,7 +530,7 @@ describe('promptForAddOns', () => { framework: 'react', bundler: 'rsbuild', mode: 'file-router', - chosenAddOns: ['eslint'], + chosenAddOns: ['eslint', 'nitro'], version: 1, }) diff --git a/packages/create/package.json b/packages/create/package.json index 699584e6..15f6de8a 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -78,6 +78,7 @@ "parse-gitignore": "^2.0.0", "prettier": "^3.5.0", "rimraf": "^6.0.1", + "semver": "^7.7.3", "yaml": "^2.8.2", "zod": "^3.24.2" }, @@ -85,6 +86,7 @@ "@types/ejs": "^3.1.5", "@types/node": "^22.13.4", "@types/parse-gitignore": "^1.0.2", + "@types/semver": "^7.7.0", "@vitest/coverage-v8": "4.1.5", "eslint": "^9.20.0", "memfs": "4.17.0", diff --git a/packages/create/src/build-tools.ts b/packages/create/src/build-tools.ts index 880967e5..1a32919c 100644 --- a/packages/create/src/build-tools.ts +++ b/packages/create/src/build-tools.ts @@ -39,19 +39,26 @@ export function resolveBundler( } export function isAddOnSupportedByBundler( - addOn: Pick, + addOn: Pick, bundlerId: string, ) { - return bundlerId !== 'rsbuild' || addOn.type === 'toolchain' + return ( + bundlerId !== 'rsbuild' || + addOn.type === 'toolchain' || + (addOn.type === 'deployment' && + addOn.integrations?.some( + (integration) => integration.type === 'rsbuild-plugin', + ) === true) + ) } export function assertAddOnSupportedByBundler( - addOn: Pick, + addOn: Pick, bundlerId: string, ) { if (!isAddOnSupportedByBundler(addOn, bundlerId)) { throw new Error( - `Rsbuild currently supports toolchain add-ons only. "${addOn.id}" (${addOn.name}) is a ${addOn.type} add-on.`, + `Rsbuild supports toolchains and deployment adapters with an Rsbuild integration only. "${addOn.id}" (${addOn.name}) is not supported.`, ) } } @@ -67,6 +74,18 @@ export function normalizeAndValidateBundlerOptions(options: Options): Options { assertAddOnSupportedByBundler(addOn, bundler) } + if (bundler === 'rsbuild') { + const deployments = options.chosenAddOns.filter( + (addOn) => addOn.type === 'deployment', + ) + if (deployments.length > 1) { + throw new Error('Only one deployment adapter can be selected.') + } + if (options.routerOnly && deployments.length > 0) { + throw new Error('Rsbuild deployment adapters require TanStack Start.') + } + } + return { ...options, bundler, diff --git a/packages/create/src/edge-package-json.ts b/packages/create/src/edge-package-json.ts index 6929cc2d..8bfab5cb 100644 --- a/packages/create/src/edge-package-json.ts +++ b/packages/create/src/edge-package-json.ts @@ -1,6 +1,7 @@ import { renderForOptions } from './edge-render.js' import { formatCommand, sortObject } from './utils.js' import { getPackageManagerExecuteCommand } from './package-manager.js' +import { mergePackageEngines } from './package-engines.js' import type { Options } from './types.js' @@ -52,6 +53,12 @@ export function mergePackageJSON( }, } + const engines = mergePackageEngines( + packageJSON.engines as Record | undefined, + overlayPackageJSON?.engines as Record | undefined, + ) + if (engines) mergedPackageJSON.engines = engines + const packagePnpm = packageJSON.pnpm && typeof packageJSON.pnpm === 'object' ? (packageJSON.pnpm as Record) @@ -123,6 +130,9 @@ export function createPackageJSON(options: Options) { const additions: Array | undefined> = [ options.framework.optionalPackages.typescript, options.framework.optionalPackages.bundlers?.[bundler], + options.chosenAddOns.some((addOn) => addOn.type === 'deployment') + ? options.framework.optionalPackages.bundlerDeployment?.[bundler] + : undefined, includeDevtools ? options.framework.optionalPackages.devtools : undefined, includeDevtools ? options.framework.optionalPackages.bundlerDevtools?.[bundler] @@ -212,6 +222,14 @@ export function createPackageJSON(options: Options) { | undefined const scripts = packageJSON.scripts as Record | undefined + if ( + bundler === 'rsbuild' && + options.chosenAddOns.some((addOn) => addOn.type === 'deployment') + ) { + delete (packageJSON.dependencies as Record | undefined) + ?.srvx + } + if (options.routerOnly) { delete dependencies?.srvx delete scripts?.start diff --git a/packages/create/src/frameworks/react/hosts/netlify/README.md b/packages/create/src/frameworks/react/hosts/netlify/README.md.ejs similarity index 59% rename from packages/create/src/frameworks/react/hosts/netlify/README.md rename to packages/create/src/frameworks/react/hosts/netlify/README.md.ejs index a00ad80f..33663e31 100644 --- a/packages/create/src/frameworks/react/hosts/netlify/README.md +++ b/packages/create/src/frameworks/react/hosts/netlify/README.md.ejs @@ -1,3 +1,10 @@ +<% if (bundler === 'rsbuild') { -%> +## Deploy to Netlify + +This project uses the experimental Nitro Rsbuild integration with the Netlify preset. + +Connect your repository to Netlify. Build with `<%= packageManager %> run build` and publish `dist`, as configured in `netlify.toml`. Nitro emits the server functions and Netlify deployment configuration in `.netlify/`. +<% } else { -%> ## Deploy to Netlify This project ships with `netlify.toml` configured for a Netlify site: @@ -9,3 +16,4 @@ This project ships with `netlify.toml` configured for a Netlify site: 5. Trigger the first deploy Server functions and API routes run on Netlify Functions. For lower-latency request handling, see Netlify Edge Functions: https://docs.netlify.com/edge-functions/overview. +<% } -%> diff --git a/packages/create/src/frameworks/react/hosts/netlify/assets/netlify.toml b/packages/create/src/frameworks/react/hosts/netlify/assets/netlify.toml deleted file mode 100644 index 0e944a36..00000000 --- a/packages/create/src/frameworks/react/hosts/netlify/assets/netlify.toml +++ /dev/null @@ -1,7 +0,0 @@ -[build] - command = "vite build" - publish = "dist/client" -[dev] - command = "npm run dev" - targetPort = 3000 - port = 8888 \ No newline at end of file diff --git a/packages/create/src/frameworks/react/hosts/netlify/assets/netlify.toml.ejs b/packages/create/src/frameworks/react/hosts/netlify/assets/netlify.toml.ejs new file mode 100644 index 00000000..e0f913a6 --- /dev/null +++ b/packages/create/src/frameworks/react/hosts/netlify/assets/netlify.toml.ejs @@ -0,0 +1,7 @@ +[build] + command = "<%= bundler %> build" + publish = "<%= bundler === 'rsbuild' ? 'dist' : 'dist/client' %>" +[dev] + command = "npm run dev" + targetPort = 3000 + port = 8888 \ No newline at end of file diff --git a/packages/create/src/frameworks/react/hosts/netlify/info.json b/packages/create/src/frameworks/react/hosts/netlify/info.json index 611ce371..d2e2c00a 100644 --- a/packages/create/src/frameworks/react/hosts/netlify/info.json +++ b/packages/create/src/frameworks/react/hosts/netlify/info.json @@ -15,6 +15,11 @@ "type": "vite-plugin", "import": "import netlify from '@netlify/vite-plugin-tanstack-start'", "code": "netlify()" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'netlify' })" } ] } diff --git a/packages/create/src/frameworks/react/hosts/netlify/package.json b/packages/create/src/frameworks/react/hosts/netlify/package.json deleted file mode 100644 index 70a27e56..00000000 --- a/packages/create/src/frameworks/react/hosts/netlify/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "@netlify/vite-plugin-tanstack-start": "^1.2.15" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "sharp" - ] - } -} diff --git a/packages/create/src/frameworks/react/hosts/netlify/package.json.ejs b/packages/create/src/frameworks/react/hosts/netlify/package.json.ejs new file mode 100644 index 00000000..38b3fb73 --- /dev/null +++ b/packages/create/src/frameworks/react/hosts/netlify/package.json.ejs @@ -0,0 +1,21 @@ +<% if (bundler === 'rsbuild') { -%> +{ + "scripts": { + "start": "rsbuild preview" + }, + "dependencies": { + "nitro": "3.0.261103-rc" + } +} +<% } else { -%> +{ + "devDependencies": { + "@netlify/vite-plugin-tanstack-start": "^1.2.15" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "sharp" + ] + } +} +<% } -%> diff --git a/packages/create/src/frameworks/react/hosts/nitro/info.json b/packages/create/src/frameworks/react/hosts/nitro/info.json index 685cf4e9..66381015 100644 --- a/packages/create/src/frameworks/react/hosts/nitro/info.json +++ b/packages/create/src/frameworks/react/hosts/nitro/info.json @@ -14,6 +14,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro({ rollupConfig: { external: [/^@sentry\\//] } })" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'node-server' })" } ], "default": true diff --git a/packages/create/src/frameworks/react/hosts/nitro/package.json b/packages/create/src/frameworks/react/hosts/nitro/package.json index 56e871bc..bd46081b 100644 --- a/packages/create/src/frameworks/react/hosts/nitro/package.json +++ b/packages/create/src/frameworks/react/hosts/nitro/package.json @@ -1,5 +1,8 @@ { + "engines": { + "node": ">=22.22.0" + }, "dependencies": { - "nitro": "3.0.260610-beta" + "nitro": "3.0.261103-rc" } } diff --git a/packages/create/src/frameworks/react/hosts/railway/info.json b/packages/create/src/frameworks/react/hosts/railway/info.json index 4ddf6b96..d7a04c5f 100644 --- a/packages/create/src/frameworks/react/hosts/railway/info.json +++ b/packages/create/src/frameworks/react/hosts/railway/info.json @@ -16,6 +16,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro({ rollupConfig: { external: [/^@sentry\\//] } })" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'node-server' })" } ] } diff --git a/packages/create/src/frameworks/react/hosts/railway/package.json.ejs b/packages/create/src/frameworks/react/hosts/railway/package.json.ejs index 909ab021..b21a5f85 100644 --- a/packages/create/src/frameworks/react/hosts/railway/package.json.ejs +++ b/packages/create/src/frameworks/react/hosts/railway/package.json.ejs @@ -1,8 +1,9 @@ { + "engines": { "node": ">=22.22.0" }, "scripts": { "start": "<%= addOnEnabled.sentry ? 'node --import ./.output/server/instrument.server.mjs .output/server/index.mjs' : 'node .output/server/index.mjs' %>" }, "dependencies": { - "nitro": "3.0.260610-beta" + "nitro": "3.0.261103-rc" } } diff --git a/packages/create/src/frameworks/react/hosts/render/info.json b/packages/create/src/frameworks/react/hosts/render/info.json index e7cfabae..6f58768d 100644 --- a/packages/create/src/frameworks/react/hosts/render/info.json +++ b/packages/create/src/frameworks/react/hosts/render/info.json @@ -16,6 +16,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro({ rollupConfig: { external: [/^@sentry\\//] } })" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'render-com' })" } ] } diff --git a/packages/create/src/frameworks/react/hosts/render/package.json.ejs b/packages/create/src/frameworks/react/hosts/render/package.json.ejs index 909ab021..b21a5f85 100644 --- a/packages/create/src/frameworks/react/hosts/render/package.json.ejs +++ b/packages/create/src/frameworks/react/hosts/render/package.json.ejs @@ -1,8 +1,9 @@ { + "engines": { "node": ">=22.22.0" }, "scripts": { "start": "<%= addOnEnabled.sentry ? 'node --import ./.output/server/instrument.server.mjs .output/server/index.mjs' : 'node .output/server/index.mjs' %>" }, "dependencies": { - "nitro": "3.0.260610-beta" + "nitro": "3.0.261103-rc" } } diff --git a/packages/create/src/frameworks/react/hosts/vercel/README.md b/packages/create/src/frameworks/react/hosts/vercel/README.md deleted file mode 100644 index 641cbfc3..00000000 --- a/packages/create/src/frameworks/react/hosts/vercel/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Deploy to Vercel - -1. Push this repo to GitHub, GitLab, or Bitbucket -2. In Vercel, choose **Add New > Project** and import the repo -3. Keep the detected TanStack Start framework settings -4. Add production values from `.env.example` under **Settings > Environment Variables** -5. Deploy - -Vercel runs the build script and deploys Nitro's output as Vercel Functions and -static assets. The included `vercel.json` makes framework detection explicit. - -Variables prefixed with `VITE_` are included in the browser bundle. Keep secrets -unprefixed so they remain server-only. diff --git a/packages/create/src/frameworks/react/hosts/vercel/README.md.ejs b/packages/create/src/frameworks/react/hosts/vercel/README.md.ejs new file mode 100644 index 00000000..858b6c3f --- /dev/null +++ b/packages/create/src/frameworks/react/hosts/vercel/README.md.ejs @@ -0,0 +1,23 @@ +<% if (bundler === 'rsbuild') { -%> +## Deploy to Vercel + +This project uses the experimental Nitro Rsbuild integration with the Vercel preset, which generates `.vercel/output` for the Vercel Build Output API. + +Import your repository using Add New > Project in Vercel. `vercel.json` disables framework auto-detection and sets the build command to `<%= getPackageManagerRunScript('build') %>`. For a local prebuilt deployment, run `<%= getPackageManagerRunScript('build') %>` followed by `vercel deploy --prebuilt`. + +Configure production secrets in the Vercel project settings. +<% } else { -%> +## Deploy to Vercel + +1. Push this repo to GitHub, GitLab, or Bitbucket +2. In Vercel, choose **Add New > Project** and import the repo +3. Keep the detected TanStack Start framework settings +4. Add production values from `.env.example` under **Settings > Environment Variables** +5. Deploy + +Vercel runs the build script and deploys Nitro's output as Vercel Functions and +static assets. The included `vercel.json` makes framework detection explicit. + +Variables prefixed with `VITE_` are included in the browser bundle. Keep secrets +unprefixed so they remain server-only. +<% } -%> diff --git a/packages/create/src/frameworks/react/hosts/vercel/assets/vercel.json b/packages/create/src/frameworks/react/hosts/vercel/assets/vercel.json deleted file mode 100644 index 935d5d78..00000000 --- a/packages/create/src/frameworks/react/hosts/vercel/assets/vercel.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "framework": "tanstack-start" -} diff --git a/packages/create/src/frameworks/react/hosts/vercel/assets/vercel.json.ejs b/packages/create/src/frameworks/react/hosts/vercel/assets/vercel.json.ejs new file mode 100644 index 00000000..8301a9c8 --- /dev/null +++ b/packages/create/src/frameworks/react/hosts/vercel/assets/vercel.json.ejs @@ -0,0 +1,5 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": <%- JSON.stringify(bundler === 'rsbuild' ? null : 'tanstack-start') %><% if (bundler === 'rsbuild') { %>, + "buildCommand": "<%= getPackageManagerRunScript('build') %>"<% } %> +} diff --git a/packages/create/src/frameworks/react/hosts/vercel/info.json b/packages/create/src/frameworks/react/hosts/vercel/info.json index eca28cc0..a4ac4070 100644 --- a/packages/create/src/frameworks/react/hosts/vercel/info.json +++ b/packages/create/src/frameworks/react/hosts/vercel/info.json @@ -16,6 +16,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro()" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'vercel' })" } ] } diff --git a/packages/create/src/frameworks/react/hosts/vercel/package.json b/packages/create/src/frameworks/react/hosts/vercel/package.json deleted file mode 100644 index 56e871bc..00000000 --- a/packages/create/src/frameworks/react/hosts/vercel/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "nitro": "3.0.260610-beta" - } -} diff --git a/packages/create/src/frameworks/react/hosts/vercel/package.json.ejs b/packages/create/src/frameworks/react/hosts/vercel/package.json.ejs new file mode 100644 index 00000000..925f9e5e --- /dev/null +++ b/packages/create/src/frameworks/react/hosts/vercel/package.json.ejs @@ -0,0 +1,9 @@ +{ + "engines": { "node": ">=22.22.0" }, + <% if (bundler === 'rsbuild') { -%> + "scripts": { "start": "rsbuild preview" }, + <% } -%> + "dependencies": { + "nitro": "3.0.261103-rc" + } +} diff --git a/packages/create/src/frameworks/react/project/base/rsbuild.config.ts.ejs b/packages/create/src/frameworks/react/project/base/rsbuild.config.ts.ejs index 02289a5f..9f94084b 100644 --- a/packages/create/src/frameworks/react/project/base/rsbuild.config.ts.ejs +++ b/packages/create/src/frameworks/react/project/base/rsbuild.config.ts.ejs @@ -1,4 +1,5 @@ <% if (bundler !== 'rsbuild') { ignoreFile() } -%> +<% const deploymentPlugins = integrations.filter(i => i.type === 'rsbuild-plugin') -%> import { defineConfig } from '@rsbuild/core' import { pluginReact } from '@rsbuild/plugin-react' <% if (tailwind) { -%> @@ -10,6 +11,10 @@ import { tanstackRouter } from '@tanstack/router-plugin/rspack' import { tanstackStart } from '@tanstack/react-start/plugin/rsbuild' <% } -%> +<% for (const integration of deploymentPlugins) { -%> +<%- integrationImportContent(integration) %> +<% } -%> + export default defineConfig({ server: { port: 3000, @@ -28,7 +33,8 @@ export default defineConfig({ plugins: [ pluginReact(), <% if (tailwind) { %>pluginTailwindcss(),<% } %> - <% if (!routerOnly) { %>tanstackStart(),<% } %> + <% if (!routerOnly) { %>tanstackStart(<% if (deploymentPlugins.length) { %>{ rsbuild: { installDevServerMiddleware: false } }<% } %>),<% } %> + <% for (const integration of deploymentPlugins) { %><%- integrationImportCode(integration) %>,<% } %> ], <% if (routerOnly) { -%> tools: { diff --git a/packages/create/src/frameworks/react/project/packages.json b/packages/create/src/frameworks/react/project/packages.json index 679581fa..cad75026 100644 --- a/packages/create/src/frameworks/react/project/packages.json +++ b/packages/create/src/frameworks/react/project/packages.json @@ -85,5 +85,22 @@ "@tanstack/router-cli": "^1.132.0" } } + }, + "bundlerDeployment": { + "rsbuild": { + "engines": { + "node": ">=22.22.0" + }, + "devDependencies": { + "@rsbuild/core": "^2.3.0" + }, + "scripts": { + "start": "node .output/server/index.mjs" + }, + "dependencies": { + "db0": "^0.4.1", + "unstorage": "^2.0.0-alpha.10" + } + } } } diff --git a/packages/create/src/frameworks/solid/hosts/netlify/README.md b/packages/create/src/frameworks/solid/hosts/netlify/README.md.ejs similarity index 59% rename from packages/create/src/frameworks/solid/hosts/netlify/README.md rename to packages/create/src/frameworks/solid/hosts/netlify/README.md.ejs index a00ad80f..33663e31 100644 --- a/packages/create/src/frameworks/solid/hosts/netlify/README.md +++ b/packages/create/src/frameworks/solid/hosts/netlify/README.md.ejs @@ -1,3 +1,10 @@ +<% if (bundler === 'rsbuild') { -%> +## Deploy to Netlify + +This project uses the experimental Nitro Rsbuild integration with the Netlify preset. + +Connect your repository to Netlify. Build with `<%= packageManager %> run build` and publish `dist`, as configured in `netlify.toml`. Nitro emits the server functions and Netlify deployment configuration in `.netlify/`. +<% } else { -%> ## Deploy to Netlify This project ships with `netlify.toml` configured for a Netlify site: @@ -9,3 +16,4 @@ This project ships with `netlify.toml` configured for a Netlify site: 5. Trigger the first deploy Server functions and API routes run on Netlify Functions. For lower-latency request handling, see Netlify Edge Functions: https://docs.netlify.com/edge-functions/overview. +<% } -%> diff --git a/packages/create/src/frameworks/solid/hosts/netlify/assets/netlify.toml b/packages/create/src/frameworks/solid/hosts/netlify/assets/netlify.toml deleted file mode 100644 index 0b48ff85..00000000 --- a/packages/create/src/frameworks/solid/hosts/netlify/assets/netlify.toml +++ /dev/null @@ -1,7 +0,0 @@ -[build] - command = "vite build" - publish = "dist/client" -[dev] - command = "npm run dev" - targetPort = 3000 - port = 8888 diff --git a/packages/create/src/frameworks/solid/hosts/netlify/assets/netlify.toml.ejs b/packages/create/src/frameworks/solid/hosts/netlify/assets/netlify.toml.ejs new file mode 100644 index 00000000..2f3b0f7d --- /dev/null +++ b/packages/create/src/frameworks/solid/hosts/netlify/assets/netlify.toml.ejs @@ -0,0 +1,7 @@ +[build] + command = "<%= bundler %> build" + publish = "<%= bundler === 'rsbuild' ? 'dist' : 'dist/client' %>" +[dev] + command = "npm run dev" + targetPort = 3000 + port = 8888 diff --git a/packages/create/src/frameworks/solid/hosts/netlify/info.json b/packages/create/src/frameworks/solid/hosts/netlify/info.json index 611ce371..d2e2c00a 100644 --- a/packages/create/src/frameworks/solid/hosts/netlify/info.json +++ b/packages/create/src/frameworks/solid/hosts/netlify/info.json @@ -15,6 +15,11 @@ "type": "vite-plugin", "import": "import netlify from '@netlify/vite-plugin-tanstack-start'", "code": "netlify()" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'netlify' })" } ] } diff --git a/packages/create/src/frameworks/solid/hosts/netlify/package.json b/packages/create/src/frameworks/solid/hosts/netlify/package.json deleted file mode 100644 index 70a27e56..00000000 --- a/packages/create/src/frameworks/solid/hosts/netlify/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "@netlify/vite-plugin-tanstack-start": "^1.2.15" - }, - "pnpm": { - "onlyBuiltDependencies": [ - "sharp" - ] - } -} diff --git a/packages/create/src/frameworks/solid/hosts/netlify/package.json.ejs b/packages/create/src/frameworks/solid/hosts/netlify/package.json.ejs new file mode 100644 index 00000000..38b3fb73 --- /dev/null +++ b/packages/create/src/frameworks/solid/hosts/netlify/package.json.ejs @@ -0,0 +1,21 @@ +<% if (bundler === 'rsbuild') { -%> +{ + "scripts": { + "start": "rsbuild preview" + }, + "dependencies": { + "nitro": "3.0.261103-rc" + } +} +<% } else { -%> +{ + "devDependencies": { + "@netlify/vite-plugin-tanstack-start": "^1.2.15" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "sharp" + ] + } +} +<% } -%> diff --git a/packages/create/src/frameworks/solid/hosts/nitro/info.json b/packages/create/src/frameworks/solid/hosts/nitro/info.json index 1a564ec0..d5b832f5 100644 --- a/packages/create/src/frameworks/solid/hosts/nitro/info.json +++ b/packages/create/src/frameworks/solid/hosts/nitro/info.json @@ -13,6 +13,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro()" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'node-server' })" } ], "default": true diff --git a/packages/create/src/frameworks/solid/hosts/nitro/package.json b/packages/create/src/frameworks/solid/hosts/nitro/package.json index 56e871bc..bd46081b 100644 --- a/packages/create/src/frameworks/solid/hosts/nitro/package.json +++ b/packages/create/src/frameworks/solid/hosts/nitro/package.json @@ -1,5 +1,8 @@ { + "engines": { + "node": ">=22.22.0" + }, "dependencies": { - "nitro": "3.0.260610-beta" + "nitro": "3.0.261103-rc" } } diff --git a/packages/create/src/frameworks/solid/hosts/railway/info.json b/packages/create/src/frameworks/solid/hosts/railway/info.json index 713a0195..042b8efe 100644 --- a/packages/create/src/frameworks/solid/hosts/railway/info.json +++ b/packages/create/src/frameworks/solid/hosts/railway/info.json @@ -16,6 +16,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro()" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'node-server' })" } ] } diff --git a/packages/create/src/frameworks/solid/hosts/railway/package.json b/packages/create/src/frameworks/solid/hosts/railway/package.json index a3b22116..a9ee531f 100644 --- a/packages/create/src/frameworks/solid/hosts/railway/package.json +++ b/packages/create/src/frameworks/solid/hosts/railway/package.json @@ -1,8 +1,11 @@ { + "engines": { + "node": ">=22.22.0" + }, "scripts": { "start": "node .output/server/index.mjs" }, "dependencies": { - "nitro": "3.0.260610-beta" + "nitro": "3.0.261103-rc" } } diff --git a/packages/create/src/frameworks/solid/hosts/render/info.json b/packages/create/src/frameworks/solid/hosts/render/info.json index 33a2001e..97f4d466 100644 --- a/packages/create/src/frameworks/solid/hosts/render/info.json +++ b/packages/create/src/frameworks/solid/hosts/render/info.json @@ -16,6 +16,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro()" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'render-com' })" } ] } diff --git a/packages/create/src/frameworks/solid/hosts/render/package.json b/packages/create/src/frameworks/solid/hosts/render/package.json index a3b22116..a9ee531f 100644 --- a/packages/create/src/frameworks/solid/hosts/render/package.json +++ b/packages/create/src/frameworks/solid/hosts/render/package.json @@ -1,8 +1,11 @@ { + "engines": { + "node": ">=22.22.0" + }, "scripts": { "start": "node .output/server/index.mjs" }, "dependencies": { - "nitro": "3.0.260610-beta" + "nitro": "3.0.261103-rc" } } diff --git a/packages/create/src/frameworks/solid/hosts/vercel/README.md b/packages/create/src/frameworks/solid/hosts/vercel/README.md deleted file mode 100644 index 641cbfc3..00000000 --- a/packages/create/src/frameworks/solid/hosts/vercel/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Deploy to Vercel - -1. Push this repo to GitHub, GitLab, or Bitbucket -2. In Vercel, choose **Add New > Project** and import the repo -3. Keep the detected TanStack Start framework settings -4. Add production values from `.env.example` under **Settings > Environment Variables** -5. Deploy - -Vercel runs the build script and deploys Nitro's output as Vercel Functions and -static assets. The included `vercel.json` makes framework detection explicit. - -Variables prefixed with `VITE_` are included in the browser bundle. Keep secrets -unprefixed so they remain server-only. diff --git a/packages/create/src/frameworks/solid/hosts/vercel/README.md.ejs b/packages/create/src/frameworks/solid/hosts/vercel/README.md.ejs new file mode 100644 index 00000000..858b6c3f --- /dev/null +++ b/packages/create/src/frameworks/solid/hosts/vercel/README.md.ejs @@ -0,0 +1,23 @@ +<% if (bundler === 'rsbuild') { -%> +## Deploy to Vercel + +This project uses the experimental Nitro Rsbuild integration with the Vercel preset, which generates `.vercel/output` for the Vercel Build Output API. + +Import your repository using Add New > Project in Vercel. `vercel.json` disables framework auto-detection and sets the build command to `<%= getPackageManagerRunScript('build') %>`. For a local prebuilt deployment, run `<%= getPackageManagerRunScript('build') %>` followed by `vercel deploy --prebuilt`. + +Configure production secrets in the Vercel project settings. +<% } else { -%> +## Deploy to Vercel + +1. Push this repo to GitHub, GitLab, or Bitbucket +2. In Vercel, choose **Add New > Project** and import the repo +3. Keep the detected TanStack Start framework settings +4. Add production values from `.env.example` under **Settings > Environment Variables** +5. Deploy + +Vercel runs the build script and deploys Nitro's output as Vercel Functions and +static assets. The included `vercel.json` makes framework detection explicit. + +Variables prefixed with `VITE_` are included in the browser bundle. Keep secrets +unprefixed so they remain server-only. +<% } -%> diff --git a/packages/create/src/frameworks/solid/hosts/vercel/assets/vercel.json b/packages/create/src/frameworks/solid/hosts/vercel/assets/vercel.json deleted file mode 100644 index 935d5d78..00000000 --- a/packages/create/src/frameworks/solid/hosts/vercel/assets/vercel.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "framework": "tanstack-start" -} diff --git a/packages/create/src/frameworks/solid/hosts/vercel/assets/vercel.json.ejs b/packages/create/src/frameworks/solid/hosts/vercel/assets/vercel.json.ejs new file mode 100644 index 00000000..8301a9c8 --- /dev/null +++ b/packages/create/src/frameworks/solid/hosts/vercel/assets/vercel.json.ejs @@ -0,0 +1,5 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": <%- JSON.stringify(bundler === 'rsbuild' ? null : 'tanstack-start') %><% if (bundler === 'rsbuild') { %>, + "buildCommand": "<%= getPackageManagerRunScript('build') %>"<% } %> +} diff --git a/packages/create/src/frameworks/solid/hosts/vercel/info.json b/packages/create/src/frameworks/solid/hosts/vercel/info.json index eca28cc0..a4ac4070 100644 --- a/packages/create/src/frameworks/solid/hosts/vercel/info.json +++ b/packages/create/src/frameworks/solid/hosts/vercel/info.json @@ -16,6 +16,11 @@ "type": "vite-plugin", "import": "import { nitro } from 'nitro/vite'", "code": "nitro()" + }, + { + "type": "rsbuild-plugin", + "import": "import { nitro } from 'nitro/rsbuild'", + "code": "nitro({ preset: 'vercel' })" } ] } diff --git a/packages/create/src/frameworks/solid/hosts/vercel/package.json b/packages/create/src/frameworks/solid/hosts/vercel/package.json deleted file mode 100644 index 56e871bc..00000000 --- a/packages/create/src/frameworks/solid/hosts/vercel/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "nitro": "3.0.260610-beta" - } -} diff --git a/packages/create/src/frameworks/solid/hosts/vercel/package.json.ejs b/packages/create/src/frameworks/solid/hosts/vercel/package.json.ejs new file mode 100644 index 00000000..925f9e5e --- /dev/null +++ b/packages/create/src/frameworks/solid/hosts/vercel/package.json.ejs @@ -0,0 +1,9 @@ +{ + "engines": { "node": ">=22.22.0" }, + <% if (bundler === 'rsbuild') { -%> + "scripts": { "start": "rsbuild preview" }, + <% } -%> + "dependencies": { + "nitro": "3.0.261103-rc" + } +} diff --git a/packages/create/src/frameworks/solid/project/base/rsbuild.config.ts.ejs b/packages/create/src/frameworks/solid/project/base/rsbuild.config.ts.ejs index 126eb707..19418d67 100644 --- a/packages/create/src/frameworks/solid/project/base/rsbuild.config.ts.ejs +++ b/packages/create/src/frameworks/solid/project/base/rsbuild.config.ts.ejs @@ -1,4 +1,5 @@ <% if (bundler !== 'rsbuild') { ignoreFile() } -%> +<% const deploymentPlugins = integrations.filter(i => i.type === 'rsbuild-plugin') -%> import { defineConfig } from '@rsbuild/core' import { pluginBabel } from '@rsbuild/plugin-babel' import { pluginSolid } from '@rsbuild/plugin-solid' @@ -11,6 +12,10 @@ import { tanstackRouter } from '@tanstack/router-plugin/rspack' import { tanstackStart } from '@tanstack/solid-start/plugin/rsbuild' <% } -%> +<% for (const integration of deploymentPlugins) { -%> +<%- integrationImportContent(integration) %> +<% } -%> + export default defineConfig({ server: { port: 3000, @@ -32,7 +37,8 @@ export default defineConfig({ }), pluginSolid(), <% if (tailwind) { %>pluginTailwindcss(),<% } %> - <% if (!routerOnly) { %>tanstackStart(),<% } %> + <% if (!routerOnly) { %>tanstackStart(<% if (deploymentPlugins.length) { %>{ rsbuild: { installDevServerMiddleware: false } }<% } %>),<% } %> + <% for (const integration of deploymentPlugins) { %><%- integrationImportCode(integration) %>,<% } %> ], <% if (routerOnly) { -%> tools: { diff --git a/packages/create/src/frameworks/solid/project/packages.json b/packages/create/src/frameworks/solid/project/packages.json index 96494f37..aedfaac3 100644 --- a/packages/create/src/frameworks/solid/project/packages.json +++ b/packages/create/src/frameworks/solid/project/packages.json @@ -78,5 +78,22 @@ "@tanstack/router-cli": "^1.133.21" } } + }, + "bundlerDeployment": { + "rsbuild": { + "engines": { + "node": ">=22.22.0" + }, + "devDependencies": { + "@rsbuild/core": "^2.3.0" + }, + "scripts": { + "start": "node .output/server/index.mjs" + }, + "dependencies": { + "db0": "^0.4.1", + "unstorage": "^2.0.0-alpha.10" + } + } } } diff --git a/packages/create/src/package-engines.ts b/packages/create/src/package-engines.ts new file mode 100644 index 00000000..6e19c9c5 --- /dev/null +++ b/packages/create/src/package-engines.ts @@ -0,0 +1,28 @@ +import { Range, subset } from 'semver' + +export function mergePackageEngines( + base?: Record, + overlay?: Record, +) { + if (!base && !overlay) return undefined + + const engines = { ...base, ...overlay } + for (const [engine, range] of Object.entries(overlay ?? {})) { + const existing = base?.[engine] + if (!existing || subset(range, existing)) continue + if (subset(existing, range)) { + engines[engine] = existing + continue + } + + // Both requirements must hold, including every alternative of OR ranges. + engines[engine] = new Range(existing).set + .flatMap((left) => + new Range(range).set.map((right) => + [...left, ...right].map((comparator) => comparator.value).join(' '), + ), + ) + .join(' || ') + } + return engines +} diff --git a/packages/create/src/package-json.ts b/packages/create/src/package-json.ts index fde5f7fd..6712b047 100644 --- a/packages/create/src/package-json.ts +++ b/packages/create/src/package-json.ts @@ -1,6 +1,7 @@ import { render } from 'ejs' import { formatCommand, sortObject } from './utils.js' import { getPackageManagerExecuteCommand } from './package-manager.js' +import { mergePackageEngines } from './package-engines.js' import type { Options } from './types.js' export function mergePackageJSON( @@ -24,6 +25,12 @@ export function mergePackageJSON( }, } + const engines = mergePackageEngines( + packageJSON.engines, + overlayPackageJSON?.engines, + ) + if (engines) mergedPackageJSON.engines = engines + const baseOnlyBuiltDependencies = Array.isArray( packageJSON.pnpm?.onlyBuiltDependencies, ) @@ -83,6 +90,9 @@ export function createPackageJSON(options: Options) { const additions: Array | undefined> = [ options.framework.optionalPackages.typescript, options.framework.optionalPackages.bundlers?.[bundler], + options.chosenAddOns.some((addOn) => addOn.type === 'deployment') + ? options.framework.optionalPackages.bundlerDeployment?.[bundler] + : undefined, includeDevtools ? options.framework.optionalPackages.devtools : undefined, includeDevtools ? options.framework.optionalPackages.bundlerDevtools?.[bundler] @@ -161,6 +171,14 @@ export function createPackageJSON(options: Options) { ) } + if ( + bundler === 'rsbuild' && + options.chosenAddOns.some((addOn) => addOn.type === 'deployment') + ) { + delete (packageJSON.dependencies as Record | undefined) + ?.srvx + } + if (options.routerOnly) { delete packageJSON.dependencies?.srvx delete packageJSON.scripts?.start diff --git a/packages/create/tests/bundlers.test.ts b/packages/create/tests/bundlers.test.ts index 94191661..dc0a08f6 100644 --- a/packages/create/tests/bundlers.test.ts +++ b/packages/create/tests/bundlers.test.ts @@ -102,13 +102,21 @@ describe('bundler project generation', () => { ['react', createReactFrameworkDefinition, 'shadcn'], ['solid', createSolidFrameworkDefinition, 'solid-ui'], ])( - '%s filters the Rsbuild catalog to toolchains', + '%s filters the Rsbuild catalog to toolchains and compatible deployments', async (_, createDefinition, incompatibleAddOn) => { const framework = frameworkFromDefinition(createDefinition()) const addOns = getAllAddOns(framework, 'file-router', 'rsbuild') expect(addOns.length).toBeGreaterThan(0) - expect(addOns.every((addOn) => addOn.type === 'toolchain')).toBe(true) + expect( + addOns.every((addOn) => + ['toolchain', 'deployment'].includes(addOn.type), + ), + ).toBe(true) + expect( + addOns.filter((addOn) => addOn.type === 'deployment'), + ).toHaveLength(5) + expect(addOns.map((addOn) => addOn.id)).not.toContain('cloudflare') await expect( finalizeAddOns(framework, 'file-router', ['eslint'], 'rsbuild'), ).resolves.toEqual( @@ -121,7 +129,7 @@ describe('bundler project generation', () => { [incompatibleAddOn], 'rsbuild', ), - ).rejects.toThrow('Rsbuild currently supports toolchain add-ons only') + ).rejects.toThrow('Rsbuild supports toolchains and deployment adapters') }, ) diff --git a/packages/create/tests/edge-manifest.test.ts b/packages/create/tests/edge-manifest.test.ts index c9fee0db..51cfbebe 100644 --- a/packages/create/tests/edge-manifest.test.ts +++ b/packages/create/tests/edge-manifest.test.ts @@ -95,7 +95,7 @@ describe('@tanstack/create/edge manifest', () => { ) }) - it('renders an Rsbuild app from the edge manifest', async () => { + it('renders an Rsbuild deployment from the edge manifest', async () => { try { vi.stubGlobal( 'fetch', @@ -122,13 +122,16 @@ describe('@tanstack/create/edge manifest', () => { git: false, install: false, intent: false, - chosenAddOns: [], + chosenAddOns: await finalizeAddOns(framework!, 'file-router', ['netlify'], 'rsbuild'), addOnOptions: {}, includeExamples: false, } satisfies Options) expect(output.files).toHaveProperty('/rsbuild-app/rsbuild.config.ts') expect(output.files).not.toHaveProperty('/rsbuild-app/vite.config.ts') + expect(output.files['/rsbuild-app/rsbuild.config.ts']).toContain("nitro({ preset: 'netlify' })") + expect(JSON.parse(output.files['/rsbuild-app/package.json']).dependencies.nitro).toBe('3.0.261103-rc') + expect(output.files['/rsbuild-app/netlify.toml']).toContain('publish = "dist"') expect(JSON.parse(output.files['/rsbuild-app/.cta.json']).bundler).toBe( 'rsbuild', ) @@ -137,6 +140,14 @@ describe('@tanstack/create/edge manifest', () => { } }) + it.each(['react', 'solid'])('keeps Cloudflare out of the %s Rsbuild edge catalog', async (id) => { + const framework = getFrameworkById(id)! + expect(getAllAddOns(framework, 'file-router', 'rsbuild').map((addOn) => addOn.id)) + .not.toContain('cloudflare') + await expect(finalizeAddOns(framework, 'file-router', ['cloudflare'], 'rsbuild')) + .rejects.toThrow('is not supported') + }) + it('returns the same React add-ons as the Node filesystem-backed path', () => { const nodeFramework = frameworkFromDefinition(createReactFrameworkDefinition()) const edgeFramework = getFrameworkById('react') diff --git a/packages/create/tests/partner-addons.test.ts b/packages/create/tests/partner-addons.test.ts index aae53945..ae2691ca 100644 --- a/packages/create/tests/partner-addons.test.ts +++ b/packages/create/tests/partner-addons.test.ts @@ -172,7 +172,7 @@ describe('partner add-on scaffolds', () => { expect(packageJSON.dependencies).toHaveProperty( 'nitro', - '3.0.260610-beta', + '3.0.261103-rc', ) expect(packageJSON.dependencies).not.toHaveProperty('nitro-nightly') expect(Object.values(packageJSON.dependencies)).not.toContain( @@ -259,7 +259,7 @@ describe('partner add-on scaffolds', () => { expect(packageJSON.dependencies).toHaveProperty( 'nitro', - '3.0.260610-beta', + '3.0.261103-rc', ) expect(packageJSON.scripts.start).toBe('node .output/server/index.mjs') expect(renderYaml).toContain('name: tanstack-start-app') @@ -327,7 +327,7 @@ describe('partner add-on scaffolds', () => { ]) expect(packageJSON.dependencies).toHaveProperty( 'nitro', - '3.0.260610-beta', + '3.0.261103-rc', ) expect(vercelJSON).toEqual({ $schema: 'https://openapi.vercel.sh/vercel.json', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 766689b5..c6825491 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -266,6 +266,9 @@ importers: rimraf: specifier: ^6.0.1 version: 6.0.1 + semver: + specifier: ^7.7.3 + version: 7.7.3 yaml: specifier: ^2.8.2 version: 2.8.2 @@ -282,6 +285,9 @@ importers: '@types/parse-gitignore': specifier: ^1.0.2 version: 1.0.2 + '@types/semver': + specifier: ^7.7.0 + version: 7.7.0 '@vitest/coverage-v8': specifier: 4.1.5 version: 4.1.5(vitest@4.1.5)