Repository navigation
Node target: production build keeps installed void external, API-only build crashes, and the CF-only guard misses dependencies #416
Description
Problem
Four problems with target: 'node':
| What the user does | What happens | |
|---|---|---|
| a | Installs void from npm, builds a pages app, runs vp preview |
A ValidationError thrown in an action gives 500 |
| b | Builds an app with no pages/ folder |
vp build crashes |
| c | Uses a package that imports void/kv |
No guard error; the request fails later |
| d | Reads packages/void/AGENTS.md |
The CF-only list does not match the code |
a. The production build keeps an installed void external
A Node-target React pages app has an action that throws ValidationError from void/handler. With void installed (npm or pnpm), vp build then vp preview returns Internal Server Error for the form POST. The log shows the error was not handled:
ValidationError: Validation failed
at .../dist/ssr/assets/form.server-*.js:5:8
at handlePageMutation (.../dist/ssr/assets/app-*.js)
...
at async .../node_modules/void/dist/index.mjs {
errors: { email: 'Email already taken' }
}
The same app returns 200 in vp dev (after #413) and with a workspace-linked void. The built dist/ssr imports void/handler, void/sse, void/_env and @void/react at runtime. So the user code's ValidationError class is a different copy from the one Void checks with instanceof. Production also needs these dev dependencies: started with only hono, react and @hono/node-server present, it fails with ERR_MODULE_NOT_FOUND '@void/react'.
b. An API-only build crashes
dist/ssr/app.js 84.65 kB │ gzip: 23.52 kB
✓ built in 21ms
error during build:
TypeError: Cannot destructure property 'logger' of 'environment' as it is undefined.
at buildEnvironment (.../vite/node/chunks/node.js)
at Object.build (.../vite/node/chunks/node.js)
at BasicMinimalPluginContext.buildApp (.../void/dist/index.mjs)
The ssr output is written, then the build fails. This happens with a workspace link too.
c. A dependency that imports void/kv skips the guard
The guard turns import 'void/kv' in app code into a clear error. A package in node_modules that imports void/kv stays external, and Node loads it without going through Vite. With a workspace link:
- dev: the app's own
void/kvroute fails withtarget: 'void/kv' is not available with target 'node'. The package's route returnsenv: Cloudflare env is unavailable. Use void runtime bindings inside Nuxt/SvelteKit/Analog server request handlers or Worker handlers. - build: no guard error;
dist/ssrkeepsimport ... from "xa-lib".
d. AGENTS.md lists the wrong imports
packages/void/AGENTS.md:40 says non-CF targets block void/ws. The code does not block it, and docs/reference/config.md:269 says it is not blocked on purpose. The code blocks void/sandbox, which the list leaves out.
Repro
a. Node-target pages app with void and @void/react installed from npm; a page action that throws new ValidationError({ email: 'Email already taken' }); vp build && vp preview; POST the form.
b.
// void.config.ts
import { defineConfig } from 'void/config';
export default defineConfig({ target: 'node' });
// routes/api/ping.ts (no pages/ folder)
import { defineHandler } from 'void';
export const GET = defineHandler(() => ({ ok: true }));Run vp build.
c. Add a package xa-lib in node_modules whose index.js imports { kv } from 'void/kv' and calls kv.put. Import it from a route. Run vp dev and request the route; run vp build.
Cause
- a.
packages/void/src/plugin-node-target.ts:144-151inlinesvoid(ssr.noExternal) only when the command is notbuild. In the build, Vite externalizes a bare import that resolves intonode_modules. Void's generated code imports runtime files by path, so it gets a second copy. On main (:144-147) neither dev nor build inlines it. Note: addingnoExternal: ['void', /^@void\//]to the build alone fails, because Rolldown cannot resolvecloudflare:workersfromenv-raw. - b.
plugin-node-target.ts:180-185returnsbuild.ssr: trueand nobuilderfor API-only apps. Vite's CLI then uses the legacy builder, which creates only anssrenvironment.buildApp(:191-196) still runs: thessrbuild works, thenbuilder.build(builder.environments.client)(:195) getsundefined. The comment at:188-190says the hook does not run for API-only apps; that is no longer true. Same code on main (:187-192). - c. The guard is a
resolveIdhook (plugin-node-target.ts:118-126). Imports inside an external package never reach it. - d.
CF_ONLY_IMPORTSatplugin-node-target.ts:9-17againstpackages/void/AGENTS.md:40.
Suggested fix
- a. Bundle
voidand@void/*into the Node-target production build, and give Node a resolution forcloudflare:workersinenv-raw(a stub or alias). Add a packed-consumer check for a Node-target build with an installedvoid. - b. In
buildApp, build only the environments that exist (skipclientwhen it is missing), and fix the comment. Add a test that builds an API-only Node-target app. - c. Make the CF-only runtime modules throw the same "not available with target 'node'" error on first use under Node, or scan the build's dependencies for these imports.
- d. Make the
AGENTS.mdlist matchCF_ONLY_IMPORTS.
Evidence
- a. Runs with npm and pnpm installs of the packed
void, before and after fix(void): make PostgreSQL and MySQL void/db work across frameworks, builds and deploys #413's installed-void change: preview 500 both times; dev 500 before and 200 after; workspace link 200 in dev and preview. - b. Reproduced with the head of fix(void): make PostgreSQL and MySQL void/db work across frameworks, builds and deploys #413 and a workspace link (Vite+ 1.1.0). The code is the same on main.
- c. Reproduced with the head of fix(void): make PostgreSQL and MySQL void/db work across frameworks, builds and deploys #413 and a workspace link, in dev and build.
- d. Code read.
- fix(void): make PostgreSQL and MySQL void/db work across frameworks, builds and deploys #413 makes dev inline
void(part of a). It does not change b, c or d.
🤖 Generated with Claude Code
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
fix(void): bundle installed void packages for the Node target and Sve…
fix(void): bundle installed void packages for the Node target and Sve…
fix(void): bundle installed void packages for the Node target and Sve…
fix(void): bundle installed void packages for the Node target and Sve…
fix(void): bundle installed void packages for the Node target and Sve…