@solidjs/vite-plugin 3.0.0-next.31 → 3.0.0-next.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,13 +27,13 @@ Join [solid discord](https://discord.com/invite/solidjs) and check the [troubles
27
27
 
28
28
  ## Requirements
29
29
 
30
- This module is 100% ESM compatible and requires NodeJS `14.18.0` or later.
30
+ This module is 100% ESM compatible and requires Node.js `^20.19.0 || >=22.12.0`.
31
31
 
32
- You can check your current version of NodeJS by typing `node -v` in your terminal. If your version is below that one version I'd encourage you to either do an update globally or use a NodeJS version management tool such as [Volta](https://volta.sh/) or [nvm](https://github.com/nvm-sh/nvm).
32
+ You can check your current Node.js version by running `node -v`. Use a version
33
+ manager such as [Volta](https://volta.sh/) or [nvm](https://github.com/nvm-sh/nvm)
34
+ to update it.
33
35
 
34
- Supported Vite versions: **Vite 6, 7 and 8**. Support for Vite 3–5 was
35
- dropped; if you are on an older Vite, stay on an earlier release of this
36
- plugin (2.x) or upgrade Vite.
36
+ Supported Vite versions: **Vite 8 and 9**.
37
37
 
38
38
  ## Quickstart
39
39
 
@@ -190,8 +190,26 @@ pnpm add -D @solidjs/start-devtools@next
190
190
  ```
191
191
 
192
192
  Start mode detects the package automatically. Set `start: { devtools: true }`
193
- to require it or `start: { devtools: false }` to disable it. The package is an
194
- optional peer and the toolbar is not included in production builds.
193
+ to require it or `start: { devtools: false }` to disable automatic integration.
194
+ The package is an optional peer and the toolbar is not included in production
195
+ builds.
196
+
197
+ Generated entries wrap the app automatically. With custom server and client
198
+ entries, place the development boundary around the app in the shared document
199
+ or root:
200
+
201
+ ```tsx
202
+ import { DevToolbar } from "@solidjs/start-devtools";
203
+
204
+ <body>
205
+ <DevToolbar>
206
+ <App />
207
+ </DevToolbar>
208
+ </body>;
209
+ ```
210
+
211
+ The package becomes a children-only passthrough in production, so no toolbar
212
+ code is included in either production bundle.
195
213
 
196
214
  ```tsx
197
215
  // src/App.tsx — the entire app: a plain content component
@@ -212,7 +230,7 @@ With `ssr: true` — **SSR start mode**:
212
230
  classic `vite build` + `vite build --ssr` two-step, work too.)
213
231
  - **Build ordering**: server builds read the client manifest, so with `ssr`
214
232
  enabled the plugin also orders builder-mode (environments API) app builds
215
- client-first via a `buildApp` hook (Vite 7.1+). That covers composed
233
+ client-first via a `buildApp` hook. That covers composed
216
234
  setups whose own orchestrator builds server environments before the
217
235
  client — e.g. @cloudflare/vite-plugin — with no hand-written ordering
218
236
  plugin; setups without another orchestrator keep Vite's stock
@@ -528,18 +528,18 @@ function boundaryModules() {
528
528
  // import graph — no directive transforms have run, so it walks
529
529
  // straight through 'use server' modules into genuinely server-only
530
530
  // code. That graph is legal once transforms split it, so the guard
531
- // must not fire on the scan pass (`options.scan`, the flag Vite's
532
- // scanner sets on plugin-container resolves in v6/7 and the rolldown
533
- // scanner in v8). Still claim the specifier: resolving to the empty
531
+ // must not fire on the scan pass (`options.scan`, set by Rolldown's
532
+ // dependency scanner). Still claim the specifier: resolving to the empty
534
533
  // virtual module keeps the scanner from chasing `server-only` /
535
534
  // `client-only` as missing bare dependencies, which would abort the
536
535
  // scan all the same. Real dev/build module graphs resolve without
537
536
  // the flag and stay fully guarded.
538
537
  const scan = !!options?.scan;
538
+ const server = this.environment.config.consumer === 'server';
539
539
  if (id === 'server-only') {
540
- if (!options?.ssr && !scan) this.error(`[@solidjs/vite-plugin] Attempt to import 'server-only' in a client module: ${importer}. ` + `Code that uses this module must run only on the server — make sure it is only ` + `imported by server code (e.g. a server entry, a "use server" module, or code ` + `reached exclusively from them).`);
540
+ if (!server && !scan) this.error(`[@solidjs/vite-plugin] Attempt to import 'server-only' in a client module: ${importer}. ` + `Code that uses this module must run only on the server — make sure it is only ` + `imported by server code (e.g. a server entry, a "use server" module, or code ` + `reached exclusively from them).`);
541
541
  } else if (id === 'client-only') {
542
- if (options?.ssr && !scan) this.error(`[@solidjs/vite-plugin] Attempt to import 'client-only' in a server module: ${importer}. ` + `Code that uses this module must run only in the browser — make sure it is only ` + `imported by client code (e.g. behind a client-only lazy boundary).`);
542
+ if (server && !scan) this.error(`[@solidjs/vite-plugin] Attempt to import 'client-only' in a server module: ${importer}. ` + `Code that uses this module must run only in the browser — make sure it is only ` + `imported by client code (e.g. behind a client-only lazy boundary).`);
543
543
  } else {
544
544
  return null;
545
545
  }
@@ -842,8 +842,6 @@ function invalidateModule(moduleGraph, path) {
842
842
  }
843
843
  }
844
844
  function invalidateModules(server, result, manifest) {
845
- // `environments` requires Vite 6+; older versions just miss the eager
846
- // manifest invalidation (the debounced reload still converges).
847
845
  if (server?.environments && result.invalidPreload) {
848
846
  invalidateModule(server.environments.client.moduleGraph, manifest);
849
847
  invalidateModule(server.environments.ssr.moduleGraph, manifest);
@@ -1036,7 +1034,7 @@ function serverFunctions(options = {}, internal = {}) {
1036
1034
  apply: 'serve',
1037
1035
  configureServer(server) {
1038
1036
  const ssrEnvironment = server.environments.ssr;
1039
- if (internal.externalDevServer || ssrEnvironment && !isRunnableEnvironment(ssrEnvironment)) {
1037
+ if (internal.externalDevServer || !isRunnableEnvironment(ssrEnvironment)) {
1040
1038
  return;
1041
1039
  }
1042
1040
  server.middlewares.use((req, res, next) => {
@@ -1059,7 +1057,7 @@ function serverFunctions(options = {}, internal = {}) {
1059
1057
  const functionId = (typeof headerId === 'string' ? headerId.split('#')[0] : undefined) || url.searchParams.get('id');
1060
1058
  if (functionId) {
1061
1059
  const entry = moduleForFunctionId(functionId);
1062
- if (entry) await server.ssrLoadModule(moduleDevUrl(entry));
1060
+ if (entry) await ssrEnvironment.runner.import(moduleDevUrl(entry));
1063
1061
  }
1064
1062
  // Dispatch through a module evaluated in the SSR environment so
1065
1063
  // the handler shares the registry instance with the app modules.
@@ -1067,7 +1065,7 @@ function serverFunctions(options = {}, internal = {}) {
1067
1065
  // in, and dispatch goes through `handleRequest` instead — one
1068
1066
  // middleware chain and one stub-backed request event front the
1069
1067
  // endpoint exactly as they front page SSR.
1070
- const handler = await server.ssrLoadModule(internal.ssrHandler ?? HANDLER_ID$1);
1068
+ const handler = await ssrEnvironment.runner.import(internal.ssrHandler ?? HANDLER_ID$1);
1071
1069
  // Both dispatch shapes carry the raw Node request on the event
1072
1070
  // (the `options.event` seam), matching the SSR dev middleware
1073
1071
  // and what a production Node entry passes.
@@ -1079,7 +1077,6 @@ function serverFunctions(options = {}, internal = {}) {
1079
1077
  const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions);
1080
1078
  await sendWebResponse(res, response);
1081
1079
  })().catch(error => {
1082
- if (error instanceof Error) server.ssrFixStacktrace(error);
1083
1080
  next(error);
1084
1081
  });
1085
1082
  });
@@ -1203,13 +1200,9 @@ function serverFunctions(options = {}, internal = {}) {
1203
1200
  }
1204
1201
 
1205
1202
  const DEVTOOLS_PACKAGE = '@solidjs/start-devtools';
1206
- const DEVTOOLS_ID = 'virtual:solid-devtools';
1207
1203
  const DEVTOOLS_MOUNT_ID = 'virtual:solid-devtools/mount';
1208
- function devtoolsModuleCode() {
1209
- return [`import * as serverFunctions from '@solidjs/web/server-functions';`, `import { DevToolbar, pushServerFunctionCall } from '${DEVTOOLS_PACKAGE}';`, `const observe = Reflect.get(serverFunctions, 'observeServerFunctionCalls');`, `if (typeof observe === 'function') observe(pushServerFunctionCall);`, `export { DevToolbar };`].join('\n');
1210
- }
1211
1204
  function devtoolsMountModuleCode() {
1212
- return [`import ${JSON.stringify(DEVTOOLS_ID)};`, `import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
1205
+ return [`import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
1213
1206
  }
1214
1207
 
1215
1208
  // Start-mode serving for plain Vite apps: `solid({ start: {...} })` (or the
@@ -1226,7 +1219,7 @@ function devtoolsMountModuleCode() {
1226
1219
  // Both paths inject the Vite client, dev style patch, and entry CSS as
1227
1220
  // `<style data-vite-dev-id>` tags before the body can paint.
1228
1221
  // - Prod: the plugin configures a full-app build (client + server bundles
1229
- // via the Vite 6+ environments/builder API — a single `vite build` builds
1222
+ // via the Vite environments/builder API — a single `vite build` builds
1230
1223
  // both) whose server entry is `virtual:solid-ssr-handler`: an
1231
1224
  // adapter-agnostic named `handleRequest(Request) => Promise<Response>` plus
1232
1225
  // a default Fetchable `{ fetch(request) }` export. Both scope each request
@@ -1408,10 +1401,9 @@ function startServe(options, internal = {}) {
1408
1401
  const serverComponents = !!internal.serverComponents;
1409
1402
  const errorBoundary = options.errorBoundary !== false;
1410
1403
  const styleFilter = internal.styleFilter;
1411
- let devtools = false;
1412
- let devtoolsResolution;
1413
- /** Resolved module id of `@solidjs/start-devtools` once detection succeeds. */
1414
- let devtoolsId = null;
1404
+ let devtoolsEnabled = false;
1405
+ let devtoolsResolutions = {};
1406
+ let devtoolsIds = {};
1415
1407
  // `external` is server-mode-only (documented no-op in client mode, so a
1416
1408
  // host-integrated config survives the `ssr` boolean flip untouched).
1417
1409
  const externalServer = !clientMode && !!options.external;
@@ -1428,31 +1420,36 @@ function startServe(options, internal = {}) {
1428
1420
  if (!entries) throw new Error('[@solidjs/vite-plugin] SSR entries not resolved yet');
1429
1421
  return entries;
1430
1422
  }
1431
- async function resolveDevtools(resolve, importer) {
1432
- if (devtools !== undefined) return devtools;
1423
+ async function resolveDevtools(resolve, importer, consumer) {
1424
+ if (!devtoolsEnabled) return false;
1433
1425
  // Detect from the app graph first (the documented install location), then
1434
1426
  // from the plugin's own file: in pnpm-isolated apps a copy that is only a
1435
1427
  // dependency of the plugin is not reachable from the app's importers. The
1436
- // resolved id is kept so the virtual modules' imports of the package can
1437
- // be delegated to it (see resolveId).
1438
- devtoolsResolution ??= (async () => ((await resolve(DEVTOOLS_PACKAGE, importer)) ?? (await resolve(DEVTOOLS_PACKAGE, node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))))))?.id ?? null)();
1439
- devtoolsId = await devtoolsResolution;
1440
- devtools = devtoolsId !== null;
1441
- if (!devtools && options.devtools === true) {
1428
+ // resolved id is kept so imports from generated modules can use it.
1429
+ devtoolsResolutions[consumer] ??= (async () => {
1430
+ // Resolving from the plugin's own file never yields null when the
1431
+ // package is absent: it is declared an optional peer dependency, so
1432
+ // Vite answers with its `__vite-optional-peer-dep:` stub (an empty
1433
+ // module). Treat that stub as "not installed".
1434
+ const realId = resolved => resolved && !resolved.id.startsWith('__vite-optional-peer-dep:') ? resolved.id : null;
1435
+ return realId(await resolve(DEVTOOLS_PACKAGE, importer)) ?? realId(await resolve(DEVTOOLS_PACKAGE, node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))));
1436
+ })();
1437
+ const id = await devtoolsResolutions[consumer];
1438
+ devtoolsIds[consumer] = id;
1439
+ if (!id && options.devtools === true) {
1442
1440
  throw new Error('[@solidjs/vite-plugin] start.devtools requires @solidjs/start-devtools. ' + 'Install it as a development dependency or set start.devtools to false.');
1443
1441
  }
1444
- return devtools;
1442
+ return id !== null;
1445
1443
  }
1446
1444
 
1447
1445
  /**
1448
- * Cheap root-walk probe mirroring how the optimizer resolves bare
1446
+ * Cheap walk-up probe mirroring how the optimizer resolves bare
1449
1447
  * `optimizeDeps.include` entries: is @solidjs/start-devtools reachable from
1450
- * the Vite root? Detection proper (resolveDevtools) runs later with a real
1448
+ * this directory? Detection proper (resolveDevtools) runs later with a real
1451
1449
  * importer; this only decides whether the toolbar graph can be pre-bundled
1452
- * at scan time — it hangs off virtual modules the scanner never sees, so
1453
- * first-request discovery would force a re-optimize + full page reload.
1450
+ * at scan time.
1454
1451
  */
1455
- function devtoolsReachableFromRoot(dir) {
1452
+ function devtoolsReachableFrom(dir) {
1456
1453
  for (let current = dir;;) {
1457
1454
  if (fs.existsSync(path.join(current, 'node_modules', DEVTOOLS_PACKAGE, 'package.json'))) {
1458
1455
  return true;
@@ -1463,6 +1460,26 @@ function startServe(options, internal = {}) {
1463
1460
  }
1464
1461
  }
1465
1462
 
1463
+ /**
1464
+ * The `optimizeDeps.include` spec that pre-bundles the toolbar graph, or
1465
+ * null when it cannot be resolved at all. Pre-bundling it is not just a
1466
+ * warm-start nicety: the toolbar hangs off virtual modules the scanner
1467
+ * never crawls, so without an include the optimizer only discovers it on
1468
+ * first request. That re-optimize can pair chunks from different passes
1469
+ * whose shared minified exports disagree, taking down the whole client
1470
+ * entry graph. The spec must therefore cover every install shape
1471
+ * resolveDevtools accepts: bare when the app installs the package, and
1472
+ * Vite's nested-include form (`plugin > dep`) when it is only a dependency
1473
+ * of this plugin (pnpm-isolated installs).
1474
+ */
1475
+ function devtoolsIncludeSpec(rootDir) {
1476
+ if (devtoolsReachableFrom(rootDir)) return DEVTOOLS_PACKAGE;
1477
+ if (devtoolsReachableFrom(path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))))) {
1478
+ return `@solidjs/vite-plugin > ${DEVTOOLS_PACKAGE}`;
1479
+ }
1480
+ return null;
1481
+ }
1482
+
1466
1483
  /** Import specifier for generated code: absolute for files, id for virtuals. */
1467
1484
  function entryServerSpec() {
1468
1485
  const {
@@ -1516,7 +1533,7 @@ function startServe(options, internal = {}) {
1516
1533
  const content = wrapper ? `<${wrapper}><${root} /></${wrapper}>` : `<${root} />`;
1517
1534
  return isBuild && errorBoundary ? [` <DefaultErrorBoundary>`, ` <Document>`, ` <DefaultErrorBoundary>`, ` ${content}`, ` </DefaultErrorBoundary>`, ` </Document>`, ` </DefaultErrorBoundary>`] : [` <Document>`, ` ${content}`, ` </Document>`];
1518
1535
  }
1519
- function generatedEntryServerCode() {
1536
+ function generatedEntryServerCode(toolbar) {
1520
1537
  if (clientMode) {
1521
1538
  // The client-mode shell: the document without the app. Rendered per
1522
1539
  // request in dev (any HTML GET gets it — history-fallback semantics)
@@ -1528,7 +1545,7 @@ function startServe(options, internal = {}) {
1528
1545
  app
1529
1546
  } = requireEntries();
1530
1547
  const streamOptions = `{ manifest${serverComponents ? ', plugins: [ServerComponentPlugin]' : ''} }`;
1531
- return [`import { renderToStream${setupPath ? ', getRequestEvent' : ''} } from '@solidjs/web';`, ...(serverComponents ? [`import { configureServerFunctionsServer } from '@solidjs/web/server-functions';`, `import { frameTransformDirectResult, ServerComponentPlugin } from '@solidjs/web/frames';`] : []), `import manifest from ${JSON.stringify(MANIFEST_ID)};`, `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ...errorBoundaryImport(), ...(setupPath ? [`import setup from ${JSON.stringify(setupPath)};`] : []), ``, ...(setupPath ? [`if (typeof setup !== 'function') {`, ` throw new Error('[@solidjs/vite-plugin] start.setup must default-export a function ' +`, ` '((event, App) => Component | void | Promise<...>): ' + ${JSON.stringify(options.setup)});`, `}`, ``] : []), ...(serverComponents ? [
1548
+ return [`import { renderToStream${setupPath ? ', getRequestEvent' : ''} } from '@solidjs/web';`, ...(serverComponents ? [`import { configureServerFunctionsServer } from '@solidjs/web/server-functions';`, `import { frameTransformDirectResult, ServerComponentPlugin } from '@solidjs/web/frames';`] : []), `import manifest from ${JSON.stringify(MANIFEST_ID)};`, `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_PACKAGE)};`] : []), ...errorBoundaryImport(), ...(setupPath ? [`import setup from ${JSON.stringify(setupPath)};`] : []), ``, ...(setupPath ? [`if (typeof setup !== 'function') {`, ` throw new Error('[@solidjs/vite-plugin] start.setup must default-export a function ' +`, ` '((event, App) => Component | void | Promise<...>): ' + ${JSON.stringify(options.setup)});`, `}`, ``] : []), ...(serverComponents ? [
1532
1549
  // Direct (in-process) server-function calls made during document
1533
1550
  // SSR must resolve to inline-renderable components; the endpoint
1534
1551
  // response transform is installed separately by the
@@ -1542,7 +1559,7 @@ function startServe(options, internal = {}) {
1542
1559
  // the *complete* render) and buffers the stream — so it crosses
1543
1560
  // boxed under a private key the generated handler unboxes
1544
1561
  // (both modules are ours).
1545
- `export function render(request, context) {`, ` const prepared = setup(getRequestEvent(), App);`, ` if (prepared && typeof prepared.then === 'function') {`, ` return prepared.then((component) => ({ ${STREAM_BOX}: renderApp(component || App) }));`, ` }`, ` return renderApp(prepared || App);`, `}`, ``, `function renderApp(Root) {`, ` return renderToStream(() => (`, ...documentTree('Root'), ` ), ${streamOptions});`, `}`] : [`export function render(request, context) {`, ` return renderToStream(() => (`, ...documentTree('App'), ` ), ${streamOptions});`, `}`])].join('\n');
1562
+ `export function render(request, context) {`, ` const prepared = setup(getRequestEvent(), App);`, ` if (prepared && typeof prepared.then === 'function') {`, ` return prepared.then((component) => ({ ${STREAM_BOX}: renderApp(component || App) }));`, ` }`, ` return renderApp(prepared || App);`, `}`, ``, `function renderApp(Root) {`, ` return renderToStream(() => (`, ...documentTree('Root', toolbar ? 'DevToolbar' : undefined), ` ), ${streamOptions});`, `}`] : [`export function render(request, context) {`, ` return renderToStream(() => (`, ...documentTree('App', toolbar ? 'DevToolbar' : undefined), ` ), ${streamOptions});`, `}`])].join('\n');
1546
1563
  }
1547
1564
  function generatedEntryClientCode(toolbar) {
1548
1565
  const {
@@ -1554,9 +1571,9 @@ function startServe(options, internal = {}) {
1554
1571
  // app cannot claim server DOM anyway. The entry script is injected
1555
1572
  // without `async` (plain module = deferred), so document.body is
1556
1573
  // complete when this runs.
1557
- return [`import { render } from '@solidjs/web';`, ...errorBoundaryImport(), ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_ID)};`] : []), `import App from ${JSON.stringify(app)};`, ``, `render(() => ${isBuild && errorBoundary ? '<DefaultErrorBoundary><App /></DefaultErrorBoundary>' : toolbar ? '<DevToolbar><App /></DevToolbar>' : '<App />'}, document.body);`].join('\n');
1574
+ return [`import { render } from '@solidjs/web';`, ...errorBoundaryImport(), ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_PACKAGE)};`] : []), `import App from ${JSON.stringify(app)};`, ``, `render(() => ${isBuild && errorBoundary ? '<DefaultErrorBoundary><App /></DefaultErrorBoundary>' : toolbar ? '<DevToolbar><App /></DevToolbar>' : '<App />'}, document.body);`].join('\n');
1558
1575
  }
1559
- return [`import { hydrate } from '@solidjs/web';`, ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_ID)};`] : []), ...(serverComponents ? [`import { installServerComponents } from '@solidjs/web/frames';`] : []), ...errorBoundaryImport(), `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ``, ...(serverComponents ? [
1576
+ return [`import { hydrate } from '@solidjs/web';`, ...(toolbar ? [`import { DevToolbar } from ${JSON.stringify(DEVTOOLS_PACKAGE)};`] : []), ...(serverComponents ? [`import { installServerComponents } from '@solidjs/web/frames';`] : []), ...errorBoundaryImport(), `import Document from ${JSON.stringify(documentSpec())};`, `import App from ${JSON.stringify(app)};`, ``, ...(serverComponents ? [
1560
1577
  // Installs the t=0 document-adoption registry and the transport
1561
1578
  // policy (component responses morph their boundary instead of
1562
1579
  // decoding as data). Must run before hydrate().
@@ -1737,9 +1754,9 @@ function startServe(options, internal = {}) {
1737
1754
  enforce: 'pre',
1738
1755
  config(userConfig, env) {
1739
1756
  root = path.resolve(userConfig.root || process.cwd());
1740
- devtools = env.command === 'serve' && !env.isPreview && options.devtools !== false ? undefined : false;
1741
- devtoolsResolution = undefined;
1742
- devtoolsId = null;
1757
+ devtoolsEnabled = env.command === 'serve' && !env.isPreview && options.devtools !== false;
1758
+ devtoolsResolutions = {};
1759
+ devtoolsIds = {};
1743
1760
  entries = resolveEntries(root, options, clientMode);
1744
1761
  middlewarePath = options.middleware ? path.resolve(root, normalizeUserPath(root, options.middleware, 'middleware')) : null;
1745
1762
  // Server-mode only, like `entryServer`/`external` (a documented
@@ -1830,7 +1847,7 @@ function startServe(options, internal = {}) {
1830
1847
  }
1831
1848
  },
1832
1849
  // Presence of `builder` makes a plain `vite build` build the
1833
- // whole app (all environments: client then ssr) on Vite 6+.
1850
+ // whole app (all environments: client then ssr).
1834
1851
  // A classic `vite build --ssr` invocation must stay a
1835
1852
  // single-environment build, so it doesn't get the flag.
1836
1853
  ...(env.isSsrBuild ? {} : {
@@ -1860,23 +1877,32 @@ function startServe(options, internal = {}) {
1860
1877
  optimizeDeps: {
1861
1878
  entries: scanEntries,
1862
1879
  // Like the refresh runtime in the main plugin: the toolbar
1863
- // graph is injected behind virtual modules the scanner never
1864
- // crawls, so pre-bundle it (and the server-functions runtime
1865
- // the virtual module pulls in) up front — first-request
1866
- // discovery would re-optimize and full-reload the page.
1867
- ...(devtools === undefined && devtoolsReachableFromRoot(root) ? {
1868
- include: [DEVTOOLS_PACKAGE, '@solidjs/web/server-functions']
1869
- } : {})
1880
+ // graph is injected behind modules the scanner never crawls,
1881
+ // so pre-bundle it and the server-functions runtime up front.
1882
+ ...(() => {
1883
+ const spec = devtoolsEnabled ? devtoolsIncludeSpec(root) : null;
1884
+ return spec ? {
1885
+ include: [spec, '@solidjs/web/server-functions']
1886
+ } : {};
1887
+ })()
1870
1888
  }
1871
1889
  })
1872
1890
  };
1873
1891
  },
1892
+ configEnvironment(name, config) {
1893
+ if (name !== 'ssr') return;
1894
+ config.resolve ??= {};
1895
+ const noExternal = config.resolve.noExternal;
1896
+ if (noExternal !== true) {
1897
+ config.resolve.noExternal = [...(Array.isArray(noExternal) ? noExternal : noExternal ? [noExternal] : []), DEVTOOLS_PACKAGE];
1898
+ }
1899
+ },
1874
1900
  configResolved(config) {
1875
1901
  root = config.root;
1876
1902
  base = config.base;
1877
1903
  isBuild = config.command === 'build';
1878
1904
  },
1879
- resolveId(source, importer) {
1905
+ resolveId(source, importer, opts) {
1880
1906
  if (source === HANDLER_ID) {
1881
1907
  return {
1882
1908
  id: HANDLER_ID,
@@ -1895,18 +1921,16 @@ function startServe(options, internal = {}) {
1895
1921
  moduleSideEffects: source === ENTRY_CLIENT_ID
1896
1922
  };
1897
1923
  }
1898
- if (devtools && (source === DEVTOOLS_ID || source === DEVTOOLS_MOUNT_ID)) {
1924
+ if (devtoolsEnabled && source === DEVTOOLS_MOUNT_ID) {
1899
1925
  return {
1900
1926
  id: source,
1901
1927
  moduleSideEffects: true
1902
1928
  };
1903
1929
  }
1904
- // The virtual devtools modules import the package by its bare name,
1905
- // but a virtual importer gives Vite no directory to walk, so the
1906
- // specifier would only resolve from the Vite root — which fails in
1907
- // pnpm-isolated apps where the package is not a root-level install.
1908
- // Delegate to the resolution captured at detection time instead.
1909
- if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === DEVTOOLS_ID || importer === DEVTOOLS_MOUNT_ID)) {
1930
+ // Generated modules have no directory for bare-package resolution.
1931
+ // Reuse the app-relative id captured during detection.
1932
+ const devtoolsId = devtoolsIds[getEnvironmentConsumer(this.environment, opts)];
1933
+ if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === ENTRY_SERVER_ID || importer === ENTRY_CLIENT_ID || importer === DEVTOOLS_MOUNT_ID)) {
1910
1934
  return {
1911
1935
  id: devtoolsId
1912
1936
  };
@@ -1928,37 +1952,40 @@ function startServe(options, internal = {}) {
1928
1952
  }
1929
1953
  return devStylesModuleCode(this.environment, file => this.addWatchFile(file));
1930
1954
  }
1931
- if (id === ENTRY_SERVER_ID) return generatedEntryServerCode();
1955
+ if (id === ENTRY_SERVER_ID) {
1956
+ const toolbar = clientMode ? false : await resolveDevtools((source, importer) => this.resolve(source, importer, {
1957
+ skipSelf: true
1958
+ }), requireEntries().app, 'server');
1959
+ return generatedEntryServerCode(toolbar);
1960
+ }
1932
1961
  if (id === ENTRY_CLIENT_ID) {
1933
1962
  const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
1934
1963
  skipSelf: true
1935
- }), requireEntries().app);
1964
+ }), requireEntries().app, 'client');
1936
1965
  return generatedEntryClientCode(toolbar);
1937
1966
  }
1938
1967
  if (id === DOCUMENT_ID) return documentShellCode;
1939
1968
  if (id === ERROR_BOUNDARY_ID) return errorBoundaryCode;
1940
- if (id === DEVTOOLS_ID || id === DEVTOOLS_MOUNT_ID) {
1941
- // A cold direct request (stale tab reload) can reach the virtual
1942
- // module before the entry has triggered detection — run it here so
1943
- // first-touch order doesn't matter.
1944
- if (!isBuild && consumer === 'client' && devtools === undefined) {
1969
+ if (id === DEVTOOLS_MOUNT_ID) {
1970
+ let enabled = false;
1971
+ if (devtoolsEnabled && consumer === 'client') {
1945
1972
  const {
1946
1973
  app,
1947
1974
  entryClient
1948
1975
  } = requireEntries();
1949
- await resolveDevtools((source, importer) => this.resolve(source, importer, {
1976
+ enabled = await resolveDevtools((source, importer) => this.resolve(source, importer, {
1950
1977
  skipSelf: true
1951
- }), app ?? path.resolve(root, entryClient));
1978
+ }), app ?? path.resolve(root, entryClient), 'client');
1952
1979
  }
1953
- if (isBuild || !devtools || consumer !== 'client') {
1980
+ if (!enabled) {
1954
1981
  this.error(`${id} is only available to the development client.`);
1955
1982
  }
1956
- return id === DEVTOOLS_ID ? devtoolsModuleCode() : devtoolsMountModuleCode();
1983
+ return devtoolsMountModuleCode();
1957
1984
  }
1958
1985
  return null;
1959
1986
  },
1960
1987
  async transform(code, id, opts) {
1961
- if (isBuild || devtools === false) return null;
1988
+ if (isBuild || !devtoolsEnabled) return null;
1962
1989
  const current = requireEntries();
1963
1990
  if (current.generated || getEnvironmentConsumer(this.environment, opts) !== 'client') {
1964
1991
  return null;
@@ -1970,7 +1997,7 @@ function startServe(options, internal = {}) {
1970
1997
  }
1971
1998
  const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
1972
1999
  skipSelf: true
1973
- }), id);
2000
+ }), id, 'client');
1974
2001
  if (!toolbar) return null;
1975
2002
  return {
1976
2003
  code: `import ${JSON.stringify(DEVTOOLS_MOUNT_ID)};\n${code}`,
@@ -2034,7 +2061,7 @@ function startServe(options, internal = {}) {
2034
2061
  // that gets the streamed SSR render.
2035
2062
  return () => {
2036
2063
  const ssrEnvironment = server.environments.ssr;
2037
- if (externalServer || ssrEnvironment && !isRunnableEnvironment(ssrEnvironment)) {
2064
+ if (externalServer || !isRunnableEnvironment(ssrEnvironment)) {
2038
2065
  return;
2039
2066
  }
2040
2067
  server.middlewares.use((req, res, next) => {
@@ -2051,7 +2078,7 @@ function startServe(options, internal = {}) {
2051
2078
  (async () => {
2052
2079
  // Loaded through the SSR environment so the app, the request
2053
2080
  // event storage, and the handler share one module registry.
2054
- const handler = await server.ssrLoadModule(HANDLER_ID);
2081
+ const handler = await ssrEnvironment.runner.import(HANDLER_ID);
2055
2082
  const styles = pageRequest ? await collectDevStyles(server, styleRoots(), styleFilter) : [];
2056
2083
  const devHead = styles.map(renderDevStyleTag).join('');
2057
2084
  // Post middlewares run after Vite's base middleware stripped
@@ -2076,7 +2103,6 @@ function startServe(options, internal = {}) {
2076
2103
  if (response.headers.has(DEV_FALLTHROUGH_HEADER)) return next();
2077
2104
  await sendWebResponse(res, response);
2078
2105
  })().catch(error => {
2079
- if (error instanceof Error) server.ssrFixStacktrace(error);
2080
2106
  // Vite's error middleware renders the overlay-enabled 500 page.
2081
2107
  next(error);
2082
2108
  });
@@ -2166,7 +2192,7 @@ function startServe(options, internal = {}) {
2166
2192
  // https://github.com/pyyupsk/vite-env), the design-correct prior art. The
2167
2193
  // implementation is fresh against this plugin's machinery: Standard Schema
2168
2194
  // is the only contract (no zod dependency or zod-specific paths), the
2169
- // schema file loads through Vite's own `runnerImport`/`loadConfigFromFile`
2195
+ // schema file loads through Vite's own `runnerImport`
2170
2196
  // (no jiti), server-graph protection keys off the environment *consumer*
2171
2197
  // rather than environment-name lists, and the types are inferred from the
2172
2198
  // user's schema instead of introspected per-library.
@@ -2225,37 +2251,18 @@ function formatValidationError(issues, envFile, mode) {
2225
2251
  return `[@solidjs/vite-plugin] env validation failed (${issues.length} issue${issues.length === 1 ? '' : 's'}) — schema: ${envFile}, mode: ${mode}\n\n` + lines.join('\n') + `\n\nSet the variables in your environment or .env files, or adjust the schema.`;
2226
2252
  }
2227
2253
 
2228
- /**
2229
- * Loads the schema module at config time through Vite itself: `runnerImport`
2230
- * (Vite 6.1+) evaluates TypeScript in-process with project resolution;
2231
- * older Vite 6 falls back to `loadConfigFromFile`, the exact machinery that
2232
- * loads vite.config.ts.
2233
- */
2254
+ /** Loads the schema module through Vite with project resolution. */
2234
2255
  async function importSchemaModule(envFileAbs, root, mode) {
2235
- const vite = await import('vite');
2236
- if (typeof vite.runnerImport === 'function') {
2237
- const {
2238
- module,
2239
- dependencies
2240
- } = await vite.runnerImport(envFileAbs, {
2241
- root,
2242
- mode
2243
- });
2244
- return {
2245
- exported: module?.default,
2246
- dependencies: (dependencies || []).map(dep => path.resolve(root, dep)).filter(dep => fs.existsSync(dep))
2247
- };
2248
- }
2249
- const result = await vite.loadConfigFromFile({
2250
- command: 'serve',
2256
+ const {
2257
+ module,
2258
+ dependencies
2259
+ } = await vite.runnerImport(envFileAbs, {
2260
+ root,
2251
2261
  mode
2252
- }, envFileAbs, root);
2253
- if (!result) {
2254
- throw new Error(`[@solidjs/vite-plugin] failed to load env schema from ${envFileAbs}`);
2255
- }
2262
+ });
2256
2263
  return {
2257
- exported: result.config,
2258
- dependencies: (result.dependencies || []).map(dep => path.resolve(root, dep)).filter(dep => fs.existsSync(dep))
2264
+ exported: module?.default,
2265
+ dependencies: dependencies.map(dep => path.resolve(root, dep)).filter(dep => fs.existsSync(dep))
2259
2266
  };
2260
2267
  }
2261
2268
  function assertSchemaShape(exported, envFile, envPrefixes) {
@@ -2739,8 +2746,6 @@ const LAZY_PLACEHOLDER_PREFIX = '__SOLID_LAZY_MODULE__:';
2739
2746
  * solid-refresh#85 — is no longer used at all).
2740
2747
  */
2741
2748
  const REFRESH_RUNTIME_SOURCE = 'solid-js/refresh';
2742
- const viteVersionMajor = +vite.version.split('.')[0];
2743
- const isVite8 = viteVersionMajor >= 8;
2744
2749
  const DEFAULT_STYLE_EXCLUDE = /node_modules/;
2745
2750
  const VIRTUAL_MANIFEST_ID = 'virtual:solid-manifest';
2746
2751
  const RESOLVED_VIRTUAL_MANIFEST_ID = '\0' + VIRTUAL_MANIFEST_ID;
@@ -3231,44 +3236,28 @@ function solidPlugin(options = {}) {
3231
3236
  // server-components runtime installs its response policy there).
3232
3237
  ...(command === 'serve' && serverComponents ? ['@solidjs/web/frames', '@solidjs/web/server-functions'] : []), ...solidPkgsConfig.optimizeDeps.include],
3233
3238
  exclude: solidPkgsConfig.optimizeDeps.exclude,
3234
- // Vite 8+ uses Rolldown for dependency scanning. Rolldown defaults to
3235
- // React's automatic JSX runtime for .tsx files, injecting a
3236
- // react/jsx-dev-runtime import that fails to resolve and aborts the
3237
- // scan. 'preserve' is no fix: the scanner re-parses the transformed
3238
- // output as plain JS, so any preserved JSX is a hard parse error
3239
- // (issue #262). The classic runtime is the only scan-safe lowering:
3240
- // it emits bare `React.createElement` calls without injecting any
3241
- // import, and the scan output is never executed — it only exists so
3242
- // rolldown can walk the import graph.
3243
- ...(isVite8 ? {
3244
- rolldownOptions: {
3245
- transform: {
3246
- jsx: {
3247
- runtime: 'classic'
3248
- }
3239
+ // Keep Solid TSX from injecting React's automatic runtime during scanning.
3240
+ rolldownOptions: {
3241
+ transform: {
3242
+ jsx: {
3243
+ runtime: 'classic'
3249
3244
  }
3250
3245
  }
3251
- } : {})
3246
+ }
3252
3247
  },
3253
3248
  ...(Object.keys(test).length ? {
3254
3249
  test
3255
3250
  } : {})
3256
3251
  };
3257
3252
  },
3258
- // @ts-ignore This hook only works in Vite 6
3259
- async configEnvironment(name, config, opts) {
3253
+ configEnvironment(name, config, opts) {
3260
3254
  config.resolve ??= {};
3261
3255
  // Emulate Vite default fallback for `resolve.conditions` if not set
3262
3256
  if (config.resolve.conditions == null) {
3263
- // @ts-ignore These exports only exist in Vite 6
3264
- const {
3265
- defaultClientConditions,
3266
- defaultServerConditions
3267
- } = await import('vite');
3268
3257
  if (config.consumer === 'client' || name === 'client' || opts.isSsrTargetWebworker) {
3269
- config.resolve.conditions = [...defaultClientConditions];
3258
+ config.resolve.conditions = [...vite.defaultClientConditions];
3270
3259
  } else {
3271
- config.resolve.conditions = [...defaultServerConditions];
3260
+ config.resolve.conditions = [...vite.defaultServerConditions];
3272
3261
  }
3273
3262
  }
3274
3263
  config.resolve.conditions = ['solid', ...(replaceDev ? ['development'] : []),
@@ -3599,8 +3588,7 @@ function solidPlugin(options = {}) {
3599
3588
  // would bake a manifest-less fallback into the server bundle. Every user
3600
3589
  // of such a setup had to hand-write this ordering plugin; absorb it.
3601
3590
  //
3602
- // Semantics (Vite 7.1+; Vite 6 has no plugin `buildApp` hook and ignores
3603
- // these, keeping its build-everything default):
3591
+ // Semantics:
3604
3592
  // - The first hook builds the client environment first, but only where
3605
3593
  // the ordering matters: a client build that emits a manifest and
3606
3594
  // actually has an input. It runs at *normal* order, deliberately not