From 4ba7b2799c46bf05da32dd659d31c1d414cf6350 Mon Sep 17 00:00:00 2001 From: honbin Date: Mon, 19 Oct 2026 16:33:00 +0900 Subject: [PATCH] Add regression test for Markdown-only Node static builds Markdown pages are implicit islands, so a Markdown-only site under target: 'node', output: 'static' relies on the islands plugin for the client input that prerendering needs. No playground covers this combination, so a regression there would go unnoticed. Add a Markdown-only playground (node-static-md) and an integration test that builds it and asserts the pages are prerendered and stay zero-JS. Verified against current main: the test passes on existing code, and fails (UNRESOLVED_ENTRY) when the islands client input is withheld. Co-Authored-By: Claude Opus 4.8 --- .../test/integration/node-static-md.test.ts | 44 +++++++++++++++++++ playground/node-static-md/package.json | 25 +++++++++++ playground/node-static-md/pages/index.md | 9 ++++ .../node-static-md/pages/posts/hello.md | 14 ++++++ playground/node-static-md/tsconfig.json | 14 ++++++ playground/node-static-md/vite.config.ts | 9 ++++ playground/node-static-md/void.config.ts | 6 +++ pnpm-lock.yaml | 37 ++++++++++++++++ test/integration-timings.ts | 1 + 9 files changed, 159 insertions(+) create mode 100644 packages/void/test/integration/node-static-md.test.ts create mode 100644 playground/node-static-md/package.json create mode 100644 playground/node-static-md/pages/index.md create mode 100644 playground/node-static-md/pages/posts/hello.md create mode 100644 playground/node-static-md/tsconfig.json create mode 100644 playground/node-static-md/vite.config.ts create mode 100644 playground/node-static-md/void.config.ts diff --git a/packages/void/test/integration/node-static-md.test.ts b/packages/void/test/integration/node-static-md.test.ts new file mode 100644 index 000000000..fb9ca0ffb --- /dev/null +++ b/packages/void/test/integration/node-static-md.test.ts @@ -0,0 +1,44 @@ +import { execSync } from 'node:child_process'; +import { existsSync, readFileSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { beforeAll, describe, expect, it } from 'vite-plus/test'; + +// A Markdown-only pages app still needs a client build: its closeBundle +// prerenders. Markdown pages are implicit islands, so the client input comes +// from the islands plugin; without it the build fails with UNRESOLVED_ENTRY. +const ROOT = join(import.meta.dirname, '..', '..', '..', '..'); +const PLAYGROUND = 'playground/node-static-md'; +const DIST = join(ROOT, PLAYGROUND, 'dist'); + +describe('playground/node-static-md build (Markdown-only, target: node, output: static)', () => { + beforeAll(() => { + const pg = join(ROOT, PLAYGROUND); + rmSync(join(pg, 'dist'), { recursive: true, force: true }); + rmSync(join(pg, '.void'), { recursive: true, force: true }); + execSync(`vp run --filter ./${PLAYGROUND} build`, { cwd: ROOT, stdio: 'pipe' }); + }); + + it('emits the Node SSR entry', () => { + expect(existsSync(join(DIST, 'ssr/app.js'))).toBe(true); + }); + + it('prerenders Markdown pages to HTML', () => { + const index = join(DIST, 'client/index.html'); + const post = join(DIST, 'client/posts/hello.html'); + expect(existsSync(index)).toBe(true); + expect(existsSync(post)).toBe(true); + + const postHtml = readFileSync(post, 'utf-8'); + expect(postHtml).toContain(''); + // The Markdown body and its syntax-highlighted code block were rendered. + expect(postHtml).toContain('Hello'); + expect(postHtml).toContain('shiki'); + }); + + it('keeps prerendered Markdown pages free of client JS bundles', () => { + const postHtml = readFileSync(join(DIST, 'client/posts/hello.html'), 'utf-8'); + // No framework hydration/entry bundle is referenced by a zero-JS page. + expect(postHtml).not.toContain('type="module"'); + expect(postHtml).not.toContain('src="/assets/'); + }); +}); diff --git a/playground/node-static-md/package.json b/playground/node-static-md/package.json new file mode 100644 index 000000000..4ea699d21 --- /dev/null +++ b/playground/node-static-md/package.json @@ -0,0 +1,25 @@ +{ + "name": "playground-node-static-md", + "private": true, + "type": "module", + "scripts": { + "build": "vp build", + "dev": "vp dev", + "preview": "vp preview" + }, + "dependencies": { + "@hono/node-server": "^2.1.5", + "hono": "^4.13.13", + "react": "catalog:", + "react-dom": "catalog:" + }, + "devDependencies": { + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@void/md": "workspace:*", + "@void/react": "workspace:*", + "vite": "catalog:", + "vite-plus": "catalog:", + "void": "workspace:*" + } +} diff --git a/playground/node-static-md/pages/index.md b/playground/node-static-md/pages/index.md new file mode 100644 index 000000000..4b41c8202 --- /dev/null +++ b/playground/node-static-md/pages/index.md @@ -0,0 +1,9 @@ +--- +title: Home +--- + +# Home + +A Markdown-only static site with no client-side islands. + +[Hello post](/posts/hello) diff --git a/playground/node-static-md/pages/posts/hello.md b/playground/node-static-md/pages/posts/hello.md new file mode 100644 index 000000000..8171f734a --- /dev/null +++ b/playground/node-static-md/pages/posts/hello.md @@ -0,0 +1,14 @@ +--- +title: Hello +--- + +# Hello + +The first paragraph of a Markdown post, with a fenced code block to exercise +syntax highlighting during the static build. + +```ts +export function add(a: number, b: number): number { + return a + b; +} +``` diff --git a/playground/node-static-md/tsconfig.json b/playground/node-static-md/tsconfig.json new file mode 100644 index 000000000..e5915466e --- /dev/null +++ b/playground/node-static-md/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noUnusedLocals": true, + "esModuleInterop": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowImportingTsExtensions": true + }, + "include": ["pages"] +} diff --git a/playground/node-static-md/vite.config.ts b/playground/node-static-md/vite.config.ts new file mode 100644 index 000000000..0a7703ecd --- /dev/null +++ b/playground/node-static-md/vite.config.ts @@ -0,0 +1,9 @@ +import { voidMarkdown } from '@void/md/plugin'; +import { voidReact } from '@void/react/plugin'; +import { defineConfig } from 'vite-plus'; +import { voidPlugin } from 'void'; + +// Markdown-only: every page is an implicit island with no client-side JS. +export default defineConfig({ + plugins: [voidPlugin(), voidReact(), voidMarkdown()], +}); diff --git a/playground/node-static-md/void.config.ts b/playground/node-static-md/void.config.ts new file mode 100644 index 000000000..519cdd881 --- /dev/null +++ b/playground/node-static-md/void.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'void/config'; + +export default defineConfig({ + target: 'node', + output: 'static', +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce42ec120..6f50642c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1437,6 +1437,43 @@ importers: specifier: workspace:* version: link:../../packages/void + playground/node-static-md: + dependencies: + '@hono/node-server': + specifier: ^2.1.5 + version: 2.1.5(hono@4.13.13) + hono: + specifier: ^4.13.13 + version: 4.13.13 + react: + specifier: 'catalog:' + version: 19.3.1 + react-dom: + specifier: 'catalog:' + version: 19.3.1(react@19.3.1) + devDependencies: + '@types/react': + specifier: ^19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: ^19.3.0 + version: 19.3.0(@types/react@19.3.0) + '@void/md': + specifier: workspace:* + version: link:../../packages/md + '@void/react': + specifier: workspace:* + version: link:../../packages/react + vite: + specifier: 'catalog:' + version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)(terser@5.51.2)(tsx@4.23.15)(typescript@7.0.2)(yaml@2.9.1)' + vite-plus: + specifier: 'catalog:' + version: 1.3.0(@astrojs/compiler-rs@0.5.2)(@types/node@26.6.5)(@vitest/ui@5.0.5)(esbuild@0.28.2)(jiti@2.7.0)(playwright@1.64.1)(sass-embedded@1.104.1)(sass@1.104.1)(svelte@5.57.2)(terser@5.51.2)(tsx@4.23.15)(typescript@7.0.2)(yaml@2.9.1) + void: + specifier: workspace:* + version: link:../../packages/void + playground/nuxt: dependencies: nuxt: diff --git a/test/integration-timings.ts b/test/integration-timings.ts index 4b952f41e..ca05e614c 100644 --- a/test/integration-timings.ts +++ b/test/integration-timings.ts @@ -24,6 +24,7 @@ export const integrationTimings: Readonly> = { 'packages/void/test/integration/mysql-test.test.ts': 11000, 'packages/void/test/integration/named-layouts.test.ts': 9939, 'packages/void/test/integration/nitro-worker.test.ts': 30000, + 'packages/void/test/integration/node-static-md.test.ts': 2000, 'packages/void/test/integration/node-static.test.ts': 13667, 'packages/void/test/integration/node-target.test.ts': 10833, 'packages/void/test/integration/nuxt.test.ts': 43000,