@solidjs/start 1.0.10 → 1.1.0

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.
@@ -58,7 +58,7 @@ export class SolidStartClientFileRouter extends BaseFileSystemRouter {
58
58
  }
59
59
  }
60
60
 
61
- const HTTP_METHODS = ["HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"];
61
+ const HTTP_METHODS = ["HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"];
62
62
  function createHTTPHandlers(src, exports) {
63
63
  const handlers = {};
64
64
  for (const exp of exports) {
package/config/index.js CHANGED
@@ -1,5 +1,4 @@
1
- import { serverFunctions } from "@vinxi/server-functions/plugin";
2
- import { server as serverFunctionServer, serverTransform } from "@vinxi/server-functions/server";
1
+ import { createTanStackServerFnPlugin } from "@tanstack/server-functions-plugin";
3
2
  import defu from "defu";
4
3
  import { existsSync } from "node:fs";
5
4
  import { join } from "node:path";
@@ -38,6 +37,36 @@ function solidStartServerFsRouter(config) {
38
37
  );
39
38
  }
40
39
 
40
+ const SolidStartServerFnsPlugin = createTanStackServerFnPlugin({
41
+ // This is the ID that will be available to look up and import
42
+ // our server function manifest and resolve its module
43
+ manifestVirtualImportId: "solidstart:server-fn-manifest",
44
+ client: {
45
+ getRuntimeCode: () =>
46
+ `import { createServerReference } from "${normalize(
47
+ fileURLToPath(new URL("../dist/runtime/server-runtime.js", import.meta.url))
48
+ )}"`,
49
+ replacer: opts =>
50
+ `createServerReference(${() => {}}, '${opts.functionId}', '${opts.extractedFilename}')`
51
+ },
52
+ ssr: {
53
+ getRuntimeCode: () =>
54
+ `import { createServerReference } from '${normalize(
55
+ fileURLToPath(new URL("../dist/runtime/server-fns-runtime.js", import.meta.url))
56
+ )}'`,
57
+ replacer: opts =>
58
+ `createServerReference(${opts.fn}, '${opts.functionId}', '${opts.extractedFilename}')`
59
+ },
60
+ server: {
61
+ getRuntimeCode: () =>
62
+ `import { createServerReference } from '${normalize(
63
+ fileURLToPath(new URL("../dist/runtime/server-fns-runtime.js", import.meta.url))
64
+ )}'`,
65
+ replacer: opts =>
66
+ `createServerReference(${opts.fn}, '${opts.functionId}', '${opts.extractedFilename}')`
67
+ }
68
+ });
69
+
41
70
  export function defineConfig(baseConfig = {}) {
42
71
  let { vite = {}, ...start } = baseConfig;
43
72
  const extensions = [...DEFAULT_EXTENSIONS, ...(start.extensions || [])];
@@ -112,11 +141,7 @@ export function defineConfig(baseConfig = {}) {
112
141
  }
113
142
  }),
114
143
  ...plugins,
115
- serverTransform({
116
- runtime: normalize(
117
- fileURLToPath(new URL("../dist/runtime/server-fns-runtime.js", import.meta.url))
118
- )
119
- }),
144
+ SolidStartServerFnsPlugin.ssr,
120
145
  start.experimental.islands ? serverComponents.server() : null,
121
146
  solid({ ...start.solid, ssr: true, extensions: extensions.map(ext => `.${ext}`) }),
122
147
  config("app-server", {
@@ -177,11 +202,7 @@ export function defineConfig(baseConfig = {}) {
177
202
  }
178
203
  }),
179
204
  ...plugins,
180
- serverFunctions.client({
181
- runtime: normalize(
182
- fileURLToPath(new URL("../dist/runtime/server-runtime.js", import.meta.url))
183
- )
184
- }),
205
+ SolidStartServerFnsPlugin.client,
185
206
  start.experimental.islands ? serverComponents.client() : null,
186
207
  solid({ ...start.solid, ssr: start.ssr, extensions: extensions.map(ext => `.${ext}`) }),
