Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/void/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ generated entries must preserve the module-boundary rules below.
- **Import redirection**: The routing plugin's `resolveId` hook redirects bare `"void"` imports to `void/handler` (the lightweight subpath). Without this, route files pull the entire plugin (vite, @cloudflare/vite-plugin, lightningcss) into the Worker bundle. Uses `import.meta.resolve()` to compute absolute paths at plugin init. Class A frameworks and Astro get the same redirect for their server environments from `createBareVoidRedirectPlugin`, in dev and build: their dev trigger dispatch imports every cron and queue file into workerd, so one `import { defineScheduled } from 'void'` would otherwise break every trigger (and, in Astro, every request).

- **Installed runtime module identity**: Keep `void` server runtime subpaths out of Vite dependency optimization. Generated entries import runtime modules by resolved file path; prebundling a bare `void/kv` creates a second `envContext` and loses request bindings. Workspace symlinks can hide this defect. The shared runtime-optimizer plugin excludes the package in server environments on every target while preserving client optimization; packed-consumer checks exercise actual npm/pnpm installations. Vite never discovers bare imports from inside node_modules, so the same plugin prebundles Void's CommonJS dependencies (`void > email-validator`, the void/db driver) in every environment whose resolve conditions contain `workerd`: Void's worker, class A frameworks and Astro. Node environments get only the exclusion. SvelteKit's dev server runs server code in Vite's Node `ssr` graph, which externalizes a bare import that resolves into node_modules before any plugin resolves it, so an installed `void/db` would load the package's D1 runtime. The SvelteKit plugin sets `ssr.noExternal: ['void']` during serve; Vite matches it by package name, so the whole package moves into that graph together and the injected `void/_env` shares void/db's env module. Like the routing plugin, it sends bare `void` to `void/handler` in server environments. In SvelteKit dev for PostgreSQL and MySQL apps, the plugin also inlines installed ES module packages that depend on void, so their `void/db` is the app's. In a D1 app, and for a CommonJS package or one that does not list void, the package stays external and loads its own copy of the env modules. `envContext` and the request scope therefore live on `globalThis` under versioned `Symbol.for` keys, like `authContext`, so every copy reads the current request; this also covers modules that a dependency-optimizer reload evaluates again during a request. Change a key when its store shape changes, because two Void versions in one process share it.
- **Plugin entry imports**: Every `vp dev` and `vp build` imports the plugin entry before it starts. Import dependencies that only some apps or commands use (feature codegen, CLI-only helpers) where they are used, so other apps don't load them at startup. `test/unit/plugin-entry-imports.test.ts` guards this; add a mock there when you move a heavy import out.
- **pnpm scaffold build policy**: Keep `create-void` and `void init` aligned with the repository: `better-sqlite3: false` uses version 13's bundled Node-API binaries; `esbuild`, `sharp`, and `workerd` have approved builds. Do not require an unnecessary node-gyp rebuild in fresh applications.
- **Scaffold dependency installation**: Vite+ projects install through local/global `vp`, bootstrapping with `npx` when neither is available. Preserve existing package-manager pins and lockfiles; fresh Vite+ projects select pnpm through the build-policy YAML without adding a version pin. Plain Vite projects use their selected package manager. Do not run a global package manager directly for Vite+ installs.
- **Prerender discovery and environment validation**: Only `POST /__void/prerender-paths` bypasses whole-schema boot validation, because path discovery runs without remote production secrets. Discovery answers only when the build-time runner marks the request (the `__VOID_PRERENDER` Miniflare binding on Cloudflare, the Hono env argument on Node/Bun/Deno), never on a deployed app or dev server. Keep validation on readiness, application requests, page rendering, and event handlers.
Expand Down
2 changes: 1 addition & 1 deletion packages/void/src/plugin-ai-setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getApiUrl, getProxyUrl, getToken } from './cli/auth.ts';
import { ensurePlatformProxyAccess } from './cli/cf-access.ts';
import { printSuccess } from './cli/output.ts';
import { readProjectConfig } from './cli/project.ts';
import { voidWarn, voidError } from './log.ts';
import { scopedAccessCredentials } from './shared/cf-access.ts';
Expand Down Expand Up @@ -97,6 +96,7 @@ export async function runAiSetupPrompt(
const { PlatformClient } = await import('./cli/client.ts');
const { attachSelectedPlatform, writeProjectConfig } = await import('./cli/project.ts');
const { inferDefaultSlug } = await import('./cli/resolve-project.ts');
const { printSuccess } = await import('./cli/output.ts');

const client = new PlatformClient(token, { apiUrl });

Expand Down
4 changes: 3 additions & 1 deletion packages/void/src/plugin-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
type AssetRoutingPolicy,
} from './asset-routing.ts';
import { loadVoidAuthConfig } from './auth/config.ts';
import { generateVoidAuthDatabaseModule, generateVoidAuthDrizzleSchema } from './auth/drizzle.ts';
import { lintRoutingPhases, mergeRoutingRules } from './cli/headers.ts';
import { stampClientAssetCachePolicy } from './client-asset-manifest.ts';
import { findEnvFileForPaths } from './codegen/env-types.ts';
Expand Down Expand Up @@ -266,6 +265,9 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug
return;
}

// Better Auth's schema helpers load zod and most of better-auth; only auth apps need them.
const { generateVoidAuthDatabaseModule, generateVoidAuthDrizzleSchema } =
await import('./auth/drizzle.ts');
const authRuntime: VoidAuthRuntimeOptions = {
dialect: compileOptions.auth.dialect,
providers: compileOptions.auth.providers,
Expand Down
27 changes: 27 additions & 0 deletions packages/void/test/unit/plugin-entry-imports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Every `vp dev`/`vp build` imports the plugin entry first, so auth codegen and CLI output
// must not load at startup.
import { expect, it, vi } from 'vite-plus/test';

const loaded = vi.hoisted((): Array<string> => []);

vi.mock('../../src/auth/drizzle.ts', () => {
loaded.push('src/auth/drizzle.ts');
return {};
});
vi.mock('../../src/cli/output.ts', () => {
loaded.push('src/cli/output.ts');
return {};
});
vi.mock('better-auth/db', () => {
loaded.push('better-auth/db');
return {};
});
vi.mock('terminal-link', () => {
loaded.push('terminal-link');
return {};
});

it('keeps auth codegen and CLI output out of the plugin entry', async () => {
await import('../../src/index.ts');
expect(loaded).toEqual([]);
});
Loading