From 8496647543ec30055c2bdcb61bf796f2483643d7 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 8 Jun 2026 16:34:24 +0100 Subject: [PATCH] fix: improve reproduction --- src/app/assets/global.css | 3 ++ src/app/entry.ts | 11 ++++++ src/entry-server.ts | 11 ++++-- src/renderer.ts | 10 ++++++ vite.config.ts | 75 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/app/assets/global.css create mode 100644 src/app/entry.ts create mode 100644 src/renderer.ts diff --git a/src/app/assets/global.css b/src/app/assets/global.css new file mode 100644 index 0000000..382b240 --- /dev/null +++ b/src/app/assets/global.css @@ -0,0 +1,3 @@ +:root { + --global: 'global'; +} diff --git a/src/app/entry.ts b/src/app/entry.ts new file mode 100644 index 0000000..6c3c8c6 --- /dev/null +++ b/src/app/entry.ts @@ -0,0 +1,11 @@ +// App entry that imports virtual modules served by `repro:virtual-fs` +// in `vite.config.ts`. The Vite ssr env resolves these correctly via +// the plugin pipeline; the prerender Nitro does not. +// @ts-expect-error virtual +import virtualCss from '#virtual/css' +// @ts-expect-error virtual +import virtualEntry from '#virtual/entry' + +export default async function app () { + return `entry=${virtualEntry} css.length=${virtualCss.length}` +} diff --git a/src/entry-server.ts b/src/entry-server.ts index 244640c..a0f5a31 100644 --- a/src/entry-server.ts +++ b/src/entry-server.ts @@ -1,10 +1,17 @@ import { useRuntimeConfig } from 'nitro/runtime-config' +import { render } from './renderer.ts' export default { - async fetch (_request: Request): Promise { + async fetch (request: Request): Promise { + const url = new URL(request.url) const config = useRuntimeConfig() return new Response( - `
runtimeConfig=${JSON.stringify(config, null, 2)}
`, + [ + ``, + `

${url.pathname}

`, + `
runtimeConfig=${JSON.stringify(config, null, 2)}
`, + `
renderer=${await render()}
`, + ].join(''), { headers: { 'content-type': 'text/html; charset=utf-8' } }, ) }, diff --git a/src/renderer.ts b/src/renderer.ts new file mode 100644 index 0000000..71d4d32 --- /dev/null +++ b/src/renderer.ts @@ -0,0 +1,10 @@ +// Renderer module wired into the prerender Nitro as `renderer.handler` +// via the `repro:prerender-handler` plugin in `vite.config.ts`. The +// prerender Nitro builds with standalone rolldown, so any static +// import chain that lands on a virtual id only resolvable by a Vite +// plugin will fail in the prerender bundle. +import app from './app/entry.ts' + +export async function render () { + return `app entry: ${await app()}` +} diff --git a/vite.config.ts b/vite.config.ts index 38b1cae..077016e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,10 +1,85 @@ +import { fileURLToPath } from 'node:url' import { defineConfig } from 'vite' import { nitro } from 'nitro/vite' +import type { Nitro } from 'nitro/types' + +const rendererEntry = fileURLToPath(new URL('./src/renderer.ts', import.meta.url)) +const globalCss = fileURLToPath(new URL('./src/app/assets/global.css', import.meta.url)) export default defineConfig({ plugins: [ nitro({ runtimeConfig: { public: { probe: 'hello-from-runtime-config' } }, + prerender: { routes: ['/', '/prerendered'] }, }), + { + // A Vite plugin that owns a custom virtual-module scheme, scoped + // to server-consumer envs. The ssr env build resolves these via + // the plugin pipeline; the deployable's nitro env rebundle does + // the same. The prerender Nitro does not, because it builds with + // standalone rolldown (see https://github.com/nitrojs/nitro/pull/4152 + // for the related `nitro/*` proxy plumbing in service envs). + name: 'repro:virtual-fs', + enforce: 'pre', + applyToEnvironment: env => env.config.consumer === 'server', + resolveId: { + filter: { id: /^#virtual\// }, + handler (id) { return '\0' + id }, + }, + load: { + filter: { id: /^\0#virtual\// }, + handler (id) { + if (id === '\0#virtual/entry') return 'export default "live-entry-content"' + if (id === '\0#virtual/css') { + return `import ${JSON.stringify(globalCss)}\nexport default "css-loaded"` + } + }, + }, + }, + { + // Mirror of the proxy plumbing landed in + // https://github.com/nitrojs/nitro/pull/4152: service environments + // must not bundle their own copy of `nitro/*` runtime modules, + // otherwise the ssr entry gets stub instances instead of sharing + // the Nitro environment's runtime state. The PR externalises + // `/^nitro(\/|$)/` from service envs in prod and proxies them via + // `__VITE_ENVIRONMENT_RUNNER_IMPORT__` in dev. + // + // In dev, Vite's env-runner short-circuits externalised + // bare-specifier imports to Node before the proxy plugin runs. + // `noExternal` here forces `nitro/*` through the plugin pipeline + // so the proxy can claim them. Without this, `useRuntimeConfig()` + // in dev returns `{ app: {}, nitro: {} }` (the stub at + // `nitro/dist/runtime/virtual/runtime-config.mjs`). + name: 'repro:noExternal-nitro', + configEnvironment (name) { + if (name !== 'ssr') return + return { resolve: { noExternal: [/^nitro(\/|$)/] } } + }, + }, + { + // Wire `src/renderer.ts` as the prerender Nitro's `/**` handler + // via a `.nitro` module hook (the shape `nitro/vite` scans for in + // `nitro/dist/vite.mjs`). + // + // The prerender Nitro spins up via + // `createNitro(prerendererConfig)` and builds with standalone + // rolldown when the main builder is `vite`, so none of the Vite + // plugins above reach it. The static chain + // `renderer.ts -> app/entry.ts -> #virtual/css` is unresolved in + // the prerender bundle; rolldown leaves the bare specifier in the + // output and Node errors at prerender runtime with + // `ERR_PACKAGE_IMPORT_NOT_DEFINED`. See README for the trace. + // + // Comment this plugin out to make the build succeed (without any + // prerendering — every prerender route 404s instead of 500ing). + name: 'repro:prerender-handler', + nitro: (nitroInstance: Nitro) => { + nitroInstance.hooks.hook('prerender:config', (prerendererConfig) => { + if (prerendererConfig.renderer) return + prerendererConfig.renderer = { handler: rendererEntry } + }) + }, + }, ], }) -- 2.51.2