Skip to content

Commit 0cfc87b

Browse files
authored
Merge pull request #395 from everton-dgn/fix/server-functions-optimize-deps
fix: pre-bundle the server-function client runtime
2 parents e647ba3 + 2a714d4 commit 0cfc87b

3 files changed

Lines changed: 149 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@solidjs/vite-plugin': patch
3+
---
4+
5+
With `serverFunctions` on, the dev server now pre-bundles the server-function client runtime even without `components` (`@solidjs/web/server-functions`, or `runtime.client` when it names a package), so a cold cache no longer re-optimizes and reloads on the first `"use server"` module. In Vitest browser mode that reload showed up as "Vite unexpectedly reloaded a test".

‎examples/start-ssr/test/components-warning.mjs‎

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@
1313
// derived serverComponents flag) matches `true` exactly, and stays off
1414
// when the option is off,
1515
// - under full SSR start mode `'external'` is redundant but harmless:
16-
// silent, exactly like `true`.
16+
// silent, exactly like `true`,
17+
// - independent of server components: `serverFunctions` alone (with or
18+
// without `ssr`) pre-bundles the server-function client runtime under
19+
// `serve` (the dependency scan never runs the transform that adds its
20+
// import), listed once when components also ask for it; not without
21+
// `serverFunctions`, not in build, and a custom `runtime.client` only as
22+
// a package specifier, beside the components entries when both are on.
1723
//
1824
// Pure resolveConfig — no dev server, no browser. Requires the plugin built
1925
// (pnpm build at the repo root). Usage: node test/components-warning.mjs
@@ -81,6 +87,14 @@ async function resolveWith(solidOptions) {
8187
config.optimizeDeps.include.includes('@solidjs/web/frames') &&
8288
config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
8389
);
90+
const runtimeEntries = config.optimizeDeps.include.filter(
91+
(dep) => dep === '@solidjs/web/server-functions',
92+
);
93+
record(
94+
'enabled: server-function client runtime listed once',
95+
runtimeEntries.length === 1,
96+
config.optimizeDeps.include.join(', '),
97+
);
8498
}
8599

86100
// ---- `'external'` in the identical config: silent, still enabled ---------
@@ -123,6 +137,84 @@ async function resolveWith(solidOptions) {
123137
config.optimizeDeps.include.includes('@solidjs/web/performance-tracks'),
124138
config.optimizeDeps.include.join(', '),
125139
);
140+
// Compiled server-function references import the client runtime, but the
141+
// dependency scanner never runs the transform that adds that import.
142+
// Without the entry, the first "use server" module the browser loads
143+
// discovers it late: re-optimize and full reload.
144+
record(
145+
'serverFunctions without components: server-function client runtime pre-bundled',
146+
config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
147+
config.optimizeDeps.include.join(', '),
148+
);
149+
}
150+
151+
// ---- serverFunctions without ssr: same entry -----------------------------
152+
{
153+
const { config } = await resolveWith({ serverFunctions: true });
154+
record(
155+
'serverFunctions without ssr: server-function client runtime pre-bundled',
156+
config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
157+
config.optimizeDeps.include.join(', '),
158+
);
159+
}
160+
161+
// ---- serverFunctions off: no server-function runtime pre-bundle -----------
162+
{
163+
const { config } = await resolveWith({ ssr: true });
164+
record(
165+
'serverFunctions off: server-function client runtime not pre-bundled',
166+
!config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
167+
config.optimizeDeps.include.join(', '),
168+
);
169+
}
170+
171+
// ---- custom runtime: only a package specifier is pre-bundled --------------
172+
{
173+
const client = '@acme/server-runtime/client';
174+
const { config } = await resolveWith({
175+
ssr: true,
176+
serverFunctions: { runtime: { server: '@acme/server-runtime/server', client } },
177+
});
178+
record(
179+
'custom runtime (package): runtime.client pre-bundled instead of the default',
180+
config.optimizeDeps.include.includes(client) &&
181+
!config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
182+
config.optimizeDeps.include.join(', '),
183+
);
184+
}
185+
{
186+
const client = '@acme/server-runtime/client';
187+
const { config } = await resolveWith({
188+
ssr: true,
189+
serverFunctions: {
190+
components: true,
191+
runtime: { server: '@acme/server-runtime/server', client },
192+
},
193+
});
194+
record(
195+
'components + custom runtime (package): runtime.client beside the components entries',
196+
config.optimizeDeps.include.includes(client) &&
197+
config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
198+
config.optimizeDeps.include.join(', '),
199+
);
200+
}
201+
for (const client of [
202+
'./src/sf-client.ts',
203+
'/src/sf-client.ts',
204+
'virtual:sf-client',
205+
'@/sf-client',
206+
'~/sf-client',
207+
]) {
208+
const { config } = await resolveWith({
209+
ssr: true,
210+
serverFunctions: { runtime: { server: client, client } },
211+
});
212+
record(
213+
`custom runtime (${client}): neither it nor the default pre-bundled`,
214+
!config.optimizeDeps.include.includes(client) &&
215+
!config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
216+
config.optimizeDeps.include.join(', '),
217+
);
126218
}
127219

