@solidjs/start 0.4.2 → 0.4.4

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/config/index.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  import { AppOptions } from "vinxi";
2
2
  import { InlineConfig } from "vite";
3
+ import type { Options } from "vite-plugin-solid";
4
+
3
5
 
4
6
  type SolidStartInlineConfig = Omit<InlineConfig, "router"> & {
5
7
  start?: {
8
+ /**
9
+ * true: streaming mode
10
+ * false: csr only
11
+ * async: ssr is in async mode
12
+ */
6
13
  ssr?: boolean | "async",
14
+ solid: Options,
7
15
  extensions?: string[],
8
16
  server?: AppOptions['server'],
9
17
  appRoot?: string,
package/config/index.js CHANGED
@@ -38,29 +38,27 @@ function solidStartServerFsRouter(config) {
38
38
  }
39
39
 
40
40
  export function defineConfig(baseConfig = {}) {
41
- let { plugins = [], start = {}, base, ...userConfig } = baseConfig;
41
+ let { plugins = [], start = {}, ...userConfig } = baseConfig;
42
42
  const extensions = [...DEFAULT_EXTENSIONS, ...(start.extensions || [])];
43
43
  start = defu(start, {
44
44
  appRoot: "./src",
45
45
  ssr: true,
46
46
  islands: false,
47
- server: { base },
48
47
  solid: {}
49
48
  });
50
49
  let server = start.server;
51
50
  if (!start.ssr) {
52
51
  server = { ...server, prerender: { routes: ["/"] } };
53
52
  }
54
- console.log("start", start)
55
- let entryExtension = ".tsx"
53
+ let entryExtension = ".tsx";
56
54
  if (existsSync(join(process.cwd(), start.appRoot, "app.jsx"))) {
57
- entryExtension = ".jsx"
55
+ entryExtension = ".jsx";
58
56
  }
59
57
 
60
58
  return createApp({
61
59
  server: {
62
60
  compressPublicAssets: {
63
- brotli: true
61
+ brotli: process.versions.bun ? false : true
64
62
  },
65
63
  ...server
66
64
  },
@@ -80,9 +78,10 @@ export function defineConfig(baseConfig = {}) {
80
78
  routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
81
79
  extensions,
82
80
  target: "server",
83
- plugins: () => [
81
+ plugins: async () => [
84
82
  config("user", userConfig),
85
- ...plugins,
83
+ ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
84
+
86
85
  serverTransform({
87
86
  runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url)))
88
87
  }),
@@ -121,9 +120,9 @@ export function defineConfig(baseConfig = {}) {
121
120
  }),
122
121
  extensions,
123
122
  target: "browser",
124
- plugins: () => [
123
+ plugins: async () => [
125
124
  config("user", userConfig),
126
- ...plugins,
125
+ ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
127
126
  serverFunctions.client({
128
127
  runtime: normalize(fileURLToPath(new URL("./server-runtime.jsx", import.meta.url)))
129
128
  }),
@@ -161,9 +160,10 @@ export function defineConfig(baseConfig = {}) {
161
160
  handler: normalize(fileURLToPath(new URL("./server-handler.js", import.meta.url))),
162
161
  runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url))),
163
162
  // routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
164
- plugins: () => [
163
+ plugins: async () => [
165
164
  config("user", userConfig),
166
- ...plugins,
165
+ ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
166
+
167
167
  solid({ ...start.solid, ssr: true, extensions: extensions.map(ext => `.${ext}`) }),
168
168
  config("app-server", {
169
169
  resolve: {
@@ -1,6 +1,6 @@
1
1
  import { getRequestEvent } from "solid-js/web";
2
2
  import { provideRequestEvent } from "solid-js/web/storage";
3
- import { cloneEvent } from "../server";
3
+ import { cloneEvent } from "../server/middleware";
4
4
 
5
5
  export function createServerReference(fn, id, name) {
6
6
  return new Proxy(fn, {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="vinxi/types/server" />
2
- import { crossSerializeStream, fromJSON } from "seroval";
2
+ import { crossSerializeStream, fromJSON, getCrossReferenceHeader } from "seroval";
3
3
  import {
4
4
  CustomEventPlugin,
5
5
  DOMExceptionPlugin,
@@ -10,16 +10,14 @@ import {
10
10
  RequestPlugin,
11
11
  ResponsePlugin,
12
12
  URLPlugin,
13
- URLSearchParamsPlugin,
14
- } from 'seroval-plugins/web';
13
+ URLSearchParamsPlugin
14
+ } from "seroval-plugins/web";
15
+ import { sharedConfig } from "solid-js";
16
+ /* @ts-ignore */
15
17
  import { provideRequestEvent } from "solid-js/web/storage";
16
18
  import invariant from "vinxi/lib/invariant";
17
19
  import {
18
20
  eventHandler,
19
- getHeader,
20
- getRequestURL,
21
- readBody,
22
- readFormData,
23
21
  setHeader
24
22
  } from "vinxi/server";
25
23
  import { getFetchEvent } from "../server/middleware";
@@ -39,10 +37,10 @@ function serializeToStream(id, value) {
39
37
  RequestPlugin,
40
38
  ResponsePlugin,
41
39
  URLSearchParamsPlugin,
42
- URLPlugin,
40
+ URLPlugin
43
41
  ],
44
42
  onSerialize(data, initial) {
45
- const result = initial ? `($R["${id}"]=[],${data})` : data;
43
+ const result = initial ? `(${getCrossReferenceHeader(id)},${data})` : data;
46
44
  controller.enqueue(new TextEncoder().encode(`${result};\n`));
47
45
  },
48
46
  onDone() {
@@ -58,12 +56,14 @@ function serializeToStream(id, value) {
58
56
  });
59
57
  }
60
58
 
61
- async function handleServerFunction(event) {
62
- invariant(event.method === "POST", `Invalid method ${event.method}. Expected POST.`);
59
+ async function handleServerFunction(h3Event) {
60
+ invariant(h3Event.method === "POST", `Invalid method ${h3Event.method}. Expected POST.`);
61
+ const event = getFetchEvent(h3Event);
62
+ const request = event.request;
63
63
 
64
- const serverReference = getHeader(event, "x-server-id");
65
- const instance = getHeader(event, "x-server-instance");
66
- const url = getRequestURL(event);
64
+ const serverReference = request.headers.get("x-server-id");
65
+ const instance = request.headers.get("x-server-instance");
66
+ const url = new URL(request.url);
67
67
  let filepath, name;
68
68
  if (serverReference) {
69
69
  invariant(typeof serverReference === "string", "Invalid server function");
@@ -84,14 +84,14 @@ async function handleServerFunction(event) {
84
84
  const args = url.searchParams.get("args");
85
85
  if (args) JSON.parse(args).forEach(arg => parsed.push(arg));
86
86
  }
87
- const contentType = getHeader(event, "content-type");
87
+ const contentType = request.headers.get("content-type");
88
88
  if (
89
89
  contentType.startsWith("multipart/form-data") ||
90
90
  contentType.startsWith("application/x-www-form-urlencoded")
91
91
  ) {
92
- parsed.push(await readFormData(event));
92
+ parsed.push(await request.formData());
93
93
  } else {
94
- parsed = fromJSON(await readBody(event), {
94
+ parsed = fromJSON(await request.json(), {
95
95
  plugins: [
96
96
  CustomEventPlugin,
97
97
  DOMExceptionPlugin,
@@ -102,32 +102,40 @@ async function handleServerFunction(event) {
102
102
  RequestPlugin,
103
103
  ResponsePlugin,
104
104
  URLSearchParamsPlugin,
105
- URLPlugin,
106
- ],
105
+ URLPlugin
106
+ ]
107
107
  });
108
108
  }
109
109
  try {
110
- const result = await provideRequestEvent(getFetchEvent(event), () => action(...parsed));
110
+ const result = await provideRequestEvent(event, () => {
111
+ /* @ts-ignore */
112
+ sharedConfig.context = { event };
113
+ return action(...parsed);
114
+ });
111
115
 
112
116
  // handle no JS success case
113
117
  if (!instance) {
114
118
  const isError = result instanceof Error;
115
- const refererUrl = new URL(getHeader(event, "referer"));
119
+ const refererUrl = new URL(request.headers.get("referer"));
116
120
  return new Response(null, {
117
121
  status: 302,
118
122
  headers: {
119
123
  Location: refererUrl.toString(),
120
- ...(result ? { "Set-Cookie": `flash=${JSON.stringify({
121
- url: url.pathname + encodeURIComponent(url.search),
122
- result: isError ? result.message : result,
123
- error: isError,
124
- input: [...parsed.slice(0, -1), [...parsed[parsed.length-1].entries()]]
125
- })}; Secure; HttpOnly;`} : {})
124
+ ...(result
125
+ ? {
126
+ "Set-Cookie": `flash=${JSON.stringify({
127
+ url: url.pathname + encodeURIComponent(url.search),
128
+ result: isError ? result.message : result,
129
+ error: isError,
130
+ input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]]
131
+ })}; Secure; HttpOnly;`
132
+ }
133
+ : {})
126
134
  }
127
135
  });
128
136
  }
129
-
130
- setHeader(event, "content-type", "text/javascript");
137
+ if (typeof result === "string") return new Response(result);
138
+ setHeader(h3Event, "content-type", "text/javascript");
131
139
  return serializeToStream(instance, result);
132
140
  } catch (x) {
133
141
  if (x instanceof Response && x.status === 302) {
@@ -138,12 +146,7 @@ async function handleServerFunction(event) {
138
146
  }
139
147
  });
140
148
  }
141
- return new Response(serializeToStream(instance, x), {
142
- status: 500,
143
- headers: {
144
- "Content-Type": "text/javascript"
145
- }
146
- });
149
+ return x;
147
150
  }
148
151
  }
149
152
 
@@ -104,7 +104,15 @@ async function fetchServerFunction(base, id, args) {
104
104
  }
105
105
  throw response;
106
106
  }
107
- const result = deserializeStream(instance, response);
107
+ const contentType = response.headers.get("Content-Type");
108
+ let result;
109
+ if (contentType && contentType.startsWith("text/plain")) {
110
+ result = await response.text();
111
+ } else if(contentType && contentType.startsWith("application/json")) {
112
+ result = await response.json();
113
+ } else {
114
+ result = deserializeStream(instance, response);
115
+ }
108
116
  if (response.ok) {
109
117
  return result;
110
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/start",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "author": "Ryan Carniato",
6
6
  "publishConfig": {
@@ -38,16 +38,21 @@
38
38
  }
39
39
  },
40
40
  "devDependencies": {
41
- "solid-js": "^1.8.7",
42
- "vinxi": "0.0.54"
41
+ "solid-js": "^1.8.8",
42
+ "vinxi": "0.1.1"
43
43
  },
44
44
  "dependencies": {
45
- "@vinxi/plugin-directives": "0.0.50",
46
- "@vinxi/server-components": "0.0.50",
47
- "@vinxi/server-functions": "0.0.51",
45
+ "@vinxi/plugin-directives": "^0.1.0",
46
+ "@vinxi/server-components": "^0.1.0",
47
+ "@vinxi/server-functions": "^0.1.0",
48
48
  "defu": "^6.1.2",
49
- "seroval": "^1.0.0",
50
- "seroval-plugins": "^1.0.1",
49
+ "error-stack-parser": "^2.1.4",
50
+ "html-to-image": "^1.11.11",
51
+ "seroval": "^1.0.2",
52
+ "seroval-plugins": "^1.0.2",
53
+ "shikiji": "^0.9.12",
54
+ "source-map-js": "^1.0.2",
55
+ "terracotta": "^1.0.4",
51
56
  "vite-plugin-inspect": "^0.7.33",
52
57
  "vite-plugin-solid": "^2.8.0"
53
58
  },
@@ -1,3 +1,4 @@
1
+ import { sharedConfig } from "solid-js";
1
2
  import { renderToStream } from "solid-js/web";
2
3
  /* @ts-ignore */
3
4
  import { provideRequestEvent } from "solid-js/web/storage";
@@ -42,6 +43,7 @@ export function createHandler(
42
43
  const mod = await match.handler.import();
43
44
  const fn = mod[event.request.method];
44
45
  (event as APIEvent).params = match.params;
46
+ sharedConfig.context = { event } as any;
45
47
  return await fn(event);
46
48
  }
47
49
 
@@ -63,7 +65,10 @@ export function createHandler(
63
65
  og(options);
64
66
  };
65
67
  } else cloned.onCompleteShell = handleShellCompleteRedirect(context, e);
66
- const stream = renderToStream(() => fn(context), cloned);
68
+ const stream = renderToStream(() => {
69
+ (sharedConfig.context as any).event = context;
70
+ return fn(context)
71
+ }, cloned);
67
72
  if (context.response && context.response.headers.get("Location")) {
68
73
  return sendRedirect(event, context.response.headers.get("Location"));
69
74
  }
@@ -12,7 +12,6 @@ export function StartServer(props) {
12
12
  <props.document
13
13
  assets={
14
14
  <>
15
- <script>$R = [];</script>
16
15
  {context.assets.map(m => renderAsset(m))}
17
16
  </>
18
17
  }
package/server/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { JSX } from "solid-js";
2
- import { EventHandlerRequest, H3Event } from "vinxi/server";
1
+ import type { JSX } from "solid-js";
2
+ import type { EventHandlerRequest, H3Event } from "vinxi/server";
3
3
 
4
4
  // export const FETCH_EVENT = "$FETCH";
5
5
 
@@ -1,48 +1,25 @@
1
1
  import {
2
- createEffect,
3
- ErrorBoundary as ErrorBoundaryBase,
2
+ ErrorBoundary as DefaultErrorBoundary,
4
3
  ParentProps,
5
- resetErrorBoundaries
6
4
  } from "solid-js";
5
+ import { HttpStatusCode } from "./HttpStatusCode";
6
+ import { DevOverlay } from "./dev-overlay";
7
7
 
8
8
  export function ErrorBoundary(props: ParentProps) {
9
+ if (import.meta.env.DEV) {
10
+ return (
11
+ <DevOverlay>
12
+ {props.children}
13
+ </DevOverlay>
14
+ );
15
+ }
9
16
  return (
10
- <ErrorBoundaryBase fallback={e => <ErrorMessage error={e} />}>
17
+ <DefaultErrorBoundary
18
+ fallback={(
19
+ <HttpStatusCode code={500} />
20
+ )}
21
+ >
11
22
  {props.children}
12
- </ErrorBoundaryBase>
13
- );
14
- }
15
-
16
- function ErrorMessage(props: { error: any }) {
17
- createEffect(() => console.error(props.error));
18
-
19
- return (
20
- <div style="padding: 16px">
21
- <div
22
- style={`
23
- background-color: rgba(252, 165, 165);
24
- color: rgb(153, 27, 27);
25
- border-radius": 5px;
26
- overflow: scroll;
27
- padding: 16px;
28
- margin-bottom: 8px;
29
- `}
30
- >
31
- <p style="font-weight: bold" id="error-message">
32
- {props.error.message}
33
- </p>
34
- <button
35
- id="reset-errors"
36
- onClick={resetErrorBoundaries}
37
- style={`color: rgba(252, 165, 165);
38
- background-color: rgb(153, 27, 27);
39
- border-radius: 5px;
40
- padding: 4px 8px`}
41
- >
42
- Clear errors and retry
43
- </button>
44
- <pre style={"margin-top: 8px;width:100%"}>{props.error.stack}</pre>
45
- </div>
46
- </div>
23
+ </DefaultErrorBoundary>
47
24
  );
48
25
  }
@@ -2,7 +2,7 @@
2
2
  import { getRequestEvent, isServer } from "solid-js/web";
3
3
  import lazyRoute from "./lazyRoute";
4
4
 
5
- import { PageEvent } from "../server";
5
+ import type { PageEvent } from "../server/types";
6
6
  import { pageRoutes as routeConfigs } from "./routes";
7
7
 
8
8
  export function createRoutes() {
@@ -10,7 +10,10 @@ export function createRoutes() {
10
10
  return {
11
11
  ...route,
12
12
  ...(route.$$route ? route.$$route.require().route : undefined),
13
- metadata: { ...(route.$$route ? route.$$route.require().route.metadata : {}), filesystem: true },
13
+ metadata: {
14
+ ...(route.$$route ? route.$$route.require().route.metadata : {}),
15
+ filesystem: true
16
+ },
14
17
  component: lazyRoute(
15
18
  route.$component,
16
19
  import.meta.env.START_ISLANDS
@@ -0,0 +1,89 @@
1
+ import type { BuiltinLanguage, Highlighter } from 'shikiji';
2
+ import { getHighlighterCore } from 'shikiji/core';
3
+ import { getWasmInlined } from 'shikiji/wasm';
4
+ import { createEffect, createResource, type JSX } from 'solid-js';
5
+
6
+ import langJS from 'shikiji/langs/javascript.mjs';
7
+ import langJSX from 'shikiji/langs/jsx.mjs';
8
+ import langTSX from 'shikiji/langs/tsx.mjs';
9
+ import langTS from 'shikiji/langs/typescript.mjs';
10
+ import darkPlus from 'shikiji/themes/dark-plus.mjs';
11
+
12
+ let HIGHLIGHTER: Highlighter;
13
+
14
+ async function loadHighlighter() {
15
+ if (!HIGHLIGHTER) {
16
+ HIGHLIGHTER = await getHighlighterCore({
17
+ themes: [
18
+ darkPlus,
19
+ ],
20
+ langs: [
21
+ langJS,
22
+ langJSX,
23
+ langTS,
24
+ langTSX,
25
+ ],
26
+ loadWasm: getWasmInlined,
27
+ });
28
+ }
29
+ return HIGHLIGHTER;
30
+ }
31
+
32
+ export interface CodeViewProps {
33
+ fileName: string;
34
+ content: string;
35
+ line: number;
36
+ }
37
+
38
+ const RANGE = 8;
39
+
40
+ export function CodeView(props: CodeViewProps): JSX.Element | null {
41
+ const lines = () =>
42
+ props.content.split('\n').map((item, index) => ({
43
+ index: index + 1,
44
+ line: item,
45
+ }));
46
+
47
+ const minLine = () => Math.max(props.line - (1 + RANGE), 0);
48
+ const maxLine = () => Math.min(props.line + RANGE, lines().length - 1);
49
+
50
+ let ref: HTMLDivElement | undefined;
51
+
52
+ const [data] = createResource(() => (
53
+ lines()
54
+ .slice(minLine(), maxLine())
55
+ .map(item => item.line)
56
+ .join('\n')
57
+ ) ,async (value) => {
58
+ const highlighter = await loadHighlighter();
59
+ const lang = props.fileName
60
+ .split(/[#?]/)[0]
61
+ .split('.')
62
+ .pop()
63
+ ?.trim() as BuiltinLanguage;
64
+ return highlighter.codeToHtml(value, {
65
+ theme: 'dark-plus',
66
+ lang,
67
+ });
68
+ });
69
+
70
+ createEffect(() => {
71
+ const result = data();
72
+ if (ref && result) {
73
+ ref.innerHTML = result;
74
+
75
+ const lines = ref.querySelectorAll('span[class="line"]');
76
+
77
+ for (let i = 0, len = lines.length; i < len; i++) {
78
+ const el = lines[i];
79
+ if ((props.line - minLine() - 1) === i) {
80
+ el.innerHTML = `<mark style="background-color:#aaaaaa80">${el.innerHTML}</mark>`;
81
+ }
82
+ }
83
+ }
84
+ });
85
+
86
+ return <div ref={ref} class="dev-overlay-code-view" style={{
87
+ '--dev-overlay-code-view-start': minLine() + 1,
88
+ }} />;
89
+ }