@solidjs/start 0.4.1 → 0.4.3

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
@@ -3,12 +3,17 @@ import { InlineConfig } from "vite";
3
3
 
4
4
  type SolidStartInlineConfig = Omit<InlineConfig, "router"> & {
5
5
  start?: {
6
+ /**
7
+ * true: streaming mode
8
+ * false: csr only
9
+ * async: ssr is in async mode
10
+ */
6
11
  ssr?: boolean | "async",
7
12
  extensions?: string[],
8
13
  server?: AppOptions['server'],
9
14
  appRoot?: string,
10
15
  middleware?: string,
11
- //islands?: boolean TODO: Get PROD bugs working probably missing release
16
+ islands?: boolean
12
17
  }
13
18
  }
14
19
 
package/config/index.js CHANGED
@@ -50,15 +50,15 @@ export function defineConfig(baseConfig = {}) {
50
50
  if (!start.ssr) {
51
51
  server = { ...server, prerender: { routes: ["/"] } };
52
52
  }
53
- let entryExtension = ".tsx"
53
+ let entryExtension = ".tsx";
54
54
  if (existsSync(join(process.cwd(), start.appRoot, "app.jsx"))) {
55
- entryExtension = ".jsx"
55
+ entryExtension = ".jsx";
56
56
  }
57
57
 
58
58
  return createApp({
59
59
  server: {
60
60
  compressPublicAssets: {
61
- brotli: true
61
+ brotli: process.versions.bun ? false : true
62
62
  },
63
63
  ...server
64
64
  },
@@ -78,9 +78,10 @@ export function defineConfig(baseConfig = {}) {
78
78
  routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
79
79
  extensions,
80
80
  target: "server",
81
- plugins: () => [
81
+ plugins: async () => [
82
82
  config("user", userConfig),
83
- ...plugins,
83
+ ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
84
+
84
85
  serverTransform({
85
86
  runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url)))
86
87
  }),
@@ -119,9 +120,9 @@ export function defineConfig(baseConfig = {}) {
119
120
  }),
120
121
  extensions,
121
122
  target: "browser",
122
- plugins: () => [
123
+ plugins: async () => [
123
124
  config("user", userConfig),
124
- ...plugins,
125
+ ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
125
126
  serverFunctions.client({
126
127
  runtime: normalize(fileURLToPath(new URL("./server-runtime.jsx", import.meta.url)))
127
128
  }),
@@ -159,9 +160,10 @@ export function defineConfig(baseConfig = {}) {
159
160
  handler: normalize(fileURLToPath(new URL("./server-handler.js", import.meta.url))),
160
161
  runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url))),
161
162
  // routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
162
- plugins: () => [
163
+ plugins: async () => [
163
164
  config("user", userConfig),
164
- ...plugins,
165
+ ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
166
+
165
167
  solid({ ...start.solid, ssr: true, extensions: extensions.map(ext => `.${ext}`) }),
166
168
  config("app-server", {
167
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,8 +10,10 @@ 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 {
@@ -19,7 +21,7 @@ import {
19
21
  getHeader,
20
22
  getRequestURL,
21
23
  readBody,
22
- readFormData,
24
+ readRawBody,
23
25
  setHeader
24
26
  } from "vinxi/server";
25
27
  import { getFetchEvent } from "../server/middleware";
@@ -39,10 +41,10 @@ function serializeToStream(id, value) {
39
41
  RequestPlugin,
40
42
  ResponsePlugin,
41
43
  URLSearchParamsPlugin,
42
- URLPlugin,
44
+ URLPlugin
43
45
  ],
44
46
  onSerialize(data, initial) {
45
- const result = initial ? `($R["${id}"]=[],${data})` : data;
47
+ const result = initial ? `(${getCrossReferenceHeader(id)},${data})` : data;
46
48
  controller.enqueue(new TextEncoder().encode(`${result};\n`));
47
49
  },
48
50
  onDone() {
@@ -89,7 +91,15 @@ async function handleServerFunction(event) {
89
91
  contentType.startsWith("multipart/form-data") ||
90
92
  contentType.startsWith("application/x-www-form-urlencoded")
91
93
  ) {
92
- parsed.push(await readFormData(event));
94
+ // Temporary workaround until https://github.com/unjs/nitro/issues/1721 is resolved
95
+ // parsed.push(await readFormData(event));
96
+
97
+ const request = new Request(getRequestURL(event), {
98
+ method: event.method,
99
+ headers: event.headers,
100
+ body: await readRawBody(event)
101
+ });
102
+ parsed.push(await request.formData());
93
103
  } else {
94
104
  parsed = fromJSON(await readBody(event), {
95
105
  plugins: [
@@ -102,12 +112,17 @@ async function handleServerFunction(event) {
102
112
  RequestPlugin,
103
113
  ResponsePlugin,
104
114
  URLSearchParamsPlugin,
105
- URLPlugin,
106
- ],
115
+ URLPlugin
116
+ ]
107
117
  });
108
118
  }
109
119
  try {
110
- const result = await provideRequestEvent(getFetchEvent(event), () => action(...parsed));
120
+ const evt = getFetchEvent(event);
121
+ const result = await provideRequestEvent(evt, () => {
122
+ /* @ts-ignore */
123
+ sharedConfig.context = { event: evt };
124
+ return action(...parsed);
125
+ });
111
126
 
112
127
  // handle no JS success case
113
128
  if (!instance) {
@@ -117,16 +132,20 @@ async function handleServerFunction(event) {
117
132
  status: 302,
118
133
  headers: {
119
134
  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;`} : {})
135
+ ...(result
136
+ ? {
137
+ "Set-Cookie": `flash=${JSON.stringify({
138
+ url: url.pathname + encodeURIComponent(url.search),
139
+ result: isError ? result.message : result,
140
+ error: isError,
141
+ input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]]
142
+ })}; Secure; HttpOnly;`
143
+ }
144
+ : {})
126
145
  }
127
146
  });
128
147
  }
129
-
148
+ if (typeof result === "string") return new Response(result);
130
149
  setHeader(event, "content-type", "text/javascript");
131
150
  return serializeToStream(instance, result);
132
151
  } catch (x) {
@@ -138,12 +157,7 @@ async function handleServerFunction(event) {
138
157
  }
139
158
  });
140
159
  }
141
- return new Response(serializeToStream(instance, x), {
142
- status: 500,
143
- headers: {
144
- "Content-Type": "text/javascript"
145
- }
146
- });
160
+ return x;
147
161
  }
148
162
  }
149
163
 
@@ -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.1",
3
+ "version": "0.4.3",
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.52"
41
+ "solid-js": "^1.8.8",
42
+ "vinxi": "0.1.1"
43
43
  },
44
44
  "dependencies": {
45
- "@vinxi/plugin-directives": "0.0.44",
46
- "@vinxi/server-components": "0.0.44",
47
- "@vinxi/server-functions": "0.0.45",
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
  },
@@ -26,7 +26,8 @@ export function StartServer(props: { document: Component<DocumentComponentProps>
26
26
  const match = context.routerMatches[0][i];
27
27
  if (match.metadata && match.metadata.filesystem) {
28
28
  const segment = current.find(r => r.path === match.path);
29
- const part = import.meta.env.MANIFEST["client"].inputs[segment["$component"].src];
29
+ const part = import.meta.env.MANIFEST[import.meta.env.START_ISLANDS ? "ssr" : "client"]
30
+ .inputs[segment["$component"].src];
30
31
  const asset = await part.assets();
31
32
  assets.push.apply(assets, asset);
32
33
  current = segment.children;
@@ -34,26 +35,32 @@ export function StartServer(props: { document: Component<DocumentComponentProps>
34
35
  }
35
36
  }
36
37
  // dedupe assets
37
- assets = [...new Map(assets.map(item => [item.attrs.key, item])).values()].filter(
38
- asset =>
39
- asset.attrs.rel === "modulepreload" &&
40
- !context.assets.find(a => a.attrs.key === asset.attrs.key)
38
+ assets = [...new Map(assets.map(item => [item.attrs.key, item])).values()].filter(asset =>
39
+ import.meta.env.START_ISLANDS
40
+ ? false
41
+ : asset.attrs.rel === "modulepreload" &&
42
+ !context.assets.find(a => a.attrs.key === asset.attrs.key)
41
43
  );
42
44
  });
43
45
 
44
- useAssets(() => assets.length ? assets.map(m => renderAsset(m)) : undefined);
46
+ useAssets(() => (assets.length ? assets.map(m => renderAsset(m)) : undefined));
45
47
 
46
48
  return (
47
49
  <NoHydration>
48
50
  {docType as unknown as any}
49
51
  <props.document
50
- assets={<>{context.assets.map(m => renderAsset(m))}</>}
51
- scripts={
52
+ assets={
52
53
  <>
53
54
  <HydrationScript />
55
+ {context.assets.map(m => renderAsset(m))}
56
+ </>
57
+ }
58
+ scripts={
59
+ <>
54
60
  <script innerHTML={`window.manifest = ${JSON.stringify(context.manifest)}`} />
55
61
  <script
56
62
  type="module"
63
+ async
57
64
  src={
58
65
  import.meta.env.MANIFEST["client"].inputs[
59
66
  import.meta.env.MANIFEST["client"].handler
@@ -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
  }
@@ -35,7 +35,9 @@ export async function createPageEvent(ctx: FetchEvent) {
35
35
  assets: [
36
36
  ...(await clientManifest.inputs[clientManifest.handler].assets()),
37
37
  ...(import.meta.env.START_ISLANDS
38
- ? await serverManifest.inputs[serverManifest.handler].assets()
38
+ ? (await serverManifest.inputs[serverManifest.handler].assets()).filter(
39
+ s => (s as any).attrs.rel !== "modulepreload"
40
+ )
39
41
  : [])
40
42
  ],
41
43
  initialSubmission: initFromFlash(ctx),
@@ -10,11 +10,14 @@ export function StartServer(props) {
10
10
  <NoHydration>
11
11
  {docType as unknown as any}
12
12
  <props.document
13
- assets={<>{context.assets.map(m => renderAsset(m))}</>}
13
+ assets={
14
+ <>
15
+ {context.assets.map(m => renderAsset(m))}
16
+ </>
17
+ }
14
18
  scripts={
15
19
  <>
16
20
  <script innerHTML={`window.manifest = ${JSON.stringify(context.manifest)}`} />
17
- <script>$R = [];</script>
18
21
  <script
19
22
  type="module"
20
23
  src={
@@ -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
  }
@@ -0,0 +1,74 @@
1
+ import * as shikiji from 'shikiji';
2
+ import { createEffect, createResource, type JSX } from 'solid-js';
3
+
4
+ let HIGHLIGHTER: shikiji.Highlighter;
5
+
6
+ async function loadHighlighter() {
7
+ if (!HIGHLIGHTER) {
8
+ HIGHLIGHTER = await shikiji.getHighlighter({
9
+ themes: ['dark-plus'],
10
+ langs: ['js', 'ts', 'jsx', 'tsx'],
11
+ });
12
+ }
13
+ return HIGHLIGHTER;
14
+ }
15
+
16
+ export interface CodeViewProps {
17
+ fileName: string;
18
+ content: string;
19
+ line: number;
20
+ }
21
+
22
+ const RANGE = 8;
23
+
24
+ export function CodeView(props: CodeViewProps): JSX.Element | null {
25
+ const lines = () =>
26
+ props.content.split('\n').map((item, index) => ({
27
+ index: index + 1,
28
+ line: item,
29
+ }));
30
+
31
+ const minLine = () => Math.max(props.line - (1 + RANGE), 0);
32
+ const maxLine = () => Math.min(props.line + RANGE, lines().length - 1);
33
+
34
+ let ref: HTMLDivElement | undefined;
35
+
36
+ const [data] = createResource(() => (
37
+ lines()
38
+ .slice(minLine(), maxLine())
39
+ .map(item => item.line)
40
+ .join('\n')
41
+ ) ,async (value) => {
42
+ const highlighter = await loadHighlighter();
43
+ const lang = props.fileName
44
+ .split(/[#?]/)[0]
45
+ .split('.')
46
+ .pop()
47
+ ?.trim() as shikiji.BuiltinLanguage;
48
+ await highlighter.loadLanguage(lang);
49
+ return highlighter.codeToHtml(value, {
50
+ theme: 'dark-plus',
51
+ lang,
52
+ });
53
+ });
54
+
55
+ createEffect(() => {
56
+ const result = data();
57
+ if (ref && result) {
58
+ ref.innerHTML = result;
59
+
60
+ const lines = ref.querySelectorAll('span[class="line"]');
61
+
62
+ for (let i = 0, len = lines.length; i < len; i++) {
63
+ const el = lines[i];
64
+ if ((props.line - minLine() - 1) === i) {
65
+ el.innerHTML = `<mark style="background-color:#aaaaaa80">${el.innerHTML}</mark>`;
66
+ }
67
+ }
68
+ }
69
+ });
70
+
71
+ return <div ref={ref} class="dev-overlay-code-view" style={{
72
+ '--dev-overlay-code-view-start': minLine() + 1,
73
+ }} />;
74
+ }