187
208
  config("app-client", {
@@ -247,11 +268,7 @@ export function defineConfig(baseConfig = {}) {
247
268
  cacheDir: "node_modules/.vinxi/server-fns"
248
269
  }),
249
270
  ...plugins,
250
- serverFunctionServer({
251
- runtime: normalize(
252
- fileURLToPath(new URL("../dist/runtime/server-fns-runtime.js", import.meta.url))
253
- )
254
- }),
271
+ SolidStartServerFnsPlugin.server,
255
272
  start.experimental.islands ? serverComponents.server() : null,
256
273
  solid({ ...start.solid, ssr: true, extensions: extensions.map(ext => `.${ext}`) }),
257
274
  config("app-server", {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
+ export { default as clientOnly } from "./shared/clientOnly";
1
2
  export { GET } from "./shared/GET";
2
3
  export { HttpHeader } from "./shared/HttpHeader";
3
4
  export { HttpStatusCode } from "./shared/HttpStatusCode";
4
- export { default as clientOnly } from "./shared/clientOnly";
5
+ export { getServerFunctionMeta } from "./shared/serverFunction";
package/dist/index.jsx CHANGED
@@ -1,5 +1,6 @@
1
1
  // @refresh skip
2
+ export { default as clientOnly } from "./shared/clientOnly";
2
3
  export { GET } from "./shared/GET";
3
4
  export { HttpHeader } from "./shared/HttpHeader";
4
5
  export { HttpStatusCode } from "./shared/HttpStatusCode";
5
- export { default as clientOnly } from "./shared/clientOnly";
6
+ export { getServerFunctionMeta } from "./shared/serverFunction";
@@ -5,11 +5,8 @@ function wrapRequestMiddleware(onRequest) {
5
5
  return async (h3Event) => {
6
6
  const fetchEvent = getFetchEvent(h3Event);
7
7
  const response = await onRequest(fetchEvent);
8
- if (!response) {
9
- return;
10
- }
11
- else {
12
- sendWebResponse(h3Event, response);
8
+ if (response) {
9
+ await sendWebResponse(h3Event, response);
13
10
  }
14
11
  };
15
12
  }
@@ -17,11 +14,8 @@ function wrapResponseMiddleware(onBeforeResponse) {
17
14
  return async (h3Event, response) => {
18
15
  const fetchEvent = getFetchEvent(h3Event);
19
16
  const mwResponse = await onBeforeResponse(fetchEvent, response);
20
- if (!mwResponse) {
21
- return;
22
- }
23
- else {
24
- sendWebResponse(h3Event, mwResponse);
17
+ if (mwResponse) {
18
+ await sendWebResponse(h3Event, mwResponse);
25
19
  }
26
20
  };
27
21
  }
@@ -7,7 +7,11 @@ function defineRoutes(fileRoutes) {
7
7
  return id.startsWith(o.id + "/");
8
8
  });
9
9
  if (!parentRoute) {
10
- routes.push({ ...route, id, path: id.replace(/\/\([^)/]+\)/g, "").replace(/\([^)/]+\)/g, "") });
10
+ routes.push({
11
+ ...route,
12
+ id,
13
+ path: id.replace(/\/\([^)/]+\)/g, "").replace(/\([^)/]+\)/g, "")
14
+ });
11
15
  return routes;
12
16
  }
13
17
  processRoute(parentRoute.children || (parentRoute.children = []), route, id.slice(parentRoute.id.length), full);
@@ -32,13 +36,24 @@ export function matchAPIRoute(path, method) {
32
36
  }
33
37
  }
34
38
  function containsHTTP(route) {
35
- return route["$HEAD"] || route["$GET"] || route["$POST"] || route["$PUT"] || route["$PATCH"] || route["$DELETE"];
39
+ return (route["$HEAD"] ||
40
+ route["$GET"] ||
41
+ route["$POST"] ||
42
+ route["$PUT"] ||
43
+ route["$PATCH"] ||
44
+ route["$DELETE"]);
36
45
  }
37
46
  const router = createRouter({
38
47
  routes: fileRoutes.reduce((memo, route) => {
39
48
  if (!containsHTTP(route))
40
49
  return memo;
41
- let path = route.path.replace(/\/\([^)/]+\)/g, "").replace(/\([^)/]+\)/g, "").replace(/\*([^/]*)/g, (_, m) => `**:${m}`).split('/').map(s => (s.startsWith(':') || s.startsWith('*')) ? s : encodeURIComponent(s)).join('/');
50
+ let path = route.path
51
+ .replace(/\/\([^)/]+\)/g, "")
52
+ .replace(/\([^)/]+\)/g, "")
53
+ .replace(/\*([^/]*)/g, (_, m) => `**:${m}`)
54
+ .split("/")
55
+ .map(s => (s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s)))
56
+ .join("/");
42
57
  if (/:[^/]*\?/g.test(path)) {
43
58
  throw new Error(`Optional parameters are not supported in API routes: ${path}`);
44
59
  }
