@solidjs/vite-plugin 3.0.0-next.28 → 3.0.0-next.29

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2026 Alexandre Mouton-Brady and Solid contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -222,6 +222,41 @@ The Fetchable wrapper deliberately accepts only the request. Hosts may pass
222
222
  environment or execution-context arguments after it; those are not the
223
223
  Solid options accepted by `handleRequest`'s second parameter.
224
224
 
225
+ Among those options, **`event`** is the supported public seam for extending
226
+ the request event: its fields spread into the event at creation, so a custom
227
+ server entry (or a host wrapper) can attach whatever its platform knows and
228
+ read it back anywhere in the request scope with `getRequestEvent()`. The
229
+ conventional field name is `nativeEvent` — the platform's raw request
230
+ object. A Node entry passes the `IncomingMessage`:
231
+
232
+ ```js
233
+ import { createServer } from 'node:http';
234
+ import { handleRequest } from './dist/server/server.js';
235
+
236
+ createServer(async (req, res) => {
237
+ const response = await handleRequest(webRequest(req), {
238
+ event: { nativeEvent: req },
239
+ });
240
+ // ... write response to res
241
+ });
242
+ ```
243
+
244
+ ```js
245
+ // anywhere inside the request scope (middleware, setup, app code)
246
+ import { getRequestEvent } from '@solidjs/web';
247
+ const event = getRequestEvent();
248
+ event.nativeEvent; // the Node IncomingMessage the entry passed
249
+ ```
250
+
251
+ The plugin's own dev and preview middlewares (and the server-function dev
252
+ middleware) pass `event: { nativeEvent: req }` with the Node request, so
253
+ `getRequestEvent().nativeEvent` answers the same under `vite dev` and
254
+ `vite preview` as behind a Node entry written like the above. For the
255
+ client's IP on bare Node, read `event.nativeEvent.socket.remoteAddress`;
256
+ behind a proxy or load balancer that address is the proxy's, so read the
257
+ forwarding headers off `event.request` instead (`x-forwarded-for` and
258
+ friends) — only when you trust the proxy that set them.
259
+
225
260
  - **Preview**: `vite build && vite preview` runs the production artifact
226
261
  with no server file — Vite's preview statics serve `dist/client`, and
227
262
  everything else (pages, the server-function endpoint, middleware)
@@ -361,7 +396,14 @@ import { env } from 'virtual:env/client'; // the VITE_-prefixed client vars
361
396
  is fine). Platform-injected vars that don't exist at build time work,
362
397
  secrets rotate without a rebuild, and no secret value exists in any
363
398
  dist artifact; an invalid server environment fails boot with the same
364
- per-key report.
399
+ per-key report. Boot validation is synchronous: the generated server
400
+ env module contains no top-level await, so the server bundle works
401
+ under any downstream build target (Nitro's node-server preset,
402
+ es2020 — no `esnext` override needed). The flip side: `server`
403
+ validators must be synchronous — an async refinement/transform on a
404
+ server key is rejected at config time with the fix in the message
405
+ (`client` keys may stay async; they are awaited at build time where
406
+ the values are baked).
365
407
  - **Leaks are errors.** Importing `virtual:env/server` from a client