128220
// ---- build: optimizeDeps is serve-only, the entries stay out --------------
@@ -138,6 +230,21 @@ async function resolveWith(solidOptions) {
138230
config.optimizeDeps.include.join(', '),
139231
);
140232
}
233+
{
234+
const config = await resolveConfig(
235+
{
236+
root: exampleDir,
237+
configFile: false,
238+
plugins: [solidPlugin({ ssr: true, serverFunctions: true })],
239+
},
240+
'build',
241+
);
242+
record(
243+
'build: server-function client runtime not in optimizeDeps.include',
244+
!config.optimizeDeps.include.includes('@solidjs/web/server-functions'),
245+
config.optimizeDeps.include.join(', '),
246+
);
247+
}
141248

142249
// ---- full SSR start mode: `'external'` is redundant but harmless ---------
143250
{

‎src/index.ts‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,18 @@ function findPackageDir(name: string, root: string): string | undefined {
582582
}
583583
}
584584

585+
/**
586+
* Whether `id` looks like a package specifier (`name`, `name/sub`,
587+
* `@scope/name/sub`), by syntax alone. Relative and absolute paths, ids with a
588+
* protocol or drive (`virtual:`, `node:`, `C:`), subpath imports (`#x`) and
589+
* aliases like `~/x` or `@/x` don't.
590+
*/
591+
function isPackageSpecifier(id: string): boolean {
592+
if (!id || /^[./\\#~\0]/.test(id) || id.includes(':')) return false;
593+
if (id.startsWith('@')) return /^@[^/]+\/[^/]/.test(id);
594+
return true;
595+
}
596+
585597
function getJestDomExport(setupFiles: string[], root: string) {
586598
if (setupFiles?.some((file) => /jest-dom/.test(file))) return undefined;
587599

@@ -1057,6 +1069,21 @@ export default function solidPlugin(options: Partial<Options> = {}): Plugin[] {
10571069
const serverComponentsOption =
10581070
typeof options.serverFunctions === 'object' ? options.serverFunctions.components : undefined;
10591071
const serverComponents = !!serverComponentsOption;
1072+
// The client runtime compiled server-function references import (the
1073+
// server-functions plugin's own default unless `runtime` is set), kept only
1074+
// when it names a package. A relative path or an alias is app source: the
1075+
// optimizer would bundle a copy nothing loads or, for `@/x`, serve that copy
1076+
// so edits stop showing up. A virtual id or `/src/x` doesn't resolve for
1077+
// `include` and only warns.
1078+
const serverFunctionsClientRuntime = options.serverFunctions
1079+
? typeof options.serverFunctions === 'object' && options.serverFunctions.runtime
1080+
? options.serverFunctions.runtime.client
1081+
: '@solidjs/web/server-functions'
1082+
: undefined;
1083+
const serverFunctionsClientDep =
1084+
serverFunctionsClientRuntime && isPackageSpecifier(serverFunctionsClientRuntime)
1085+
? serverFunctionsClientRuntime
1086+
: undefined;
10601087
// `start: true` is sugar for the empty options bag — one start mode,
10611088
// two spellings — so normalize here and let everything downstream see a
10621089
// single shape (`false` behaves exactly like omission).
@@ -1483,6 +1510,15 @@ export default function solidPlugin(options: Partial<Options> = {}): Plugin[] {
14831510
...(command === 'serve' && serverComponents
14841511
? ['@solidjs/web/frames', '@solidjs/web/server-functions']
14851512
: []),
1513+
// Plain `serverFunctions` needs the runtime too: the scanner never
1514+
// runs the transform that adds its import, so the first "use server"
1515+
// module forces the same re-optimize + full reload (in Vitest
1516+
// browser mode, "Vite unexpectedly reloaded a test").
1517+
...(command === 'serve' &&
1518+
serverFunctionsClientDep &&
1519+
!(serverComponents && serverFunctionsClientDep === '@solidjs/web/server-functions')
1520+
? [serverFunctionsClientDep]
1521+
: []),
14861522
// The attribution engine and the Chrome performance-tracks
14871523
// recorder are subpaths the scanner only sees when the app's own
14881524
// graph imports them; a consumer it never crawls (a linked

0 commit comments

Comments
 (0)