@@ -5,11 +5,13 @@ import { CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, Hea
5
5
  import { sharedConfig } from "solid-js";
6
6
  import { renderToString } from "solid-js/web";
7
7
  import { provideRequestEvent } from "solid-js/web/storage";
8
- import { eventHandler, setHeader, setResponseStatus } from "vinxi/http";
8
+ import { eventHandler, parseCookies, setHeader, setResponseStatus } from "vinxi/http";
9
9
  import invariant from "vinxi/lib/invariant";
10
10
  import { cloneEvent, getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
11
11
  import { getExpectedRedirectStatus } from "../server/handler";
12
12
  import { createPageEvent } from "../server/pageEvent";
13
+ // @ts-ignore
14
+ import serverFnManifest from "solidstart:server-fn-manifest";
13
15
  function createChunk(data) {
14
16
  const encodeData = new TextEncoder().encode(data);
15
17
  const bytes = encodeData.length;
@@ -58,18 +60,40 @@ async function handleServerFunction(h3Event) {
58
60
  const instance = request.headers.get("X-Server-Instance");
59
61
  const singleFlight = request.headers.has("X-Single-Flight");
60
62
  const url = new URL(request.url);
61
- let filepath, name;
63
+ let functionId, name;
62
64
  if (serverReference) {
63
65
  invariant(typeof serverReference === "string", "Invalid server function");
64
- [filepath, name] = serverReference.split("#");
66
+ [functionId, name] = serverReference.split("#");
65
67
  }
66
68
  else {
67
- filepath = url.searchParams.get("id");
69
+ functionId = url.searchParams.get("id");
68
70
  name = url.searchParams.get("name");
69
- if (!filepath || !name)
70
- throw new Error("Invalid request");
71
+ if (!functionId || !name) {
72
+ return process.env.NODE_ENV === "development"
73
+ ? new Response("Server function not found", { status: 404 })
74
+ : new Response(null, { status: 404 });
75
+ }
76
+ }
77
+ const serverFnInfo = serverFnManifest[functionId];
78
+ let fnModule;
79
+ if (!serverFnInfo) {
80
+ return process.env.NODE_ENV === "development"
81
+ ? new Response("Server function not found", { status: 404 })
82
+ : new Response(null, { status: 404 });
83
+ }
84
+ if (process.env.NODE_ENV === "development") {
85
+ // In dev, we use Vinxi to get the "server" server-side router
86
+ // Then we use that router's devServer.ssrLoadModule to get the serverFn
87
+ // This code comes from:
88
+ // https://github.com/TanStack/router/blob/266f5cc863cd1a99809d1af2669e58b6b6db9a67/packages/start-server-functions-handler/src/index.tsx#L83-L87
89
+ fnModule = await globalThis.app
90
+ .getRouter("server-fns")
91
+ .internals.devServer.ssrLoadModule(serverFnInfo.extractedFilename);
92
+ }
93
+ else {
94
+ fnModule = await serverFnInfo.importer();
71
95
  }
72
- const serverFunction = (await import.meta.env.MANIFEST[import.meta.env.ROUTER_NAME].chunks[filepath].import())[name];
96
+ const serverFunction = fnModule[serverFnInfo.functionName];
73
97
  let parsed = [];
74
98
  // grab bound arguments from url when no JS
75
99
  if (!instance || h3Event.method === "GET") {
@@ -100,12 +124,14 @@ async function handleServerFunction(h3Event) {
100
124
  // This should never be the case in "proper" Nitro presets since node.req has to be IncomingMessage,
101
125
  // But the new azure-functions preset for some reason uses a ReadableStream in node.req (#1521)
102
126
  const isReadableStream = h3Request instanceof ReadableStream;
103
- const isH3EventBodyStreamLocked = isReadableStream && h3Request.locked;
127
+ const hasReadableStream = h3Request.body instanceof ReadableStream;
128
+ const isH3EventBodyStreamLocked = (isReadableStream && h3Request.locked) ||
129
+ (hasReadableStream && h3Request.body.locked);
104
130
  const requestBody = isReadableStream ? h3Request : h3Request.body;
105
131
  if (contentType?.startsWith("multipart/form-data") ||
106
132
  contentType?.startsWith("application/x-www-form-urlencoded")) {
107
133
  // workaround for https://github.com/unjs/nitro/issues/1721
108
- // (issue only in edge runtimes)
134
+ // (issue only in edge runtimes and netlify preset)
109
135
  parsed.push(await (isH3EventBodyStreamLocked
110
136
  ? request
111
137
  : new Request(request, { ...request, body: requestBody })).formData());
@@ -114,7 +140,7 @@ async function handleServerFunction(h3Event) {
114
140
  }
115
141
  else if (contentType?.startsWith("application/json")) {
116
142
  // workaround for https://github.com/unjs/nitro/issues/1721
117
- // (issue only in edge runtimes)
143
+ // (issue only in edge runtimes and netlify preset)
118
144
  const tmpReq = isH3EventBodyStreamLocked
119
145
  ? request
120
146
  : new Request(request, { ...request, body: requestBody });
@@ -141,7 +167,7 @@ async function handleServerFunction(h3Event) {
141
167
  /* @ts-ignore */
142
168
  sharedConfig.context = { event };
143
169
  event.locals.serverFunctionMeta = {
144
- id: filepath + "#" + name
170
+ id: functionId + "#" + name
145
171
  };
146
172
  return serverFunction(...parsed);
147
173
  });
@@ -233,6 +259,32 @@ function handleNoJS(result, request, parsed, thrown) {
233
259
  });
234
260
  }
235
261
  let App;
262
+ function createSingleFlightHeaders(sourceEvent) {
263
+ // cookie handling logic is pretty simplistic so this might be imperfect
264
+ // unclear if h3 internals are available on all platforms but we need a way to
265
+ // update request headers on the underlying H3 event.
266
+ const headers = new Headers(sourceEvent.request.headers);
267
+ const cookies = parseCookies(sourceEvent.nativeEvent);
268
+ const SetCookies = sourceEvent.response.headers.getSetCookie();
269
+ headers.delete("cookie");
270
+ let useH3Internals = false;
271
+ if (sourceEvent.nativeEvent.node?.req) {
272
+ useH3Internals = true;
273
+ sourceEvent.nativeEvent.node.req.headers.cookie = "";
274
+ }
275
+ SetCookies.forEach(cookie => {
276
+ if (!cookie)
277
+ return;
278
+ const keyValue = cookie.split(";")[0];
279
+ const [key, value] = keyValue.split("=");
280
+ key && value && (cookies[key] = value);
281
+ });
282
+ Object.entries(cookies).forEach(([key, value]) => {
283
+ headers.append("cookie", `${key}=${value}`);
284
+ useH3Internals && (sourceEvent.nativeEvent.node.req.headers.cookie += `${key}=${value};`);
285
+ });
286
+ return headers;
287
+ }
236
288
  async function handleSingleFlight(sourceEvent, result) {
237
289
  let revalidate;
238
290
  let url = new URL(sourceEvent.request.headers.get("referer")).toString();
@@ -243,7 +295,7 @@ async function handleSingleFlight(sourceEvent, result) {
243
295
  url = new URL(result.headers.get("Location"), new URL(sourceEvent.request.url).origin + import.meta.env.SERVER_BASE_URL).toString();
244
296
  }
245
297
  const event = cloneEvent(sourceEvent);
246
- event.request = new Request(url, { headers: sourceEvent.request.headers });
298
+ event.request = new Request(url, { headers: createSingleFlightHeaders(sourceEvent) });
247
299
  return await provideRequestEvent(event, async () => {
248
300
  await createPageEvent(event);
249
301
  /* @ts-ignore */
@@ -1,4 +1,6 @@
1
- export { StartServer } from "./StartServer";
2
1
  export { createHandler } from "./handler";
3
- export { getServerFunctionMeta } from "./serverFunction";
4
- export type { DocumentComponentProps, Asset, HandlerOptions, ContextMatches, ResponseStub, FetchEvent, RequestEventLocals, PageEvent, APIEvent, APIHandler, ServerFunctionMeta } from "./types";
2
+ export { StartServer } from "./StartServer";
3
+ export type { APIEvent, APIHandler, Asset, ContextMatches, DocumentComponentProps, FetchEvent, HandlerOptions, PageEvent, ResponseStub, ServerFunctionMeta } from "./types";
4
+ import { getServerFunctionMeta as getServerFunctionMeta_ } from "../shared/serverFunction";
5
+ /** @deprecated */
6
+ export declare const getServerFunctionMeta: typeof getServerFunctionMeta_;
@@ -1,4 +1,6 @@
1
1
  // @refresh skip
2
- export { StartServer } from "./StartServer";
3
2
  export { createHandler } from "./handler";
4
- export { getServerFunctionMeta } from "./serverFunction";
3
+ export { StartServer } from "./StartServer";
4
+ import { getServerFunctionMeta as getServerFunctionMeta_ } from "../shared/serverFunction";
5
+ /** @deprecated */
6
+ export const getServerFunctionMeta = getServerFunctionMeta_;
@@ -49,12 +49,9 @@ export interface FetchEvent {
49
49
  request: Request;
50
50
  response: ResponseStub;
51
51
  clientAddress?: string;
52
- locals: RequestEventLocals;
52
+ locals: App.RequestEventLocals;
53
53
  nativeEvent: HTTPEvent;
54
54
  }
55
- export interface RequestEventLocals {
56
- [key: string | symbol]: any;
57
- }
58
55
  export interface PageEvent extends RequestEvent {
59
56
  manifest: any;
60
57
  assets: any;
@@ -1,16 +1,18 @@
1
- import { getHighlighterCore, loadWasm } from 'shikiji/core';
1
+ // @refresh skip
2
+ import { getSingletonHighlighter } from 'shiki';
3
+ import { loadWasm } from 'shiki/engine/oniguruma';
2
4
  import { createEffect, createResource } from 'solid-js';
3
- import url from 'shikiji/onig.wasm?url';
4
- import langJS from 'shikiji/langs/javascript.mjs';
5
- import langJSX from 'shikiji/langs/jsx.mjs';
6
- import langTSX from 'shikiji/langs/tsx.mjs';
7
- import langTS from 'shikiji/langs/typescript.mjs';
8
- import darkPlus from 'shikiji/themes/dark-plus.mjs';
5
+ import url from 'shiki/onig.wasm?url';
6
+ import langJS from 'shiki/langs/javascript.mjs';
7
+ import langJSX from 'shiki/langs/jsx.mjs';
8
+ import langTSX from 'shiki/langs/tsx.mjs';
9
+ import langTS from 'shiki/langs/typescript.mjs';
10
+ import darkPlus from 'shiki/themes/dark-plus.mjs';
9
11
  let HIGHLIGHTER;
10
12
  async function loadHighlighter() {
11
13
  if (!HIGHLIGHTER) {
12
14
  await loadWasm(await fetch(url));
13
- HIGHLIGHTER = await getHighlighterCore({
15
+ HIGHLIGHTER = await getSingletonHighlighter({
14
16
  themes: [
15
17
  darkPlus,
16
18
  ],
@@ -1,4 +1,4 @@
1
- import type { ServerFunctionMeta } from "./types";
1
+ import type { ServerFunctionMeta } from "../server/types";
2
2
  /**
3
3
  *
4
4
  * Read more: https://docs.solidjs.com/solid-start/reference/server/get-server-function-meta
package/env.d.ts CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  /* eslint-disable @typescript-eslint/consistent-type-imports */
6
6
 
7
+ declare namespace App {
8
+ export interface RequestEventLocals {
9
+ [key: string | symbol]: any;
10
+ }
11
+ }
12
+
7
13
  interface ImportMetaEnv extends Record<`VITE_${string}`, any>, SolidStartMetaEnv {
8
14
  BASE_URL: string;
9
15
  MODE: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/start",
3
- "version": "1.0.10",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
@@ -55,25 +55,27 @@
55
55
  }
56
56
  },
57
57
  "devDependencies": {
58
- "solid-js": "^1.9.2",
59
- "vinxi": "^0.4.3",
60
- "typescript": "^5.4.2"
58
+ "solid-js": "^1.9.4",
59
+ "vinxi": "^0.5.3"
61
60
  },
62
61
  "dependencies": {
63
- "@vinxi/plugin-directives": "^0.4.3",
64
- "@vinxi/server-components": "^0.4.3",
65
- "@vinxi/server-functions": "^0.4.3",
62
+ "@vinxi/plugin-directives": "^0.5.0",
63
+ "@vinxi/server-components": "^0.5.0",
64
+ "@tanstack/server-functions-plugin": "^1.99.5",
66
65
  "defu": "^6.1.2",
67
66
  "error-stack-parser": "^2.1.4",
68
67
  "html-to-image": "^1.11.11",
69
68
  "radix3": "^1.1.0",
70
69
  "seroval": "^1.0.2",
71
70
  "seroval-plugins": "^1.0.2",
72
- "shikiji": "^0.9.12",
71
+ "shiki": "^1.26.1",
73
72
  "source-map-js": "^1.0.2",
74
73
  "terracotta": "^1.0.4",
75
74
  "tinyglobby": "^0.2.2",
76
- "vite-plugin-solid": "^2.10.2"
75
+ "vite-plugin-solid": "^2.11.1"
76
+ },
77
+ "peerDependencies": {
78
+ "vinxi": "^0.5.3"
77
79
  },
78
80
  "scripts": {
79
81
  "typecheck": "tsc --noEmit",
File without changes