@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.
@@ -6,7 +6,7 @@ import { mergeAndConcat } from 'merge-anything';
6
6
  import { createRequire } from 'module';
7
7
  import path from 'path';
8
8
  import { Readable } from 'node:stream';
9
- import { createFilter, normalizePath, loadEnv, version } from 'vite';
9
+ import { createFilter, normalizePath, loadEnv, runnerImport, defaultClientConditions, defaultServerConditions } from 'vite';
10
10
  import { pathToFileURL, fileURLToPath } from 'node:url';
11
11
  import { crawlFrameworkPkgs } from 'vitefu';
12
12
 
@@ -504,18 +504,18 @@ function boundaryModules() {
504
504
  // import graph — no directive transforms have run, so it walks
505
505
  // straight through 'use server' modules into genuinely server-only
506
506
  // code. That graph is legal once transforms split it, so the guard
507
- // must not fire on the scan pass (`options.scan`, the flag Vite's
508
- // scanner sets on plugin-container resolves in v6/7 and the rolldown
509
- // scanner in v8). Still claim the specifier: resolving to the empty
507
+ // must not fire on the scan pass (`options.scan`, set by Rolldown's
508
+ // dependency scanner). Still claim the specifier: resolving to the empty
510
509
  // virtual module keeps the scanner from chasing `server-only` /
511
510
  // `client-only` as missing bare dependencies, which would abort the
512
511
  // scan all the same. Real dev/build module graphs resolve without
513
512
  // the flag and stay fully guarded.
514
513
  const scan = !!options?.scan;
514
+ const server = this.environment.config.consumer === 'server';
515
515
  if (id === 'server-only') {
516
- 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).`);
516
+ 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).`);
517
517
  } else if (id === 'client-only') {
518
- 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).`);
518
+ 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).`);
519
519
  } else {
520
520
  return null;
521
521
  }
@@ -818,8 +818,6 @@ function invalidateModule(moduleGraph, path) {
818
818
  }
819
819
  }
