@solidjs/vite-plugin 3.0.0-next.29 → 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.
@@ -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
- const url = new URL(urlPath ?? req.url ?? '/', `http://${req.headers.host || 'localhost'}`);
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
- if (!response.body) {
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
  }
@@ -1010,7 +1044,7 @@ function serverFunctions(options = {}, internal = {}) {
1010
1044
  nativeEvent: req
1011
1045
  }
1012
1046
  };
1013
- const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl), dispatchOptions);
1047
+ const response = internal.ssrHandler ? await handler.handleRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions) : await handler.handleServerFunctionRequest(webRequestFromNode(req, dispatchUrl, res), dispatchOptions);
1014
1048
  await sendWebResponse(res, response);
1015
1049
  })().catch(error => {
1016
1050
  if (error instanceof Error) server.ssrFixStacktrace(error);
@@ -1804,7 +1838,7 @@ function startServe(options, internal = {}) {
1804
1838
  // server-function endpoint) and hands the URL to application
1805
1839
  // code, so restore the base — the deployed production handler
1806
1840
  // receives base-prefixed URLs and preview must match it.
1807
- const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')),
1841
+ const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/'), res),
1808
1842
  // Same event extension the dev middleware and a production
1809
1843
  // Node entry pass: the raw Node request as `nativeEvent`.
1810
1844
  {
@@ -1861,7 +1895,7 @@ function startServe(options, internal = {}) {
1861
1895
  // the configured `base` from req.url; restore it so the app
1862
1896
  // sees the same URLs in dev as in production (where the
1863
1897
  // deployed handler receives base-prefixed requests).
1864
- const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/')), {
1898
+ const response = await handler.handleRequest(webRequestFromNode(req, joinBase(base, req.url || '/'), res), {
1865
1899
  devHead,
1866
1900
  pageRequest,
1867
1901
  // The raw Node request on the event, matching what a