366
408
  module graph is a hard error naming the importer (the app root and
367
409
  everything it imports hydrate — they are client code; keep server env
@@ -434,6 +476,19 @@ Two explicit switches remain for custom host setups:
434
476
  serving, hands only server-function dispatch in dev to the host. For
435
477
  setups without `start`, or when only the endpoint should move.
436
478
 
479
+ **`virtual:solid-manifest`** exposes the client asset manifest that serving
480
+ works from — a server-side module, available in dev and in SSR builds. In
481
+ an SSR build its default export is the parsed client manifest
482
+ (`dist/client/.vite/manifest.json`), keyed by source path with the resolved
483
+ Vite `base` attached as `_base`; in dev it exports the live asset resolver
484
+ the plugin uses for dev CSS collection instead of a static object. This is
485
+ the seam for frameworks and routers that do their own asset gating —
486
+ deciding which scripts and styles a response carries, as the TanStack Start
487
+ integration does — without re-reading the manifest from disk or re-deriving
488
+ `base`. Ambient types ship with the plugin
489
+ (`/// <reference types="@solidjs/vite-plugin/virtual-solid-manifest" />`);
490
+ see that `.d.ts` and the exported `ViteManifest` type for the full shape.
491
+
437
492
  Without `ssr: true` — **client mode** (experimental), the same conventions
438
493
  with client-only rendering:
439
494
 
@@ -934,7 +934,15 @@ function serverFunctions(options = {}, internal = {}) {
934
934
  // the same value (config merges per key), but the handler graph cannot
935
935
  // depend on that: in dev, a mutation from an already-open page can be
936
936
  // the first request after a server restart.
937
- `configureServerFunctionsServer({ provideEvent: provideRequestEvent, endpoint: ${JSON.stringify(resolvedEndpoint)}${components ? ', transformResult: frameTransformResult, transformFlightResult: frameTransformFlightResult, transformDirectResult: frameTransformDirectResult' : ''} });`, `export const endpoint = ${JSON.stringify(resolvedEndpoint)};`, `export function handleServerFunctionRequest(request, options) {`, ` return handle(request, { provideEvent: provideRequestEvent, ...options });`, `}`].join('\n');
937
+ `configureServerFunctionsServer({ provideEvent: provideRequestEvent, endpoint: ${JSON.stringify(resolvedEndpoint)}${components ? ', transformResult: frameTransformResult, transformFlightResult: frameTransformFlightResult, transformDirectResult: frameTransformDirectResult' : ''} });`, `export const endpoint = ${JSON.stringify(resolvedEndpoint)};`,
938
+ // `options.event` is the same wrapper->event extension seam the SSR
939
+ // handler's handleRequest carries (conventionally `nativeEvent`, the
940
+ // platform's raw request object). The runtime's standalone handler
941
+ // creates its own event (`{ request, locals }`) with no init
942
+ // parameter, so the extension threads through its existing
943
+ // `createEvent` option instead — spread before `...options` so an
944
+ // explicit host-provided createEvent still wins.
945
+ `export function handleServerFunctionRequest(request, options) {`, ` const { event: eventInit, ...rest } = options || {};`, ` return handle(request, {`, ` provideEvent: provideRequestEvent,`, ` ...(eventInit ? { createEvent: (req) => ({ request: req, locals: {}, ...eventInit }) } : {}),`, ` ...rest,`, ` });`, `}`].join('\n');
938
946
  }
939
947
 
940
948
  // Function IDs are `xxHash32(root-relative path)-<count>` (see compile.ts),
@@ -1018,7 +1026,15 @@ function serverFunctions(options = {}, internal = {}) {
1018
1026
  // middleware chain and one stub-backed request event front the
1019
1027
  // endpoint exactly as they front page SSR.
1020
1028
  const handler = await server.ssrLoadModule(internal.ssrHandler ?? HANDLER_ID$1);
1021
- const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl)) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl));
1029
+ // Both dispatch shapes carry the raw Node request on the event
1030
+ // (the `options.event` seam), matching the SSR dev middleware
1031
+ // and what a production Node entry passes.
1032
+ const dispatchOptions = {
1033
+ event: {
1034
+ nativeEvent: req
1035
+ }
1036
+ };
1037
+ const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl), dispatchOptions);
1022
1038
  await sendWebResponse(res, response);
