diff --git a/e2e/solid-start/basic/package.json b/e2e/solid-start/basic/package.json index b71b1238dcc..d77a05fe39d 100644 --- a/e2e/solid-start/basic/package.json +++ b/e2e/solid-start/basic/package.json @@ -8,11 +8,15 @@ "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", "build:spa": "MODE=spa vite build && tsc --noEmit", + "build:prerender": "MODE=prerender vite build && tsc --noEmit", "start": "pnpx srvx --prod -s ../client dist/server/server.js", "start:spa": "node server.js", + "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' &", + "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'", "test:e2e:spaMode": "rm -rf port*.txt; MODE=spa playwright test --project=chromium", "test:e2e:ssrMode": "rm -rf port*.txt; playwright test --project=chromium", - "test:e2e": "pnpm run test:e2e:spaMode && pnpm run test:e2e:ssrMode" + "test:e2e:prerender": "rm -rf port*.txt; MODE=prerender playwright test --project=chromium", + "test:e2e": "pnpm run test:e2e:spaMode && pnpm run test:e2e:ssrMode && pnpm run test:e2e:prerender" }, "dependencies": { "@tanstack/solid-router": "workspace:^", diff --git a/e2e/solid-start/basic/playwright.config.ts b/e2e/solid-start/basic/playwright.config.ts index 786244ff4ce..493f4e29edc 100644 --- a/e2e/solid-start/basic/playwright.config.ts +++ b/e2e/solid-start/basic/playwright.config.ts @@ -3,8 +3,9 @@ import { getDummyServerPort, getTestServerPort, } from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } import { isSpaMode } from './tests/utils/isSpaMode' +import { isPrerender } from './tests/utils/isPrerender' +import packageJson from './package.json' with { type: 'json' } const PORT = await getTestServerPort( `${packageJson.name}${isSpaMode ? '_spa' : ''}`, @@ -16,15 +17,21 @@ const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) const baseURL = `http://localhost:${PORT}` const spaModeCommand = `pnpm build:spa && pnpm start:spa` const ssrModeCommand = `pnpm build && pnpm start` +const prerenderModeCommand = `pnpm run test:e2e:startDummyServer && pnpm build:prerender && pnpm run test:e2e:stopDummyServer && pnpm start` +const getCommand = () => { + if (isSpaMode) return spaModeCommand + if (isPrerender) return prerenderModeCommand + return ssrModeCommand +} console.log('running in spa mode: ', isSpaMode.toString()) +console.log('running in prerender mode: ', isPrerender.toString()) /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ testDir: './tests', workers: 1, - reporter: [['line']], globalSetup: './tests/setup/global.setup.ts', @@ -36,7 +43,7 @@ export default defineConfig({ }, webServer: { - command: isSpaMode ? spaModeCommand : ssrModeCommand, + command: getCommand(), url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', @@ -53,7 +60,9 @@ export default defineConfig({ projects: [ { name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + use: { + ...devices['Desktop Chrome'], + }, }, ], }) diff --git a/e2e/solid-start/basic/tests/prerendering.spec.ts b/e2e/solid-start/basic/tests/prerendering.spec.ts new file mode 100644 index 00000000000..9a3ec145dc4 --- /dev/null +++ b/e2e/solid-start/basic/tests/prerendering.spec.ts @@ -0,0 +1,53 @@ +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' +import { isPrerender } from './utils/isPrerender' + +test.describe('Prerender Static Path Discovery', () => { + test.skip(!isPrerender, 'Skipping since not in prerender mode') + test.describe('Build Output Verification', () => { + test('should automatically discover and prerender static routes', () => { + // Check that static routes were automatically discovered and prerendered + const distDir = join(process.cwd(), 'dist', 'client') + + // These static routes should be automatically discovered and prerendered + expect(existsSync(join(distDir, 'index.html'))).toBe(true) + expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) + expect(existsSync(join(distDir, 'users/index.html'))).toBe(true) + expect(existsSync(join(distDir, 'deferred/index.html'))).toBe(true) + expect(existsSync(join(distDir, 'scripts/index.html'))).toBe(true) + expect(existsSync(join(distDir, 'inline-scripts/index.html'))).toBe(true) + expect(existsSync(join(distDir, '대한민국/index.html'))).toBe(true) + + // Pathless layouts should NOT be prerendered (they start with _) + expect(existsSync(join(distDir, '_layout', 'index.html'))).toBe(false) // /_layout + + // API routes should NOT be prerendered + + expect(existsSync(join(distDir, 'api', 'users', 'index.html'))).toBe( + false, + ) // /api/users + }) + }) + + test.describe('Static Files Verification', () => { + test('should contain prerendered content in posts.html', () => { + const distDir = join(process.cwd(), 'dist', 'client') + expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) + + // "Select a post." should be in the prerendered HTML + const html = readFileSync(join(distDir, 'posts/index.html'), 'utf-8') + expect(html).toContain('Select a post.') + }) + + test('should contain prerendered content in users.html', () => { + const distDir = join(process.cwd(), 'dist', 'client') + expect(existsSync(join(distDir, 'users/index.html'))).toBe(true) + + // "Select a user." should be in the prerendered HTML + const html = readFileSync(join(distDir, 'users/index.html'), 'utf-8') + expect(html).toContain('Select a user.') + }) + }) +}) diff --git a/e2e/solid-start/basic/tests/search-params.spec.ts b/e2e/solid-start/basic/tests/search-params.spec.ts index 64ef39919ed..74c1aa54dc9 100644 --- a/e2e/solid-start/basic/tests/search-params.spec.ts +++ b/e2e/solid-start/basic/tests/search-params.spec.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test' import { test } from '@tanstack/router-e2e-utils' -import { isSpaMode } from '../tests/utils/isSpaMode' +import { isSpaMode } from 'tests/utils/isSpaMode' +import { isPrerender } from './utils/isPrerender' import type { Response } from '@playwright/test' function expectRedirect(response: Response | null, endsWith: string) { @@ -26,9 +27,11 @@ test.describe('/search-params/loader-throws-redirect', () => { page, }) => { const response = await page.goto('/search-params/loader-throws-redirect') - if (!isSpaMode) { + + if (!isSpaMode && !isPrerender) { expectRedirect(response, '/search-params/loader-throws-redirect?step=a') } + await expect(page.getByTestId('search-param')).toContainText('a') expect(page.url().endsWith('/search-params/loader-throws-redirect?step=a')) }) @@ -50,7 +53,7 @@ test.describe('/search-params/default', () => { page, }) => { const response = await page.goto('/search-params/default') - if (!isSpaMode) { + if (!isSpaMode && !isPrerender) { expectRedirect(response, '/search-params/default?default=d1') } await expect(page.getByTestId('search-default')).toContainText('d1') diff --git a/e2e/solid-start/basic/tests/utils/isPrerender.ts b/e2e/solid-start/basic/tests/utils/isPrerender.ts new file mode 100644 index 00000000000..d5d991d4545 --- /dev/null +++ b/e2e/solid-start/basic/tests/utils/isPrerender.ts @@ -0,0 +1 @@ +export const isPrerender: boolean = process.env.MODE === 'prerender' diff --git a/e2e/solid-start/basic/vite.config.ts b/e2e/solid-start/basic/vite.config.ts index 60c1947e40c..9f4a744b19f 100644 --- a/e2e/solid-start/basic/vite.config.ts +++ b/e2e/solid-start/basic/vite.config.ts @@ -3,6 +3,7 @@ import tsConfigPaths from 'vite-tsconfig-paths' import { tanstackStart } from '@tanstack/solid-start/plugin/vite' import viteSolid from 'vite-plugin-solid' import { isSpaMode } from './tests/utils/isSpaMode' +import { isPrerender } from './tests/utils/isPrerender' const spaModeConfiguration = { enabled: true, @@ -11,6 +12,20 @@ const spaModeConfiguration = { }, } +const prerenderConfiguration = { + enabled: true, + filter: (page: { path: string }) => + ![ + '/this-route-does-not-exist', + '/redirect', + '/i-do-not-exist', + '/not-found/via-beforeLoad', + '/not-found/via-loader', + '/search-params/default', + ].some((p) => page.path.includes(p)), + maxRedirects: 100, +} + export default defineConfig({ server: { port: 3000, @@ -22,6 +37,7 @@ export default defineConfig({ // @ts-ignore we want to keep one test with verboseFileRoutes off even though the option is hidden tanstackStart({ spa: isSpaMode ? spaModeConfiguration : undefined, + prerender: isPrerender ? prerenderConfiguration : undefined, }), viteSolid({ ssr: true }), ], diff --git a/packages/start-plugin-core/src/prerender.ts b/packages/start-plugin-core/src/prerender.ts index 8c22633b374..fdf25b942b1 100644 --- a/packages/start-plugin-core/src/prerender.ts +++ b/packages/start-plugin-core/src/prerender.ts @@ -321,4 +321,4 @@ export async function writeBundleToDisk({ await fsp.writeFile(fullPath, content) } -} +} \ No newline at end of file