From a0ebb4d4a76500d49146bea30698127221b5f585 Mon Sep 17 00:00:00 2001 From: Yangshun Date: Thu, 29 Oct 2026 15:31:05 +0800 Subject: [PATCH] fix(pages): prebundle shared client runtime --- packages/react/src/plugin-pages.ts | 7 ++-- .../pages-client-prebundle.test.ts | 7 ++++ packages/solid/src/plugin-pages.ts | 10 ++++-- .../pages-client-prebundle.test.ts | 7 ++++ packages/svelte/src/plugin-pages.ts | 11 ++++-- .../pages-client-prebundle.test.ts | 7 ++++ .../svelte/test/unit/plugin-pages.test.ts | 9 ++++- packages/vue/src/plugin-pages.ts | 6 ++-- .../pages-client-prebundle.test.ts | 7 ++++ test/pages-deps-contract.ts | 36 +++++++++++++++++++ 10 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 packages/react/test/integration/pages-client-prebundle.test.ts create mode 100644 packages/solid/test/integration/pages-client-prebundle.test.ts create mode 100644 packages/svelte/test/integration/pages-client-prebundle.test.ts create mode 100644 packages/vue/test/integration/pages-client-prebundle.test.ts create mode 100644 test/pages-deps-contract.ts diff --git a/packages/react/src/plugin-pages.ts b/packages/react/src/plugin-pages.ts index 372098274..fd801e85d 100644 --- a/packages/react/src/plugin-pages.ts +++ b/packages/react/src/plugin-pages.ts @@ -166,10 +166,9 @@ export function pagesPlugin(pagesDir: string, options?: PagesPluginOptions): Plu }, optimizeDeps: { exclude: ['@void/react'], - // Pre-include React entries that are discovered late via dynamic - // imports during SPA navigation, avoiding a full-page reload - // from Vite's dep optimizer. - include: CLIENT_OPTIMIZE_DEPS, + // The virtual client entry is absent from the initial dependency crawl. + // Include its runtime imports up front to avoid optimizer reloads. + include: [...CLIENT_OPTIMIZE_DEPS, 'void/pages-client', 'void/pages-prefetch'], }, ssr: { optimizeDeps: { diff --git a/packages/react/test/integration/pages-client-prebundle.test.ts b/packages/react/test/integration/pages-client-prebundle.test.ts new file mode 100644 index 000000000..4876c25bc --- /dev/null +++ b/packages/react/test/integration/pages-client-prebundle.test.ts @@ -0,0 +1,7 @@ +import { resolve } from 'node:path'; +import { registerPagesDependencyTests } from '../../../../test/pages-deps-contract.ts'; +import { pagesPlugin } from '../../src/plugin-pages.ts'; + +registerPagesDependencyTests(resolve(import.meta.dirname, '../..'), (pagesDir) => [ + pagesPlugin(pagesDir), +]); diff --git a/packages/solid/src/plugin-pages.ts b/packages/solid/src/plugin-pages.ts index 74bfc4afe..157e3eb45 100644 --- a/packages/solid/src/plugin-pages.ts +++ b/packages/solid/src/plugin-pages.ts @@ -141,8 +141,14 @@ export function pagesPlugin(pagesDir: string, options?: PagesPluginOptions): Plu if (isDev) { return { optimizeDeps: { - // Pre-include Solid entries discovered late via dynamic imports - include: ['solid-js', 'solid-js/web', 'solid-js/store'], + // Include imports from the virtual client entry before the first request. + include: [ + 'solid-js', + 'solid-js/web', + 'solid-js/store', + 'void/pages-client', + 'void/pages-prefetch', + ], }, }; } diff --git a/packages/solid/test/integration/pages-client-prebundle.test.ts b/packages/solid/test/integration/pages-client-prebundle.test.ts new file mode 100644 index 000000000..4876c25bc --- /dev/null +++ b/packages/solid/test/integration/pages-client-prebundle.test.ts @@ -0,0 +1,7 @@ +import { resolve } from 'node:path'; +import { registerPagesDependencyTests } from '../../../../test/pages-deps-contract.ts'; +import { pagesPlugin } from '../../src/plugin-pages.ts'; + +registerPagesDependencyTests(resolve(import.meta.dirname, '../..'), (pagesDir) => [ + pagesPlugin(pagesDir), +]); diff --git a/packages/svelte/src/plugin-pages.ts b/packages/svelte/src/plugin-pages.ts index cba27c0ec..e8843c1ac 100644 --- a/packages/svelte/src/plugin-pages.ts +++ b/packages/svelte/src/plugin-pages.ts @@ -14,8 +14,15 @@ const SVELTE_OPTIMIZE_DEPS = { // Exclude @void/svelte so the virtual module and user components share // the same Svelte context (identity). exclude: ['@void/svelte'], - // Pre-include Svelte so it's not discovered late via dynamic imports. - include: ['svelte', 'svelte/store', 'svelte/internal', 'svelte/internal/client'], + // Include imports from the virtual client entry before the first request. + include: [ + 'svelte', + 'svelte/store', + 'svelte/internal', + 'svelte/internal/client', + 'void/pages-client', + 'void/pages-prefetch', + ], }; export type PrefetchConfig = { diff --git a/packages/svelte/test/integration/pages-client-prebundle.test.ts b/packages/svelte/test/integration/pages-client-prebundle.test.ts new file mode 100644 index 000000000..4876c25bc --- /dev/null +++ b/packages/svelte/test/integration/pages-client-prebundle.test.ts @@ -0,0 +1,7 @@ +import { resolve } from 'node:path'; +import { registerPagesDependencyTests } from '../../../../test/pages-deps-contract.ts'; +import { pagesPlugin } from '../../src/plugin-pages.ts'; + +registerPagesDependencyTests(resolve(import.meta.dirname, '../..'), (pagesDir) => [ + pagesPlugin(pagesDir), +]); diff --git a/packages/svelte/test/unit/plugin-pages.test.ts b/packages/svelte/test/unit/plugin-pages.test.ts index 96a46443d..a0e08fdfa 100644 --- a/packages/svelte/test/unit/plugin-pages.test.ts +++ b/packages/svelte/test/unit/plugin-pages.test.ts @@ -124,7 +124,14 @@ describe('pagesPlugin dev HMR', () => { expect(config).toMatchObject({ optimizeDeps: { exclude: ['@void/svelte'], - include: ['svelte', 'svelte/store', 'svelte/internal', 'svelte/internal/client'], + include: [ + 'svelte', + 'svelte/store', + 'svelte/internal', + 'svelte/internal/client', + 'void/pages-client', + 'void/pages-prefetch', + ], }, environments: { void_worker: { diff --git a/packages/vue/src/plugin-pages.ts b/packages/vue/src/plugin-pages.ts index ea609ec2f..b4c74a230 100644 --- a/packages/vue/src/plugin-pages.ts +++ b/packages/vue/src/plugin-pages.ts @@ -272,10 +272,8 @@ export function pagesPlugin(pagesDir: string, options?: PagesPluginOptions): Rea if (isDev) { return { optimizeDeps: { - // Pre-include Vue so it's not discovered late via dynamic - // imports during SPA navigation, avoiding a full-page reload - // from Vite's dep optimizer. - include: ['vue'], + // Include imports from the virtual client entry before the first request. + include: ['vue', 'void/pages-client', 'void/pages-prefetch'], }, }; } diff --git a/packages/vue/test/integration/pages-client-prebundle.test.ts b/packages/vue/test/integration/pages-client-prebundle.test.ts new file mode 100644 index 000000000..f40d744dd --- /dev/null +++ b/packages/vue/test/integration/pages-client-prebundle.test.ts @@ -0,0 +1,7 @@ +import { resolve } from 'node:path'; +import { registerPagesDependencyTests } from '../../../../test/pages-deps-contract.ts'; +import { pagesPlugin } from '../../src/plugin-pages.ts'; + +registerPagesDependencyTests(resolve(import.meta.dirname, '../..'), (pagesDir) => + pagesPlugin(pagesDir), +); diff --git a/test/pages-deps-contract.ts b/test/pages-deps-contract.ts new file mode 100644 index 000000000..44f9e9a55 --- /dev/null +++ b/test/pages-deps-contract.ts @@ -0,0 +1,36 @@ +import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { optimizeDeps, resolveConfig, type Plugin } from 'vite'; +import { expect, it } from 'vite-plus/test'; + +export function registerPagesDependencyTests( + root: string, + plugins: (pagesDir: string) => ReadonlyArray, +): void { + it('prebundles the Pages client runtime before the first browser request', async () => { + const directory = mkdtempSync(join(root, 'test', '__tmp_pages_deps_')); + const pagesDir = join(directory, 'pages'); + mkdirSync(pagesDir); + try { + const config = await resolveConfig( + { + root: directory, + configFile: false, + logLevel: 'error', + cacheDir: join(directory, '.vite'), + plugins: [...plugins(pagesDir)], + // The generated virtual client entry is absent from the initial dependency crawl. + optimizeDeps: { noDiscovery: true }, + }, + 'serve', + ); + const metadata = await optimizeDeps(config, true, true); + for (const dependency of ['void/pages-client', 'void/pages-prefetch']) { + expect(metadata.optimized[dependency], dependency).toBeDefined(); + expect(existsSync(metadata.optimized[dependency].file)).toBe(true); + } + } finally { + rmSync(directory, { recursive: true, force: true }); + } + }); +}