@solidjs/vite-plugin 3.0.0-next.28 → 3.0.0-next.30
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 +123 -14
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.mjs +123 -14
- package/dist/esm/index.mjs.map +1 -1
- package/dist/types/src/http.d.ts +8 -1
- package/dist/types/src/ssr/index.d.ts +5 -0
- package/package.json +2 -2
- package/virtual-solid-manifest.d.ts +21 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -21,24 +21,54 @@ import { crawlFrameworkPkgs } from 'vitefu';
|
|
|
21
21
|
* different URL than the one node saw — the dev middlewares use it to
|
|
22
22
|
* restore the configured Vite `base` that the dev/preview base middleware
|
|
23
23
|
* stripped, so the handler always sees production-shaped URLs.
|
|
24
|
+
*
|
|
25
|
+
* Handles plain HTTP/1 *and* the HTTP/2 compat API: Vite's dev server uses
|
|
26
|
+
* `http2.createSecureServer({ allowHTTP1: true })` whenever `server.https`
|
|
27
|
+
* is set without a proxy, so under https the middlewares receive
|
|
28
|
+
* `Http2ServerRequest`s. The h2/protocol/abort techniques here are
|
|
29
|
+
* reimplemented from srvx's Node adapter (github.com/h3js/srvx,
|
|
30
|
+
* src/adapters/_node) — reference, not copied code.
|
|
24
31
|
*/
|
|
25
|
-
function webRequestFromNode(req, urlPath) {
|
|
26
|
-
|
|
32
|
+
function webRequestFromNode(req, urlPath, res) {
|
|
33
|
+
// TLS sockets (https and h2) expose `encrypted`; a Request whose url says
|
|
34
|
+
// http: on a TLS connection breaks secure-cookie logic, absolute
|
|
35
|
+
// redirects, and origin checks in application code.
|
|
36
|
+
const protocol = req.socket?.encrypted ? 'https' : 'http';
|
|
37
|
+
// HTTP/2 has no Host header — the authority travels in the `:authority`
|
|
38
|
+
// pseudo-header instead.
|
|
39
|
+
const host = req.headers.host ?? req.headers[':authority'] ?? 'localhost';
|
|
40
|
+
const url = new URL(urlPath ?? req.url ?? '/', `${protocol}://${host}`);
|
|
27
41
|
const headers = new Headers();
|
|
28
42
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
29
43
|
if (value === undefined) continue;
|
|
44
|
+
// HTTP/2 pseudo-headers (:method, :path, :authority, :scheme) are not
|
|
45
|
+
// legal field names — Headers#append throws a TypeError on them.
|
|
46
|
+
if (key[0] === ':') continue;
|
|
30
47
|
if (Array.isArray(value)) {
|
|
31
48
|
for (const item of value) headers.append(key, item);
|
|
32
49
|
} else {
|
|
33
50
|
headers.append(key, value);
|
|
34
51
|
}
|
|
35
52
|
}
|
|
53
|
+
// Surface client disconnects as the request's AbortSignal so handlers can
|
|
54
|
+
// cancel work (streamed SSR renders, in-flight fetches). The response's
|
|
55
|
+
// 'close' fires on normal completion too; `writableEnded` distinguishes a
|
|
56
|
+
// finished response from a client that went away.
|
|
57
|
+
let signal;
|
|
58
|
+
if (res) {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
res.once('close', () => {
|
|
61
|
+
if (!res.writableEnded) controller.abort();
|
|
62
|
+
});
|
|
63
|
+
signal = controller.signal;
|
|
64
|
+
}
|
|
36
65
|
const method = req.method || 'GET';
|
|
37
66
|
const body = method === 'GET' || method === 'HEAD' ? undefined : Readable.toWeb(req);
|
|
38
67
|
return new Request(url, {
|
|
39
68
|
method,
|
|
40
69
|
headers,
|
|
41
70
|
body,
|
|
71
|
+
signal,
|
|
42
72
|
// undici requires half-duplex for streamed request bodies.
|
|
43
73
|
...(body ? {
|
|
44
74
|
duplex: 'half'
|
|
@@ -53,7 +83,11 @@ async function sendWebResponse(res, response) {
|
|
|
53
83
|
if (key !== 'set-cookie') res.setHeader(key, value);
|
|
54
84
|
});
|
|
55
85
|
if (cookies && cookies.length) res.setHeader('set-cookie', cookies);
|
|
56
|
-
|
|
86
|
+
// HEAD gets the head only — and the body must be *cancelled*, not pumped:
|
|
87
|
+
// node discards HEAD body writes, so streaming a long (or endless) body
|
|
88
|
+
// into the void just burns the render. (Technique from srvx.)
|
|
89
|
+
if (!response.body || res.req?.method === 'HEAD') {
|
|
90
|
+
response.body?.cancel().catch(() => {});
|
|
57
91
|
res.end();
|
|
58
92
|
return;
|
|
59
93
|
}
|
|
@@ -910,7 +944,15 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
910
944
|
// the same value (config merges per key), but the handler graph cannot
|
|
911
945
|
// depend on that: in dev, a mutation from an already-open page can be
|
|
912
946
|
// the first request after a server restart.
|
|
913
|
-
`configureServerFunctionsServer({ provideEvent: provideRequestEvent, endpoint: ${JSON.stringify(resolvedEndpoint)}${components ? ', transformResult: frameTransformResult, transformFlightResult: frameTransformFlightResult, transformDirectResult: frameTransformDirectResult' : ''} });`, `export const endpoint = ${JSON.stringify(resolvedEndpoint)};`,
|
|
947
|
+
`configureServerFunctionsServer({ provideEvent: provideRequestEvent, endpoint: ${JSON.stringify(resolvedEndpoint)}${components ? ', transformResult: frameTransformResult, transformFlightResult: frameTransformFlightResult, transformDirectResult: frameTransformDirectResult' : ''} });`, `export const endpoint = ${JSON.stringify(resolvedEndpoint)};`,
|
|
948
|
+
// `options.event` is the same wrapper->event extension seam the SSR
|
|
949
|
+
// handler's handleRequest carries (conventionally `nativeEvent`, the
|
|
950
|
+
// platform's raw request object). The runtime's standalone handler
|
|
951
|
+
// creates its own event (`{ request, locals }`) with no init
|
|
952
|
+
// parameter, so the extension threads through its existing
|
|
953
|
+
// `createEvent` option instead — spread before `...options` so an
|
|
954
|
+
// explicit host-provided createEvent still wins.
|
|
955
|
+
`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');
|
|
914
956
|
}
|
|
915
957
|
|
|
916
958
|
// Function IDs are `xxHash32(root-relative path)-<count>` (see compile.ts),
|
|
@@ -994,7 +1036,15 @@ function serverFunctions(options = {}, internal = {}) {
|
|
|
994
1036
|
// middleware chain and one stub-backed request event front the
|
|
995
1037
|
// endpoint exactly as they front page SSR.
|
|
996
1038
|
const handler = await server.ssrLoadModule(internal.ssrHandler ?? HANDLER_ID$1);
|
|
997
|
-
|
|
1039
|
+
// Both dispatch shapes carry the raw Node request on the event
|
|
1040
|
+
// (the `options.event` seam), matching the SSR dev middleware
|
|
1041
|
+
// and what a production Node entry passes.
|
|
1042
|
+
const dispatchOptions = {
|
|
1043
|
+
event: {
|
|
1044
|
+
nativeEvent: req
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions);
|
|
998
1048
|
await sendWebResponse(res, response);
|
|
999
1049
|
})().catch(error => {
|
|
1000
1050
|
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
@@ -1566,7 +1616,14 @@ function startServe(options, internal = {}) {
|
|
|
1566
1616
|
// The runtime's response-head lifecycle: commit at shell flush,
|
|
1567
1617
|
// pre-flush Location as a real redirect, post-flush Location as the
|
|
1568
1618
|
// script fallback; the transform injects the doctype/head pieces.
|
|
1569
|
-
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1619
|
+
` return createSSRResponse(result, event, {`, ` responseInit: options.responseInit,`, ` nonce: options.nonce,`, ` transformChunk: createHtmlChunkTransform(clientEntry, options.devHead),`, ` });`, `}`, ``, `export async function handleRequest(request, options = {}) {`,
|
|
1620
|
+
// `options.event` is the public wrapper->event extension seam: extra
|
|
1621
|
+
// fields (conventionally `nativeEvent`, the platform's raw request
|
|
1622
|
+
// object) spread over the event's defaults at creation, so hosts and
|
|
1623
|
+
// custom server entries can extend what getRequestEvent() answers
|
|
1624
|
+
// with — no new convention beyond createRequestEvent's own init
|
|
1625
|
+
// parameter (spreading undefined is a no-op).
|
|
1626
|
+
` const event = createRequestEvent(request, options.event);`,
|
|
1570
1627
|
// Middleware runs inside the request scope, after event creation —
|
|
1571
1628
|
// getRequestEvent() answers in middleware exactly as in app code, and
|
|
1572
1629
|
// nothing reaches the wire until the outermost middleware returns.
|
|
@@ -1781,7 +1838,14 @@ function startServe(options, internal = {}) {
|
|
|
1781
1838
|
// server-function endpoint) and hands the URL to application
|
|
1782
1839
|
// code, so restore the base — the deployed production handler
|
|
1783
1840
|
// receives base-prefixed URLs and preview must match it.
|
|
1784
|
-
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/'))
|
|
1841
|
+
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/'), res),
|
|
1842
|
+
// Same event extension the dev middleware and a production
|
|
1843
|
+
// Node entry pass: the raw Node request as `nativeEvent`.
|
|
1844
|
+
{
|
|
1845
|
+
event: {
|
|
1846
|
+
nativeEvent: req
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1785
1849
|
// Preview's compression middleware buffers whole responses;
|
|
1786
1850
|
// opting HTML out keeps SSR streaming observable, matching
|
|
1787
1851
|
// production behavior.
|
|
@@ -1831,9 +1895,16 @@ function startServe(options, internal = {}) {
|
|
|
1831
1895
|
// the configured `base` from req.url; restore it so the app
|
|
1832
1896
|
// sees the same URLs in dev as in production (where the
|
|
1833
1897
|
// deployed handler receives base-prefixed requests).
|
|
1834
|
-
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')), {
|
|
1898
|
+
const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/'), res), {
|
|
1835
1899
|
devHead,
|
|
1836
|
-
pageRequest
|
|
1900
|
+
pageRequest,
|
|
1901
|
+
// The raw Node request on the event, matching what a
|
|
1902
|
+
// production Node server entry passes through the
|
|
1903
|
+
// `options.event` seam — getRequestEvent().nativeEvent
|
|
1904
|
+
// answers the same in dev as deployed.
|
|
1905
|
+
event: {
|
|
1906
|
+
nativeEvent: req
|
|
1907
|
+
}
|
|
1837
1908
|
});
|
|
1838
1909
|
// A non-page request the chain never handled: the terminal
|
|
1839
1910
|
// dispatch answered with the marked 404 — hand it back to
|
|
@@ -1914,6 +1985,11 @@ function startServe(options, internal = {}) {
|
|
|
1914
1985
|
// time work, secrets rotate without a rebuild, and no secret value exists
|
|
1915
1986
|
// in any dist artifact. Build-time server-value failures downgrade to a
|
|
1916
1987
|
// warning (boot enforces); dev failures stay hard errors — dev IS runtime.
|
|
1988
|
+
// Boot validation is synchronous by design: the generated server module
|
|
1989
|
+
// contains no top-level await (a TLA chunk forces esnext on downstream
|
|
1990
|
+
// bundlers — Nitro's node-server preset rejects it), which is why async
|
|
1991
|
+
// validators are rejected for `server` keys (client keys may stay async;
|
|
1992
|
+
// they are awaited at build time where the values are baked).
|
|
1917
1993
|
//
|
|
1918
1994
|
// A failed validation fails the build; in dev it renders Vite's error
|
|
1919
1995
|
// overlay with the per-key report (the virtual modules throw it on load)
|
|
@@ -2184,7 +2260,23 @@ function startEnv(option) {
|
|
|
2184
2260
|
for (const side of ['server', 'client']) {
|
|
2185
2261
|
for (const [key, validator] of Object.entries(schema[side] ?? {})) {
|
|
2186
2262
|
let result = validator['~standard'].validate(raw[key]);
|
|
2187
|
-
if (result instanceof Promise)
|
|
2263
|
+
if (result instanceof Promise) {
|
|
2264
|
+
// Async validation is fine for `client` keys — their values are
|
|
2265
|
+
// baked right here at build time, where awaiting costs nothing.
|
|
2266
|
+
// `server` keys validate process.env at boot through generated
|
|
2267
|
+
// code that is deliberately synchronous (a top-level await in the
|
|
2268
|
+
// server env chunk forces esnext on every downstream bundle
|
|
2269
|
+
// target — Nitro's node-server preset rejects it outright), so a
|
|
2270
|
+
// Promise-returning server validator could only ever fail at
|
|
2271
|
+
// deploy boot. Async-ness is a property of the schema, not the
|
|
2272
|
+
// value, so fail fast here with the fix in the message. Boot
|
|
2273
|
+
// still backstops (schemas whose sync prefix short-circuits at
|
|
2274
|
+
// build time can go async on real values).
|
|
2275
|
+
if (side === 'server') {
|
|
2276
|
+
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.`);
|
|
2277
|
+
}
|
|
2278
|
+
result = await result;
|
|
2279
|
+
}
|
|
2188
2280
|
if (result.issues && result.issues.length) {
|
|
2189
2281
|
for (const issue of result.issues) {
|
|
2190
2282
|
const at = (issue.path ?? []).map(segment => typeof segment === 'object' && segment !== null && 'key' in segment ? String(segment.key) : String(segment)).join('.');
|
|
@@ -2261,6 +2353,16 @@ function startEnv(option) {
|
|
|
2261
2353
|
// means. Platform-injected vars that don't exist at build time work,
|
|
2262
2354
|
// secrets rotate without a rebuild, and no secret value exists in any
|
|
2263
2355
|
// dist artifact.
|
|
2356
|
+
//
|
|
2357
|
+
// The generated code is deliberately free of top-level await. Module
|
|
2358
|
+
// init is the only point where "validated and frozen before any
|
|
2359
|
+
// importer's body runs" can be guaranteed — user server modules read
|
|
2360
|
+
// `env.KEY` at their own top level — and the only async thing here is
|
|
2361
|
+
// Standard Schema's option to return a Promise from validate(). A TLA
|
|
2362
|
+
// chunk breaks every downstream bundler with a non-esnext target
|
|
2363
|
+
// (Nitro's node-server preset in practice), so validation runs
|
|
2364
|
+
// synchronously and a Promise-returning server validator is itself a
|
|
2365
|
+
// boot issue (build/config time rejects it earlier when detectable).
|
|
2264
2366
|
function serverEnvModuleCode(loaded) {
|
|
2265
2367
|
const serverKeys = Object.keys(loaded.schema.server ?? {});
|
|
2266
2368
|
const baked = `const __env = ${JSON.stringify(loaded.client)};`;
|
|
@@ -2271,7 +2373,7 @@ function startEnv(option) {
|
|
|
2271
2373
|
};
|
|
2272
2374
|
}
|
|
2273
2375
|
return {
|
|
2274
|
-
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)}) {`, `
|
|
2376
|
+
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'),
|
|
2275
2377
|
moduleType: 'js'
|
|
2276
2378
|
};
|
|
2277
2379
|
}
|
|
@@ -2947,12 +3049,19 @@ function solidPlugin(options = {}) {
|
|
|
2947
3049
|
exclude: solidPkgsConfig.optimizeDeps.exclude,
|
|
2948
3050
|
// Vite 8+ uses Rolldown for dependency scanning. Rolldown defaults to
|
|
2949
3051
|
// React's automatic JSX runtime for .tsx files, injecting a
|
|
2950
|
-
// react/jsx-dev-runtime import
|
|
2951
|
-
//
|
|
3052
|
+
// react/jsx-dev-runtime import that fails to resolve and aborts the
|
|
3053
|
+
// scan. 'preserve' is no fix: the scanner re-parses the transformed
|
|
3054
|
+
// output as plain JS, so any preserved JSX is a hard parse error
|
|
3055
|
+
// (issue #262). The classic runtime is the only scan-safe lowering:
|
|
3056
|
+
// it emits bare `React.createElement` calls without injecting any
|
|
3057
|
+
// import, and the scan output is never executed — it only exists so
|
|
3058
|
+
// rolldown can walk the import graph.
|
|
2952
3059
|
...(isVite8 ? {
|
|
2953
3060
|
rolldownOptions: {
|
|
2954
3061
|
transform: {
|
|
2955
|
-
jsx:
|
|
3062
|
+
jsx: {
|
|
3063
|
+
runtime: 'classic'
|
|
3064
|
+
}
|
|
2956
3065
|
}
|
|
2957
3066
|
}
|
|
2958
3067
|
} : {})
|