1023
1039
  })().catch(error => {
1024
1040
  if (error instanceof Error) server.ssrFixStacktrace(error);
@@ -1590,7 +1606,14 @@ function startServe(options, internal = {}) {
1590
1606
  // The runtime's response-head lifecycle: commit at shell flush,
1591
1607
  // pre-flush Location as a real redirect, post-flush Location as the
1592
1608
  // script fallback; the transform injects the doctype/head pieces.
1593
- ` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`, ` const event = createRequestEvent(request);`,
1609
+ ` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
1610
+ // `options.event` is the public wrapper->event extension seam: extra
1611
+ // fields (conventionally `nativeEvent`, the platform's raw request
1612
+ // object) spread over the event's defaults at creation, so hosts and
1613
+ // custom server entries can extend what getRequestEvent() answers
1614
+ // with — no new convention beyond createRequestEvent's own init
1615
+ // parameter (spreading undefined is a no-op).
1616
+ ` const event = createRequestEvent(request, options.event);`,
1594
1617
  // Middleware runs inside the request scope, after event creation —
1595
1618
  // getRequestEvent() answers in middleware exactly as in app code, and
1596
1619
  // nothing reaches the wire until the outermost middleware returns.
@@ -1805,7 +1828,14 @@ function startServe(options, internal = {}) {
1805
1828
  // server-function endpoint) and hands the URL to application
1806
1829
  // code, so restore the base — the deployed production handler
1807
1830
  // receives base-prefixed URLs and preview must match it.
1808
- const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')));
1831
+ const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')),
1832
+ // Same event extension the dev middleware and a production
1833
+ // Node entry pass: the raw Node request as `nativeEvent`.
1834
+ {
1835
+ event: {
1836
+ nativeEvent: req
1837
+ }
1838
+ });
1809
1839
  // Preview's compression middleware buffers whole responses;
1810
1840
  // opting HTML out keeps SSR streaming observable, matching
1811
1841
  // production behavior.
@@ -1857,7 +1887,14 @@ function startServe(options, internal = {}) {
1857
1887
  // deployed handler receives base-prefixed requests).
1858
1888
  const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')), {
1859
1889
  devHead,
1860
- pageRequest
1890
+ pageRequest,
1891
+ // The raw Node request on the event, matching what a
1892
+ // production Node server entry passes through the
1893
+ // `options.event` seam — getRequestEvent().nativeEvent
1894
+ // answers the same in dev as deployed.
1895
+ event: {
1896
+ nativeEvent: req
1897
+ }
1861
1898
  });
1862
1899
  // A non-page request the chain never handled: the terminal
1863
1900
  // dispatch answered with the marked 404 — hand it back to
@@ -1938,6 +1975,11 @@ function startServe(options, internal = {}) {
1938
1975
  // time work, secrets rotate without a rebuild, and no secret value exists
1939
1976
  // in any dist artifact. Build-time server-value failures downgrade to a
1940
1977
  // warning (boot enforces); dev failures stay hard errors — dev IS runtime.
1978
+ // Boot validation is synchronous by design: the generated server module
1979
+ // contains no top-level await (a TLA chunk forces esnext on downstream
1980
+ // bundlers — Nitro's node-server preset rejects it), which is why async
1981
+ // validators are rejected for `server` keys (client keys may stay async;
1982
+ // they are awaited at build time where the values are baked).
1941
1983
  //
1942
1984
  // A failed validation fails the build; in dev it renders Vite's error
1943
1985
  // overlay with the per-key report (the virtual modules throw it on load)
@@ -2208,7 +2250,23 @@ function startEnv(option) {
2208
2250
  for (const side of ['server', 'client']) {
2209
2251
  for (const [key, validator] of Object.entries(schema[side] ?? {})) {
2210
2252
  let result = validator['~standard'].validate(raw[key]);
2211
- if (result instanceof Promise) result = await result;
2253
+ if (result instanceof Promise) {
2254
+ // Async validation is fine for `client` keys — their values are
2255
+ // baked right here at build time, where awaiting costs nothing.
2256
+ // `server` keys validate process.env at boot through generated
2257
+ // code that is deliberately synchronous (a top-level await in the
2258
+ // server env chunk forces esnext on every downstream bundle
2259
+ // target — Nitro's node-server preset rejects it outright), so a
2260
+ // Promise-returning server validator could only ever fail at
2261
+ // deploy boot. Async-ness is a property of the schema, not the
2262
+ // value, so fail fast here with the fix in the message. Boot
2263
+ // still backstops (schemas whose sync prefix short-circuits at
2264
+ // build time can go async on real values).
2265
+ if (side === 'server') {
2266
+ throw new Error(`[@solidjs/vite-plugin] server env var "${key}" in ${envFile} uses an async ` + `validator (validate() returned a Promise). Server env is validated ` + `synchronously at boot — the generated module contains no top-level ` + `await, so server bundles work on non-esnext targets — which async ` + `validators cannot do. Make the validator synchronous (drop async ` + `refinements/transforms), or run the async check in application code.`);
2267
+ }
2268
+ result = await result;
2269
+ }
2212
2270
  if (result.issues && result.issues.length) {
2213
2271
  for (const issue of result.issues) {
2214
2272
  const at = (issue.path ?? []).map(segment => typeof segment === 'object' && segment !== null && 'key' in segment ? String(segment.key) : String(segment)).join('.');
@@ -2285,6 +2343,16 @@ function startEnv(option) {
2285
2343
  // means. Platform-injected vars that don't exist at build time work,
2286
2344
  // secrets rotate without a rebuild, and no secret value exists in any
2287
2345
  // dist artifact.
2346
+ //
2347
+ // The generated code is deliberately free of top-level await. Module
2348
+ // init is the only point where "validated and frozen before any
2349
+ // importer's body runs" can be guaranteed — user server modules read
2350
+ // `env.KEY` at their own top level — and the only async thing here is
2351
+ // Standard Schema's option to return a Promise from validate(). A TLA
2352
+ // chunk breaks every downstream bundler with a non-esnext target
2353
+ // (Nitro's node-server preset in practice), so validation runs
2354
+ // synchronously and a Promise-returning server validator is itself a
2355
+ // boot issue (build/config time rejects it earlier when detectable).
2288
2356
  function serverEnvModuleCode(loaded) {
2289
2357
  const serverKeys = Object.keys(loaded.schema.server ?? {});
2290
2358
  const baked = `const __env = ${JSON.stringify(loaded.client)};`;
@@ -2295,7 +2363,7 @@ function startEnv(option) {
2295
2363
  };
2296
2364
  }
2297
2365
  return {
2298
- code: [`// Generated by @solidjs/vite-plugin (start.env) — server env.`, `// Server values are read from process.env and validated at boot;`, `// client (public) values are baked at build time.`, `import __schema from ${JSON.stringify(envFileAbs)};`, baked, `const __issues = [];`, `for (const __key of ${JSON.stringify(serverKeys)}) {`, ` let __result = __schema.server[__key]['~standard'].validate(process.env[__key]);`, ` if (__result instanceof Promise) __result = await __result;`, ` if (__result.issues && __result.issues.length) {`, ` for (const __issue of __result.issues) __issues.push(' \\u2717 ' + __key + ': ' + __issue.message);`, ` } else {`, ` __env[__key] = __result.value;`, ` }`, `}`, `if (__issues.length) {`, ` throw new Error(`, ` '[@solidjs/vite-plugin] server env validation failed at boot (' + __issues.length +`, ` ' issue' + (__issues.length === 1 ? '' : 's') + ') \\u2014 schema: ' + ${JSON.stringify(envFile)} +`, ` '\\n\\n' + __issues.join('\\n') +`, ` '\\n\\nServer env is read from process.env at boot, not baked at build time: set the ' +`, ` 'variables in the server process environment.'`, ` );`, `}`, `export const env = Object.freeze(__env);`, `export default env;`].join('\n'),
2366
+ code: [`// Generated by @solidjs/vite-plugin (start.env) — server env.`, `// Server values are read from process.env and validated at boot;`, `// client (public) values are baked at build time. Boot validation is`, `// synchronous on purpose: a top-level await here would force esnext`, `// on every downstream bundle target (Nitro's node-server preset and`, `// anything else below esnext rejects a TLA chunk outright).`, `import __schema from ${JSON.stringify(envFileAbs)};`, baked, `const __issues = [];`, `for (const __key of ${JSON.stringify(serverKeys)}) {`, ` const __result = __schema.server[__key]['~standard'].validate(process.env[__key]);`, ` if (__result && typeof __result.then === 'function') {`, ` __issues.push(' \\u2717 ' + __key + ': validator returned a Promise \\u2014 async validators are not supported for server keys (boot validation is synchronous so the server bundle carries no top-level await); make this validator synchronous');`, ` } else if (__result.issues && __result.issues.length) {`, ` for (const __issue of __result.issues) __issues.push(' \\u2717 ' + __key + ': ' + __issue.message);`, ` } else {`, ` __env[__key] = __result.value;`, ` }`, `}`, `if (__issues.length) {`, ` throw new Error(`, ` '[@solidjs/vite-plugin] server env validation failed at boot (' + __issues.length +`, ` ' issue' + (__issues.length === 1 ? '' : 's') + ') \\u2014 schema: ' + ${JSON.stringify(envFile)} +`, ` '\\n\\n' + __issues.join('\\n') +`, ` '\\n\\nServer env is read from process.env at boot, not baked at build time: set the ' +`, ` 'variables in the server process environment.'`, ` );`, `}`, `export const env = Object.freeze(__env);`, `export default env;`].join('\n'),
2299
2367
  moduleType: 'js'
2300
2368
  };
2301
2369
  }
@@ -2971,12 +3039,19 @@ function solidPlugin(options = {}) {
2971
3039
  exclude: solidPkgsConfig.optimizeDeps.exclude,
2972
3040
  // Vite 8+ uses Rolldown for dependency scanning. Rolldown defaults to
2973
3041
  // React's automatic JSX runtime for .tsx files, injecting a
2974
- // react/jsx-dev-runtime import. Tell it to preserve JSX as-is since
2975
- // this plugin handles JSX transformation via babel-preset-solid.
3042
+ // react/jsx-dev-runtime import that fails to resolve and aborts the
3043
+ // scan. 'preserve' is no fix: the scanner re-parses the transformed
3044
+ // output as plain JS, so any preserved JSX is a hard parse error
3045
+ // (issue #262). The classic runtime is the only scan-safe lowering:
3046
+ // it emits bare `React.createElement` calls without injecting any
3047
+ // import, and the scan output is never executed — it only exists so
3048
+ // rolldown can walk the import graph.
2976
3049
  ...(isVite8 ? {
2977
3050
  rolldownOptions: {
2978
3051
  transform: {
2979
- jsx: 'preserve'
3052
+ jsx: {
3053
+ runtime: 'classic'
3054
+ }
2980
3055
  }
2981
3056
  }
2982
3057
  } : {})