820
820
  function invalidateModules(server, result, manifest) {
821
- // `environments` requires Vite 6+; older versions just miss the eager
822
- // manifest invalidation (the debounced reload still converges).
823
821
  if (server?.environments && result.invalidPreload) {
824
822
  invalidateModule(server.environments.client.moduleGraph, manifest);
825
823
  invalidateModule(server.environments.ssr.moduleGraph, manifest);
@@ -1012,7 +1010,7 @@ function serverFunctions(options = {}, internal = {}) {
1012
1010
  apply: 'serve',
1013
1011
  configureServer(server) {
1014
1012
  const ssrEnvironment = server.environments.ssr;
1015
- if (internal.externalDevServer || ssrEnvironment && !isRunnableEnvironment(ssrEnvironment)) {
1013
+ if (internal.externalDevServer || !isRunnableEnvironment(ssrEnvironment)) {
1016
1014
  return;
1017
1015
  }
1018
1016
  server.middlewares.use((req, res, next) => {
@@ -1035,7 +1033,7 @@ function serverFunctions(options = {}, internal = {}) {
1035
1033
  const functionId = (typeof headerId === 'string' ? headerId.split('#')[0] : undefined) || url.searchParams.get('id');
1036
1034
  if (functionId) {
1037
1035
  const entry = moduleForFunctionId(functionId);
1038
- if (entry) await server.ssrLoadModule(moduleDevUrl(entry));
1036
+ if (entry) await ssrEnvironment.runner.import(moduleDevUrl(entry));
1039
1037
  }
1040
1038
  // Dispatch through a module evaluated in the SSR environment so
1041
1039
  // the handler shares the registry instance with the app modules.
@@ -1043,7 +1041,7 @@ function serverFunctions(options = {}, internal = {}) {
1043
1041
  // in, and dispatch goes through `handleRequest` instead — one
1044
1042
  // middleware chain and one stub-backed request event front the
1045
1043
  // endpoint exactly as they front page SSR.
1046
- const handler = await server.ssrLoadModule(internal.ssrHandler ?? HANDLER_ID$1);
1044
+ const handler = await ssrEnvironment.runner.import(internal.ssrHandler ?? HANDLER_ID$1);
1047
1045
  // Both dispatch shapes carry the raw Node request on the event
1048
1046
  // (the `options.event` seam), matching the SSR dev middleware
1049
1047
  // and what a production Node entry passes.
@@ -1055,7 +1053,6 @@ function serverFunctions(options = {}, internal = {}) {
1055
1053
  const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions);
1056
1054
  await sendWebResponse(res, response);
1057
1055
  })().catch(error => {
1058
- if (error instanceof Error) server.ssrFixStacktrace(error);
1059
1056
  next(error);
1060
1057
  });
1061
1058
  });
@@ -1179,13 +1176,9 @@ function serverFunctions(options = {}, internal = {}) {
1179
1176
  }
1180
1177
 
1181
1178
  const DEVTOOLS_PACKAGE = '@solidjs/start-devtools';
1182
- const DEVTOOLS_ID = 'virtual:solid-devtools';
1183
1179
  const DEVTOOLS_MOUNT_ID = 'virtual:solid-devtools/mount';
1184
- function devtoolsModuleCode() {
1185
- 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');
1186
- }
1187
1180
  function devtoolsMountModuleCode() {
1188
- return [`import ${JSON.stringify(DEVTOOLS_ID)};`, `import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
1181
+ return [`import { mountDevToolbar } from '${DEVTOOLS_PACKAGE}';`, `mountDevToolbar();`].join('\n');
1189
1182
  }
1190
1183
 
1191
1184
  // Start-mode serving for plain Vite apps: `solid({ start: {...} })` (or the
@@ -1202,7 +1195,7 @@ function devtoolsMountModuleCode() {
1202
1195
  // Both paths inject the Vite client, dev style patch, and entry CSS as
1203
1196
  // `<style data-vite-dev-id>` tags before the body can paint.
1204
1197
  // - Prod: the plugin configures a full-app build (client + server bundles
1205
- // via the Vite 6+ environments/builder API — a single `vite build` builds
1198
+ // via the Vite environments/builder API — a single `vite build` builds
1206
1199
  // both) whose server entry is `virtual:solid-ssr-handler`: an
1207
1200
  // adapter-agnostic named `handleRequest(Request) => Promise<Response>` plus
1208
1201
  // a default Fetchable `{ fetch(request) }` export. Both scope each request
@@ -1384,10 +1377,9 @@ function startServe(options, internal = {}) {
1384
1377
  const serverComponents = !!internal.serverComponents;
1385
1378
  const errorBoundary = options.errorBoundary !== false;
1386
1379
  const styleFilter = internal.styleFilter;
1387
- let devtools = false;
1388
- let devtoolsResolution;
1389
- /** Resolved module id of `@solidjs/start-devtools` once detection succeeds. */
1390
- let devtoolsId = null;
1380
+ let devtoolsEnabled = false;
1381
+ let devtoolsResolutions = {};
1382
+ let devtoolsIds = {};
1391
1383
  // `external` is server-mode-only (documented no-op in client mode, so a
1392
1384
  // host-integrated config survives the `ssr` boolean flip untouched).
1393
1385
  const externalServer = !clientMode && !!options.external;
@@ -1404,31 +1396,36 @@ function startServe(options, internal = {}) {
1404
1396
  if (!entries) throw new Error('[@solidjs/vite-plugin] SSR entries not resolved yet');
1405
1397
  return entries;
1406
1398
  }
1407
- async function resolveDevtools(resolve, importer) {
1408
- if (devtools !== undefined) return devtools;
1399
+ async function resolveDevtools(resolve, importer, consumer) {
1400
+ if (!devtoolsEnabled) return false;
1409
1401
  // Detect from the app graph first (the documented install location), then
1410
1402
  // from the plugin's own file: in pnpm-isolated apps a copy that is only a
1411
1403
  // dependency of the plugin is not reachable from the app's importers. The
1412
- // resolved id is kept so the virtual modules' imports of the package can
1413
- // be delegated to it (see resolveId).
1414
- devtoolsResolution ??= (async () => ((await resolve(DEVTOOLS_PACKAGE, importer)) ?? (await resolve(DEVTOOLS_PACKAGE, fileURLToPath(import.meta.url))))?.id ?? null)();
1415
- devtoolsId = await devtoolsResolution;
1416
- devtools = devtoolsId !== null;
1417
- if (!devtools && options.devtools === true) {
1404
+ // resolved id is kept so imports from generated modules can use it.
1405
+ devtoolsResolutions[consumer] ??= (async () => {
1406
+ // Resolving from the plugin's own file never yields null when the
1407
+ // package is absent: it is declared an optional peer dependency, so
1408
+ // Vite answers with its `__vite-optional-peer-dep:` stub (an empty
1409
+ // module). Treat that stub as "not installed".
1410
+ const realId = resolved => resolved && !resolved.id.startsWith('__vite-optional-peer-dep:') ? resolved.id : null;
1411
+ return realId(await resolve(DEVTOOLS_PACKAGE, importer)) ?? realId(await resolve(DEVTOOLS_PACKAGE, fileURLToPath(import.meta.url)));
1412
+ })();
1413
+ const id = await devtoolsResolutions[consumer];
1414
+ devtoolsIds[consumer] = id;
1415
+ if (!id && options.devtools === true) {
1418
1416
  throw new Error('[@solidjs/vite-plugin] start.devtools requires @solidjs/start-devtools. ' + 'Install it as a development dependency or set start.devtools to false.');
1419
1417
  }
1420
- return devtools;
1418
+ return id !== null;
1421
1419
  }
1422
1420
 
1423
1421
  /**
1424
- * Cheap root-walk probe mirroring how the optimizer resolves bare
1422
+ * Cheap walk-up probe mirroring how the optimizer resolves bare
1425
1423
  * `optimizeDeps.include` entries: is @solidjs/start-devtools reachable from
1426
- * the Vite root? Detection proper (resolveDevtools) runs later with a real
1424
+ * this directory? Detection proper (resolveDevtools) runs later with a real
1427
1425
  * importer; this only decides whether the toolbar graph can be pre-bundled
1428
- * at scan time — it hangs off virtual modules the scanner never sees, so
1429
- * first-request discovery would force a re-optimize + full page reload.
1426
+ * at scan time.
1430
1427
  */
1431
- function devtoolsReachableFromRoot(dir) {
1428
+ function devtoolsReachableFrom(dir) {
1432
1429
  for (let current = dir;;) {
1433
1430
  if (existsSync(path.join(current, 'node_modules', DEVTOOLS_PACKAGE, 'package.json'))) {
1434
1431
  return true;
@@ -1439,6 +1436,26 @@ function startServe(options, internal = {}) {
1439
1436
  }
1440
1437
  }
1441
1438
 
1439
+ /**
1440
+ * The `optimizeDeps.include` spec that pre-bundles the toolbar graph, or
1441
+ * null when it cannot be resolved at all. Pre-bundling it is not just a
1442
+ * warm-start nicety: the toolbar hangs off virtual modules the scanner
1443
+ * never crawls, so without an include the optimizer only discovers it on
1444
+ * first request. That re-optimize can pair chunks from different passes
1445
+ * whose shared minified exports disagree, taking down the whole client
1446
+ * entry graph. The spec must therefore cover every install shape
1447
+ * resolveDevtools accepts: bare when the app installs the package, and
1448
+ * Vite's nested-include form (`plugin > dep`) when it is only a dependency
1449
+ * of this plugin (pnpm-isolated installs).
1450
+ */
1451
+ function devtoolsIncludeSpec(rootDir) {
1452
+ if (devtoolsReachableFrom(rootDir)) return DEVTOOLS_PACKAGE;
1453
+ if (devtoolsReachableFrom(path.dirname(fileURLToPath(import.meta.url)))) {
1454
+ return `@solidjs/vite-plugin > ${DEVTOOLS_PACKAGE}`;
1455
+ }
1456
+ return null;
1457
+ }
1458
+
1442
1459
  /** Import specifier for generated code: absolute for files, id for virtuals. */
1443
1460
  function entryServerSpec() {
1444
1461
  const {
@@ -1492,7 +1509,7 @@ function startServe(options, internal = {}) {
1492
1509
  const content = wrapper ? `<${wrapper}><${root} /></${wrapper}>` : `<${root} />`;
1493
1510
  return isBuild && errorBoundary ? [` <DefaultErrorBoundary>`, ` <Document>`, ` <DefaultErrorBoundary>`, ` ${content}`, ` </DefaultErrorBoundary>`, ` </Document>`, ` </DefaultErrorBoundary>`] : [` <Document>`, ` ${content}`, ` </Document>`];
1494
1511
  }
1495
- function generatedEntryServerCode() {
1512
+ function generatedEntryServerCode(toolbar) {
1496
1513
  if (clientMode) {
1497
1514
  // The client-mode shell: the document without the app. Rendered per
1498
1515
  // request in dev (any HTML GET gets it — history-fallback semantics)
@@ -1504,7 +1521,7 @@ function startServe(options, internal = {}) {
1504
1521
  app
1505
1522
  } = requireEntries();
1506
1523
  const streamOptions = `{ manifest${serverComponents ? ', plugins: [ServerComponentPlugin]' : ''} }`;
1507
- 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 ? [
1524
+ 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 ? [
1508
1525
  // Direct (in-process) server-function calls made during document
1509
1526
  // SSR must resolve to inline-renderable components; the endpoint
1510
1527
  // response transform is installed separately by the
@@ -1518,7 +1535,7 @@ function startServe(options, internal = {}) {
1518
1535
  // the *complete* render) and buffers the stream — so it crosses
1519
1536
  // boxed under a private key the generated handler unboxes
1520
1537
  // (both modules are ours).
1521
- `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');
1538
+ `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');
1522
1539
  }
1523
1540
  function generatedEntryClientCode(toolbar) {
1524
1541
  const {
@@ -1530,9 +1547,9 @@ function startServe(options, internal = {}) {
1530
1547
  // app cannot claim server DOM anyway. The entry script is injected
1531
1548
  // without `async` (plain module = deferred), so document.body is
1532
1549
  // complete when this runs.
1533
- 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');
1550
+ 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');
1534
1551
  }
1535
- 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 ? [
1552
+ 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 ? [
1536
1553
  // Installs the t=0 document-adoption registry and the transport
1537
1554
  // policy (component responses morph their boundary instead of
1538
1555
  // decoding as data). Must run before hydrate().
@@ -1713,9 +1730,9 @@ function startServe(options, internal = {}) {
1713
1730
  enforce: 'pre',
1714
1731
  config(userConfig, env) {
1715
1732
  root = path.resolve(userConfig.root || process.cwd());
1716
- devtools = env.command === 'serve' && !env.isPreview && options.devtools !== false ? undefined : false;
1717
- devtoolsResolution = undefined;
1718
- devtoolsId = null;
1733
+ devtoolsEnabled = env.command === 'serve' && !env.isPreview && options.devtools !== false;
1734
+ devtoolsResolutions = {};
1735
+ devtoolsIds = {};
1719
1736
  entries = resolveEntries(root, options, clientMode);
1720
1737
  middlewarePath = options.middleware ? path.resolve(root, normalizeUserPath(root, options.middleware, 'middleware')) : null;
1721
1738
  // Server-mode only, like `entryServer`/`external` (a documented
@@ -1806,7 +1823,7 @@ function startServe(options, internal = {}) {
1806
1823
  }
1807
1824
  },
1808
1825
  // Presence of `builder` makes a plain `vite build` build the
1809
- // whole app (all environments: client then ssr) on Vite 6+.
1826
+ // whole app (all environments: client then ssr).
1810
1827
  // A classic `vite build --ssr` invocation must stay a
1811
1828
  // single-environment build, so it doesn't get the flag.
1812
1829
  ...(env.isSsrBuild ? {} : {
@@ -1836,23 +1853,32 @@ function startServe(options, internal = {}) {
1836
1853
  optimizeDeps: {
1837
1854
  entries: scanEntries,
1838
1855
  // Like the refresh runtime in the main plugin: the toolbar
1839
- // graph is injected behind virtual modules the scanner never
1840
- // crawls, so pre-bundle it (and the server-functions runtime
1841
- // the virtual module pulls in) up front — first-request
1842
- // discovery would re-optimize and full-reload the page.
1843
- ...(devtools === undefined && devtoolsReachableFromRoot(root) ? {
1844
- include: [DEVTOOLS_PACKAGE, '@solidjs/web/server-functions']
1845
- } : {})
1856
+ // graph is injected behind modules the scanner never crawls,
1857
+ // so pre-bundle it and the server-functions runtime up front.
1858
+ ...(() => {
1859
+ const spec = devtoolsEnabled ? devtoolsIncludeSpec(root) : null;
1860
+ return spec ? {
1861
+ include: [spec, '@solidjs/web/server-functions']
1862
+ } : {};
1863
+ })()
1846
1864
  }
1847
1865
  })
1848
1866
  };
1849
1867
  },
1868
+ configEnvironment(name, config) {
1869
+ if (name !== 'ssr') return;
1870
+ config.resolve ??= {};
1871
+ const noExternal = config.resolve.noExternal;
1872
+ if (noExternal !== true) {
1873
+ config.resolve.noExternal = [...(Array.isArray(noExternal) ? noExternal : noExternal ? [noExternal] : []), DEVTOOLS_PACKAGE];
1874
+ }
1875
+ },
1850
1876
  configResolved(config) {
1851
1877
  root = config.root;
1852
1878
  base = config.base;
1853
1879
  isBuild = config.command === 'build';
1854
1880
  },
1855
- resolveId(source, importer) {
1881
+ resolveId(source, importer, opts) {
1856
1882
  if (source === HANDLER_ID) {
1857
1883
  return {
1858
1884
  id: HANDLER_ID,
@@ -1871,18 +1897,16 @@ function startServe(options, internal = {}) {
1871
1897
  moduleSideEffects: source === ENTRY_CLIENT_ID
1872
1898
  };
1873
1899
  }
1874
- if (devtools && (source === DEVTOOLS_ID || source === DEVTOOLS_MOUNT_ID)) {
1900
+ if (devtoolsEnabled && source === DEVTOOLS_MOUNT_ID) {
1875
1901
  return {
1876
1902
  id: source,
1877
1903
  moduleSideEffects: true
1878
1904
  };
1879
1905
  }
1880
- // The virtual devtools modules import the package by its bare name,
1881
- // but a virtual importer gives Vite no directory to walk, so the
1882
- // specifier would only resolve from the Vite root — which fails in
1883
- // pnpm-isolated apps where the package is not a root-level install.
1884
- // Delegate to the resolution captured at detection time instead.
1885
- if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === DEVTOOLS_ID || importer === DEVTOOLS_MOUNT_ID)) {
1906
+ // Generated modules have no directory for bare-package resolution.
1907
+ // Reuse the app-relative id captured during detection.
1908
+ const devtoolsId = devtoolsIds[getEnvironmentConsumer(this.environment, opts)];
1909
+ if (devtoolsId && source === DEVTOOLS_PACKAGE && (importer === ENTRY_SERVER_ID || importer === ENTRY_CLIENT_ID || importer === DEVTOOLS_MOUNT_ID)) {
1886
1910
  return {
1887
1911
  id: devtoolsId
1888
1912
  };
@@ -1904,37 +1928,40 @@ function startServe(options, internal = {}) {
1904
1928
  }
1905
1929
  return devStylesModuleCode(this.environment, file => this.addWatchFile(file));
1906
1930
  }
1907
- if (id === ENTRY_SERVER_ID) return generatedEntryServerCode();
1931
+ if (id === ENTRY_SERVER_ID) {
1932
+ const toolbar = clientMode ? false : await resolveDevtools((source, importer) => this.resolve(source, importer, {
1933
+ skipSelf: true
1934
+ }), requireEntries().app, 'server');
1935
+ return generatedEntryServerCode(toolbar);
1936
+ }
1908
1937
  if (id === ENTRY_CLIENT_ID) {
1909
1938
  const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
1910
1939
  skipSelf: true
1911
- }), requireEntries().app);
1940
+ }), requireEntries().app, 'client');
1912
1941
  return generatedEntryClientCode(toolbar);
1913
1942
  }
1914
1943
  if (id === DOCUMENT_ID) return documentShellCode;
1915
1944
  if (id === ERROR_BOUNDARY_ID) return errorBoundaryCode;
1916
- if (id === DEVTOOLS_ID || id === DEVTOOLS_MOUNT_ID) {
1917
- // A cold direct request (stale tab reload) can reach the virtual
1918
- // module before the entry has triggered detection — run it here so
1919
- // first-touch order doesn't matter.
1920
- if (!isBuild && consumer === 'client' && devtools === undefined) {
1945
+ if (id === DEVTOOLS_MOUNT_ID) {
1946
+ let enabled = false;
1947
+ if (devtoolsEnabled && consumer === 'client') {
1921
1948
  const {
1922
1949
  app,
1923
1950
  entryClient
1924
1951
  } = requireEntries();
1925
- await resolveDevtools((source, importer) => this.resolve(source, importer, {
1952
+ enabled = await resolveDevtools((source, importer) => this.resolve(source, importer, {
1926
1953
  skipSelf: true
1927
- }), app ?? path.resolve(root, entryClient));
1954
+ }), app ?? path.resolve(root, entryClient), 'client');
1928
1955
  }
1929
- if (isBuild || !devtools || consumer !== 'client') {
1956
+ if (!enabled) {
1930
1957
  this.error(`${id} is only available to the development client.`);
1931
1958
  }
1932
- return id === DEVTOOLS_ID ? devtoolsModuleCode() : devtoolsMountModuleCode();
1959
+ return devtoolsMountModuleCode();
1933
1960
  }
1934
1961
  return null;
1935
1962
  },
1936
1963
  async transform(code, id, opts) {
1937
- if (isBuild || devtools === false) return null;
1964
+ if (isBuild || !devtoolsEnabled) return null;
1938
1965
  const current = requireEntries();
1939
1966
  if (current.generated || getEnvironmentConsumer(this.environment, opts) !== 'client') {
1940
1967
  return null;
@@ -1946,7 +1973,7 @@ function startServe(options, internal = {}) {
1946
1973
  }
1947
1974
  const toolbar = await resolveDevtools((source, importer) => this.resolve(source, importer, {
1948
1975
  skipSelf: true
1949
- }), id);
1976
+ }), id, 'client');
1950
1977
  if (!toolbar) return null;
1951
1978
  return {
1952
1979
  code: `import ${JSON.stringify(DEVTOOLS_MOUNT_ID)};\n${code}`,
@@ -2010,7 +2037,7 @@ function startServe(options, internal = {}) {
2010
2037
  // that gets the streamed SSR render.
2011
2038
  return () => {
2012
2039
  const ssrEnvironment = server.environments.ssr;
2013
- if (externalServer || ssrEnvironment && !isRunnableEnvironment(ssrEnvironment)) {
2040
+ if (externalServer || !isRunnableEnvironment(ssrEnvironment)) {
2014
2041
  return;
2015
2042
  }
2016
2043
  server.middlewares.use((req, res, next) => {
@@ -2027,7 +2054,7 @@ function startServe(options, internal = {}) {
2027
2054
  (async () => {
2028
2055
  // Loaded through the SSR environment so the app, the request
2029
2056
  // event storage, and the handler share one module registry.
2030
- const handler = await server.ssrLoadModule(HANDLER_ID);
2057
+ const handler = await ssrEnvironment.runner.import(HANDLER_ID);
2031
2058
  const styles = pageRequest ? await collectDevStyles(server, styleRoots(), styleFilter) : [];
2032
2059
  const devHead = styles.map(renderDevStyleTag).join('');
2033
2060
  // Post middlewares run after Vite's base middleware stripped
@@ -2052,7 +2079,6 @@ function startServe(options, internal = {}) {
2052
2079
  if (response.headers.has(DEV_FALLTHROUGH_HEADER)) return next();
2053
2080
  await sendWebResponse(res, response);
2054
2081
  })().catch(error => {
2055
- if (error instanceof Error) server.ssrFixStacktrace(error);
2056
2082
  // Vite's error middleware renders the overlay-enabled 500 page.
2057
2083
  next(error);
2058
2084
  });
@@ -2142,7 +2168,7 @@ function startServe(options, internal = {}) {
2142
2168
  // https://github.com/pyyupsk/vite-env), the design-correct prior art. The
2143
2169
  // implementation is fresh against this plugin's machinery: Standard Schema
2144
2170
  // is the only contract (no zod dependency or zod-specific paths), the
2145
- // schema file loads through Vite's own `runnerImport`/`loadConfigFromFile`
2171
+ // schema file loads through Vite's own `runnerImport`
2146
2172
  // (no jiti), server-graph protection keys off the environment *consumer*
2147
2173
  // rather than environment-name lists, and the types are inferred from the
2148
2174
  // user's schema instead of introspected per-library.
@@ -2201,37 +2227,18 @@ function formatValidationError(issues, envFile, mode) {
2201
2227
  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.`;
2202
2228
  }
2203
2229
 
2204
- /**
2205
- * Loads the schema module at config time through Vite itself: `runnerImport`
2206
- * (Vite 6.1+) evaluates TypeScript in-process with project resolution;
2207
- * older Vite 6 falls back to `loadConfigFromFile`, the exact machinery that
2208
- * loads vite.config.ts.
2209
- */
2230
+ /** Loads the schema module through Vite with project resolution. */
2210
2231
  async function importSchemaModule(envFileAbs, root, mode) {
2211
- const vite = await import('vite');
2212
- if (typeof vite.runnerImport === 'function') {
2213
- const {
2214
- module,
2215
- dependencies
2216
- } = await vite.runnerImport(envFileAbs, {
2217
- root,
2218
- mode
2219
- });
2220
- return {
2221
- exported: module?.default,
2222
- dependencies: (dependencies || []).map(dep => path.resolve(root, dep)).filter(dep => existsSync(dep))
2223
- };
2224
- }
2225
- const result = await vite.loadConfigFromFile({
2226
- command: 'serve',
2232
+ const {
2233
+ module,
2234
+ dependencies
2235
+ } = await runnerImport(envFileAbs, {
2236
+ root,
2227
2237
  mode
2228
- }, envFileAbs, root);
2229
- if (!result) {
2230
- throw new Error(`[@solidjs/vite-plugin] failed to load env schema from ${envFileAbs}`);
2231
- }
2238
+ });
2232
2239
  return {
2233
- exported: result.config,
2234
- dependencies: (result.dependencies || []).map(dep => path.resolve(root, dep)).filter(dep => existsSync(dep))
2240
+ exported: module?.default,
2241
+ dependencies: dependencies.map(dep => path.resolve(root, dep)).filter(dep => existsSync(dep))
2235
2242
  };
2236
2243
  }
2237
2244
  function assertSchemaShape(exported, envFile, envPrefixes) {
@@ -2715,8 +2722,6 @@ const LAZY_PLACEHOLDER_PREFIX = '__SOLID_LAZY_MODULE__:';
2715
2722
  * solid-refresh#85 — is no longer used at all).
2716
2723
  */
2717
2724
  const REFRESH_RUNTIME_SOURCE = 'solid-js/refresh';
2718
- const viteVersionMajor = +version.split('.')[0];
2719
- const isVite8 = viteVersionMajor >= 8;
2720
2725
  const DEFAULT_STYLE_EXCLUDE = /node_modules/;
2721
2726
  const VIRTUAL_MANIFEST_ID = 'virtual:solid-manifest';
2722
2727
  const RESOLVED_VIRTUAL_MANIFEST_ID = '\0' + VIRTUAL_MANIFEST_ID;
@@ -3207,40 +3212,24 @@ function solidPlugin(options = {}) {
3207
3212
  // server-components runtime installs its response policy there).
3208
3213
  ...(command === 'serve' && serverComponents ? ['@solidjs/web/frames', '@solidjs/web/server-functions'] : []), ...solidPkgsConfig.optimizeDeps.include],
3209
3214
  exclude: solidPkgsConfig.optimizeDeps.exclude,
3210
- // Vite 8+ uses Rolldown for dependency scanning. Rolldown defaults to
3211
- // React's automatic JSX runtime for .tsx files, injecting a
3212
- // react/jsx-dev-runtime import that fails to resolve and aborts the
3213
- // scan. 'preserve' is no fix: the scanner re-parses the transformed
3214
- // output as plain JS, so any preserved JSX is a hard parse error
3215
- // (issue #262). The classic runtime is the only scan-safe lowering:
3216
- // it emits bare `React.createElement` calls without injecting any
3217
- // import, and the scan output is never executed — it only exists so
3218
- // rolldown can walk the import graph.
3219
- ...(isVite8 ? {
3220
- rolldownOptions: {
3221
- transform: {
3222
- jsx: {
3223
- runtime: 'classic'
3224
- }
3215
+ // Keep Solid TSX from injecting React's automatic runtime during scanning.
3216
+ rolldownOptions: {
3217
+ transform: {
3218
+ jsx: {
3219
+ runtime: 'classic'
3225
3220
  }
3226
3221
  }
3227
- } : {})
3222
+ }
3228
3223
  },
3229
3224
  ...(Object.keys(test).length ? {
3230
3225
  test
3231
3226
  } : {})
3232
3227
  };
3233
3228
  },
3234
- // @ts-ignore This hook only works in Vite 6
3235
- async configEnvironment(name, config, opts) {
3229
+ configEnvironment(name, config, opts) {
3236
3230
  config.resolve ??= {};
3237
3231
  // Emulate Vite default fallback for `resolve.conditions` if not set
3238
3232
  if (config.resolve.conditions == null) {
3239
- // @ts-ignore These exports only exist in Vite 6
3240
- const {
3241
- defaultClientConditions,
3242
- defaultServerConditions
3243
- } = await import('vite');
3244
3233
  if (config.consumer === 'client' || name === 'client' || opts.isSsrTargetWebworker) {
3245
3234
  config.resolve.conditions = [...defaultClientConditions];
3246
3235
  } else {
@@ -3575,8 +3564,7 @@ function solidPlugin(options = {}) {
3575
3564
  // would bake a manifest-less fallback into the server bundle. Every user
3576
3565
  // of such a setup had to hand-write this ordering plugin; absorb it.
3577
3566
  //
3578
- // Semantics (Vite 7.1+; Vite 6 has no plugin `buildApp` hook and ignores
3579
- // these, keeping its build-everything default):
3567
+ // Semantics:
3580
3568
  // - The first hook builds the client environment first, but only where
3581
3569
  // the ordering matters: a client build that emits a manifest and
3582
3570
  // actually has an input. It runs at *normal* order, deliberately not