@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 +21 -0
- package/README.md +56 -1
- package/dist/cjs/index.cjs +85 -10
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +85 -10
- package/dist/esm/index.mjs.map +1 -1
- package/dist/types/src/ssr/index.d.ts +5 -0
- package/package.json +1 -1
- package/virtual-solid-manifest.d.ts +21 -1
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
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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)};`,
|
|
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
|
-
|
|
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 = {}) {`,
|
|
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)
|
|
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)}) {`, `
|
|
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
|
|
2975
|
-
//
|
|
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:
|
|
3052
|
+
jsx: {
|
|
3053
|
+
runtime: 'classic'
|
|
3054
|
+
}
|
|
2980
3055
|
}
|
|
2981
3056
|
}
|
|
2982
3057
|
} : {})
|