@solidjs/start 0.4.9 → 0.4.11

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.js CHANGED
@@ -73,6 +73,9 @@ export function defineConfig(baseConfig = {}) {
73
73
  {
74
74
  name: "ssr",
75
75
  mode: "handler",
76
+ link: {
77
+ client: "client"
78
+ },
76
79
  handler: `${start.appRoot}/entry-server${entryExtension}`,
77
80
  middleware: start.middleware,
78
81
  routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
@@ -85,10 +88,10 @@ export function defineConfig(baseConfig = {}) {
85
88
  ...(userConfig.optimizeDeps || {}),
86
89
  include: [
87
90
  ...(userConfig.optimizeDeps?.include || []),
88
- '@solidjs/start > source-map-js',
89
- '@solidjs/start > error-stack-parser',
90
- ],
91
- },
91
+ "@solidjs/start > source-map-js",
92
+ "@solidjs/start > error-stack-parser"
93
+ ]
94
+ }
92
95
  }),
93
96
  ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
94
97
 
@@ -138,10 +141,10 @@ export function defineConfig(baseConfig = {}) {
138
141
  ...(userConfig.optimizeDeps || {}),
139
142
  include: [
140
143
  ...(userConfig.optimizeDeps?.include || []),
141
- '@solidjs/start > source-map-js',
142
- '@solidjs/start > error-stack-parser',
143
- ],
144
- },
144
+ "@solidjs/start > source-map-js",
145
+ "@solidjs/start > error-stack-parser"
146
+ ]
147
+ }
145
148
  }),
146
149
  ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
147
150
  serverFunctions.client({
@@ -189,10 +192,10 @@ export function defineConfig(baseConfig = {}) {
189
192
  ...(userConfig.optimizeDeps || {}),
190
193
  include: [
191
194
  ...(userConfig.optimizeDeps?.include || []),
192
- '@solidjs/start > source-map-js',
193
- '@solidjs/start > error-stack-parser',
194
- ],
195
- },
195
+ "@solidjs/start > source-map-js",
196
+ "@solidjs/start > error-stack-parser"
197
+ ]
198
+ }
196
199
  }),
197
200
  ...(typeof plugins === "function" ? [...(await plugins())] : plugins),
198
201
 
@@ -3,12 +3,14 @@ import { provideRequestEvent } from "solid-js/web/storage";
3
3
  import { cloneEvent } from "../server/middleware";
4
4
 
5
5
  export function createServerReference(fn, id, name) {
6
+ if (typeof fn !== "function") throw new Error("Export from a 'use server' module must be a function");
6
7
  const baseURL = import.meta.env.SERVER_BASE_URL;
7
8
  return new Proxy(fn, {
8
9
  get(target, prop, receiver) {
9
10
  if (prop === "url") {
10
11
  return `${baseURL}/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
11
12
  }
13
+ if (prop === "GET") return receiver;
12
14
  },
13
15
  apply(target, thisArg, args) {
14
16
  const ogEvt = getRequestEvent();
@@ -16,12 +16,16 @@ import { sharedConfig } from "solid-js";
16
16
  /* @ts-ignore */
17
17
  import { provideRequestEvent } from "solid-js/web/storage";
18
18
  import invariant from "vinxi/lib/invariant";
19
- import {
20
- eventHandler,
21
- setHeader
22
- } from "vinxi/server";
19
+ import { eventHandler, setHeader } from "vinxi/server";
23
20
  import { getFetchEvent } from "../server/middleware";
24
21
 
22
+ function createChunk(data) {
23
+ const bytes = data.length;
24
+ const baseHex = bytes.toString(16);
25
+ const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit
26
+ return new TextEncoder().encode(`;0x${totalHex};${data}`);
27
+ }
28
+
25
29
  function serializeToStream(id, value) {
26
30
  return new ReadableStream({
27
31
  start(controller) {
@@ -40,15 +44,14 @@ function serializeToStream(id, value) {
40
44
  URLPlugin
41
45
  ],
42
46
  onSerialize(data, initial) {
43
- const result = initial ? `(${getCrossReferenceHeader(id)},${data})` : data;
44
- controller.enqueue(new TextEncoder().encode(`${result};\n`));
47
+ controller.enqueue(
48
+ createChunk(initial ? `(${getCrossReferenceHeader(id)},${data})` : data)
49
+ );
45
50
  },
46
51
  onDone() {
47
- // controller.enqueue(`delete $R["${id}"];\n`);
48
52
  controller.close();
49
53
  },
50
54
  onError(error) {
51
- // controller.enqueue(`delete $R["${id}"];\n`);
52
55
  controller.error(error);
53
56
  }
54
57
  });
@@ -57,7 +60,6 @@ function serializeToStream(id, value) {
57
60
  }
58
61
 
59
62
  async function handleServerFunction(h3Event) {
60
- invariant(h3Event.method === "POST", `Invalid method ${h3Event.method}. Expected POST.`);
61
63
  const event = getFetchEvent(h3Event);
62
64
  const request = event.request;
63
65
 
@@ -80,39 +82,71 @@ async function handleServerFunction(h3Event) {
80
82
  let parsed = [];
81
83
 
82
84
  // grab bound arguments from url when no JS
83
- if (!instance) {
85
+ if (!instance || h3Event.method === "GET") {
84
86
  const args = url.searchParams.get("args");
85
87
  if (args) JSON.parse(args).forEach(arg => parsed.push(arg));
86
88
  }
87
- const contentType = request.headers.get("content-type");
88
- if (
89
- contentType.startsWith("multipart/form-data") ||
90
- contentType.startsWith("application/x-www-form-urlencoded")
91
- ) {
92
- parsed.push(await request.formData());
93
- } else {
94
- parsed = fromJSON(await request.json(), {
95
- plugins: [
96
- CustomEventPlugin,
97
- DOMExceptionPlugin,
98
- EventPlugin,
99
- FormDataPlugin,
100
- HeadersPlugin,
101
- ReadableStreamPlugin,
102
- RequestPlugin,
103
- ResponsePlugin,
104
- URLSearchParamsPlugin,
105
- URLPlugin
106
- ]
107
- });
89
+ if (h3Event.method === "POST") {
90
+ const contentType = request.headers.get("content-type");
91
+ if (
92
+ contentType.startsWith("multipart/form-data") ||
93
+ contentType.startsWith("application/x-www-form-urlencoded")
94
+ ) {
95
+ // workaround for https://github.com/unjs/nitro/issues/1721
96
+ // (issue only in edge runtimes)
97
+ parsed.push(await new Request(request, { ...request, body: event.node.req.body }).formData());
98
+ // what should work when #1721 is fixed
99
+ // parsed.push(await request.formData);
100
+ } else {
101
+ // workaround for https://github.com/unjs/nitro/issues/1721
102
+ // (issue only in edge runtimes)
103
+ const tmpReq = new Request(request, { ...request, body: event.node.req.body })
104
+ // what should work when #1721 is fixed
105
+ // just use request.json() here
106
+ parsed = fromJSON(await tmpReq.json(), {
107
+ plugins: [
108
+ CustomEventPlugin,
109
+ DOMExceptionPlugin,
110
+ EventPlugin,
111
+ FormDataPlugin,
112
+ HeadersPlugin,
113
+ ReadableStreamPlugin,
114
+ RequestPlugin,
115
+ ResponsePlugin,
116
+ URLSearchParamsPlugin,
117
+ URLPlugin
118
+ ]
119
+ });
120
+ }
108
121
  }
109
122
  try {
110
- const result = await provideRequestEvent(event, () => {
123
+ let result = await provideRequestEvent(event, () => {
111
124
  /* @ts-ignore */
112
125
  sharedConfig.context = { event };
113
126
  return action(...parsed);
114
127
  });
115
128
 
129
+ // handle responses
130
+ if (result instanceof Response) {
131
+ if (result.status === 302) {
132
+ return new Response(null, {
133
+ status: instance ? 204 : 302,
134
+ headers: {
135
+ Location: result.headers.get("Location")
136
+ }
137
+ });
138
+ }
139
+ // forward headers
140
+ if (result.headers) {
141
+ for (const [key, value] of result.headers.entries()) {
142
+ setHeader(h3Event, key, value);
143
+ }
144
+ }
145
+ if (result.customBody) {
146
+ result = await result.customBody();
147
+ } else if (result.body == undefined) result = undefined;
148
+ }
149
+
116
150
  // handle no JS success case
117
151
  if (!instance) {
118
152
  const isError = result instanceof Error;
@@ -138,13 +172,24 @@ async function handleServerFunction(h3Event) {
138
172
  setHeader(h3Event, "content-type", "text/javascript");
139
173
  return serializeToStream(instance, result);
140
174
  } catch (x) {
141
- if (x instanceof Response && x.status === 302) {
142
- return new Response(null, {
143
- status: instance ? 204 : 302,
144
- headers: {
145
- Location: x.headers.get("Location")
175
+ if (x instanceof Response) {
176
+ if (x.status === 302) {
177
+ return new Response(null, {
178
+ status: instance ? 204 : 302,
179
+ headers: {
180
+ Location: x.headers.get("Location")
181
+ }
182
+ });
183
+ }
184
+ // forward headers
185
+ if (x.headers) {
186
+ for (const [key, value] of x.headers.entries()) {
187
+ setHeader(h3Event, key, value);
146
188
  }
147
- });
189
+ }
190
+ if (x.customBody) {
191
+ x = await x.customBody();
192
+ } else if (x.body == undefined) x = undefined;
148
193
  }
149
194
  return x;
150
195
  }
@@ -9,14 +9,14 @@ import {
9
9
  RequestPlugin,
10
10
  ResponsePlugin,
11
11
  URLPlugin,
12
- URLSearchParamsPlugin,
13
- } from 'seroval-plugins/web';
12
+ URLSearchParamsPlugin
13
+ } from "seroval-plugins/web";
14
14
  import { createIslandReference } from "../server/islands";
15
15
 
16
- class ChunkReader {
16
+ class SerovalChunkReader {
17
17
  constructor(stream) {
18
18
  this.reader = stream.getReader();
19
- this.buffer = '';
19
+ this.buffer = "";
20
20
  this.done = false;
21
21
  }
22
22
 
@@ -31,50 +31,44 @@ class ChunkReader {
31
31
  }
32
32
  }
33
33
 
34
- async nextValue() {
34
+ async next() {
35
35
  // Check if the buffer is empty
36
- if (this.buffer === '') {
37
- // if we are already one...
36
+ if (this.buffer === "") {
37
+ // if we are already done...
38
38
  if (this.done) {
39
39
  return {
40
40
  done: true,
41
- value: undefined,
41
+ value: undefined
42
42
  };
43
43
  }
44
44
  // Otherwise, read a new chunk
45
45
  await this.readChunk();
46
+ return await this.next();
46
47
  }
47
- // Get the first valid seroval chunk
48
- const [first, ...rest] = this.buffer.split('\n');
49
- // Deserialize the seroval chunk
50
- const result = {
51
- done: false,
52
- value: deserialize(first),
53
- };
54
- // if it succeeds, remove the first valid chunk
55
- // from the buffer
56
- this.buffer = rest.join('\n');
57
- return result;
58
- }
59
-
60
- async next() {
61
- try {
62
- // Attempt to read a valid seroval chunk
63
- return await this.nextValue();
64
- } catch (error) {
65
- // If it happens that there's an error again
66
- // and we are done reading the buffer
67
- // then the whole stream is invalid.
48
+ // Read the "byte header"
49
+ // The byte header tells us how big the expected data is
50
+ // so we know how much data we should wait before we
51
+ // deserialize the data
52
+ const bytes = Number.parseInt(this.buffer.substring(1, 11), 16); // ;0x00000000;
53
+ // Check if the buffer has enough bytes to be parsed
54
+ while (bytes > this.buffer.length - 12) {
55
+ // If it's not enough, and the reader is done
56
+ // then the chunk is invalid.
68
57
  if (this.done) {
69
- throw new Error('Malformed server function stream.');
58
+ throw new Error("Malformed server function stream.");
70
59
  }
71
- // Since it's invalid (some syntax-related issue)
72
- // we read a new chunk, and hope there's a valid
73
- // seroval chunk there
60
+ // Otherwise, we read more chunks
74
61
  await this.readChunk();
75
- // Retry again
76
- return await this.next();
77
62
  }
63
+ // Extract the exact chunk as defined by the byte header
64
+ const partial = this.buffer.substring(12, 12 + bytes);
65
+ // The rest goes to the buffer
66
+ this.buffer = this.buffer.substring(12 + bytes);
67
+ // Deserialize the chunk
68
+ return {
69
+ done: false,
70
+ value: deserialize(partial)
71
+ };
78
72
  }
79
73
 
80
74
  async drain() {
@@ -91,7 +85,7 @@ async function deserializeStream(id, response) {
91
85
  if (!response.body) {
92
86
  throw new Error("missing body");
93
87
  }
94
- const reader = new ChunkReader(response.body);
88
+ const reader = new SerovalChunkReader(response.body);
95
89
 
96
90
  const result = await reader.next();
97
91
 
@@ -123,31 +117,47 @@ function createRequest(base, id, instance, body, contentType) {
123
117
  });
124
118
  }
125
119
 
126
- async function fetchServerFunction(base, id, args) {
120
+ async function fetchServerFunction(base, id, method, args) {
127
121
  const instance = `server-fn:${INSTANCE++}`;
128
- const response = await (args.length === 1 && args[0] instanceof FormData
122
+ const response = await (method === "GET"
123
+ ? fetch(base + (args.length ? `&args=${JSON.stringify(args)}` : ""), {
124
+ headers: {
125
+ "x-server-instance": instance
126
+ }
127
+ })
128
+ : args.length === 1 && args[0] instanceof FormData
129
129
  ? createRequest(base, id, instance, args[0])
130
- : createRequest(base, id, instance, JSON.stringify(await Promise.resolve(toJSONAsync(args, {
131
- plugins: [
132
- CustomEventPlugin,
133
- DOMExceptionPlugin,
134
- EventPlugin,
135
- FormDataPlugin,
136
- HeadersPlugin,
137
- ReadableStreamPlugin,
138
- RequestPlugin,
139
- ResponsePlugin,
140
- URLSearchParamsPlugin,
141
- URLPlugin,
142
- ],
143
- }))), "application/json"));
130
+ : createRequest(
131
+ base,
132
+ id,
133
+ instance,
134
+ JSON.stringify(
135
+ await Promise.resolve(
136
+ toJSONAsync(args, {
137
+ plugins: [
138
+ CustomEventPlugin,
139
+ DOMExceptionPlugin,
140
+ EventPlugin,
141
+ FormDataPlugin,
142
+ HeadersPlugin,
143
+ ReadableStreamPlugin,
144
+ RequestPlugin,
145
+ ResponsePlugin,
146
+ URLSearchParamsPlugin,
147
+ URLPlugin
148
+ ]
149
+ })
150
+ )
151
+ ),
152
+ "application/json"
153
+ ));
144
154
 
145
155
  if (response.headers.get("Location")) throw response;
146
156
  if (response.headers.get("X-Revalidate")) {
147
157
  /* ts-ignore-next-line */
148
158
  response.customBody = () => {
149
159
  return deserializeStream(instance, response);
150
- }
160
+ };
151
161
  throw response;
152
162
  }
153
163
  const contentType = response.headers.get("Content-Type");
@@ -172,9 +182,12 @@ export function createServerReference(fn, id, name) {
172
182
  if (prop === "url") {
173
183
  return `${baseURL}/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
174
184
  }
185
+ if (prop === "GET") {
186
+ return (...args) => fetchServerFunction(`${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`, `${id}#${name}`, "GET", args);
187
+ }
175
188
  },
176
189
  apply(target, thisArg, args) {
177
- return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`, args);
190
+ return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`, "POST", args);
178
191
  }
179
192
  });
180
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/start",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "type": "module",
5
5
  "author": "Ryan Carniato",
6
6
  "publishConfig": {
@@ -43,13 +43,13 @@
43
43
  }
44
44
  },
45
45
  "devDependencies": {
46
- "solid-js": "^1.8.11",
47
- "vinxi": "^0.1.2"
46
+ "solid-js": "^1.8.12",
47
+ "vinxi": "^0.1.6"
48
48
  },
49
49
  "dependencies": {
50
- "@vinxi/plugin-directives": "^0.1.0",
51
- "@vinxi/server-components": "^0.1.0",
52
- "@vinxi/server-functions": "^0.1.0",
50
+ "@vinxi/plugin-directives": "^0.1.3",
51
+ "@vinxi/server-components": "^0.1.3",
52
+ "@vinxi/server-functions": "^0.1.3",
53
53
  "defu": "^6.1.2",
54
54
  "error-stack-parser": "^2.1.4",
55
55
  "html-to-image": "^1.11.11",
@@ -59,7 +59,7 @@
59
59
  "source-map-js": "^1.0.2",
60
60
  "terracotta": "^1.0.4",
61
61
  "vite-plugin-inspect": "^0.7.33",
62
- "vite-plugin-solid": "^2.8.0"
62
+ "vite-plugin-solid": "^2.9.1"
63
63
  },
64
64
  "scripts": {
65
65
  "typecheck": "tsc --noEmit --checkJs false --skipLibCheck"
@@ -1,8 +1,6 @@
1
1
  import {
2
2
  defineMiddleware,
3
- EventHandlerRequest,
4
3
  getRequestIP,
5
- getRequestURL,
6
4
  H3Event,
7
5
  sendWebResponse,
8
6
  toWebRequest
@@ -18,15 +16,11 @@ const eventTraps = {
18
16
  }
19
17
  };
20
18
 
21
- export function createFetchEvent(event: H3Event<EventHandlerRequest>): FetchEvent {
22
- event.web ||
23
- (event.web = {
24
- url: getRequestURL(event),
25
- request: toWebRequest(event)
26
- });
19
+ export function createFetchEvent(event: H3Event): FetchEvent {
20
+ const request = toWebRequest(event);
27
21
  return new Proxy(
28
22
  {
29
- request: event.web.request,
23
+ request: request,
30
24
  clientAddress: getRequestIP(event),
31
25
  locals: {},
32
26
  // @ts-ignore
@@ -45,7 +45,7 @@ export async function createPageEvent(ctx: FetchEvent) {
45
45
  components: {
46
46
  status: props => {
47
47
  setResponseStatus(ctx, props.code, props.text);
48
- return () => setResponseStatus(ctx, 200);
48
+ return () => !ctx.handled && setResponseStatus(ctx, 200);
49
49
  },
50
50
  header: props => {
51
51
  if (props.append) {
@@ -55,18 +55,13 @@ export async function createPageEvent(ctx: FetchEvent) {
55
55
  }
56
56
 
57
57
  return () => {
58
- const value = getResponseHeader(ctx, props.name);
59
-
60
- // Todo: review this logic still the same in H3
61
- // H3 supports arrays so its possible there is no need to split..
62
- // typeof check here is to guard but ultimately may be unneeded
63
- if (value && typeof value === "string") {
64
- const values = value.split(", ");
65
- const index = values.indexOf(props.value);
66
- index !== -1 && values.splice(index, 1);
67
- if (values.length) setResponseHeader(ctx, props.name, values.join(", "));
68
- else removeResponseHeader(ctx, props.name);
69
- }
58
+ if (ctx.handled) return;
59
+ let values = getResponseHeader(ctx, props.name);
60
+ if (!Array.isArray(values)) values = [values as string];
61
+ const index = values.indexOf(props.value);
62
+ index !== -1 && values.splice(index, 1);
63
+ if (values.length) setResponseHeader(ctx, props.name, values);
64
+ else removeResponseHeader(ctx, props.name);
70
65
  };
71
66
  }
72
67
  },
package/shared/GET.ts ADDED
@@ -0,0 +1,5 @@
1
+ export function GET<T extends (...args: any[]) => any>(fn: T) {
2
+ return (...args: Parameters<T>) => {
3
+ return (fn as any).GET(...args) as ReturnType<T>;
4
+ };
5
+ }
@@ -1,8 +1,9 @@
1
1
  import type { BuiltinLanguage, Highlighter } from 'shikiji';
2
- import { getHighlighterCore } from 'shikiji/core';
3
- import { getWasmInlined } from 'shikiji/wasm';
2
+ import { getHighlighterCore, loadWasm } from 'shikiji/core';
4
3
  import { createEffect, createResource, type JSX } from 'solid-js';
5
4
 
5
+ import url from 'shikiji/onig.wasm?url';
6
+
6
7
  import langJS from 'shikiji/langs/javascript.mjs';
7
8
  import langJSX from 'shikiji/langs/jsx.mjs';
8
9
  import langTSX from 'shikiji/langs/tsx.mjs';
@@ -13,6 +14,7 @@ let HIGHLIGHTER: Highlighter;
13
14
 
14
15
  async function loadHighlighter() {
15
16
  if (!HIGHLIGHTER) {
17
+ await loadWasm(await fetch(url))
16
18
  HIGHLIGHTER = await getHighlighterCore({
17
19
  themes: [
18
20
  darkPlus,
@@ -23,7 +25,6 @@ async function loadHighlighter() {
23
25
  langTS,
24
26
  langTSX,
25
27
  ],
26
- loadWasm: getWasmInlined,
27
28
  });
28
29
  }
29
30
  return HIGHLIGHTER;
@@ -77,7 +78,7 @@ export function CodeView(props: CodeViewProps): JSX.Element | null {
77
78
  for (let i = 0, len = lines.length; i < len; i++) {
78
79
  const el = lines[i];
79
80
  if ((props.line - minLine() - 1) === i) {
80
- el.innerHTML = `<mark style="background-color:#aaaaaa80">${el.innerHTML}</mark>`;
81
+ el.classList.add('dev-overlay-error-line');
81
82
  }
82
83
  }
83
84
  }
@@ -190,7 +190,7 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
190
190
  if (current) {
191
191
  htmlToImage.toPng(current, {
192
192
  style: {
193
- transform: 'scale(1)',
193
+ transform: 'scale(0.75)',
194
194
  },
195
195
  }).then((url) => {
196
196
  download(url, 'start-screenshot.png');
@@ -216,59 +216,61 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
216
216
  <Dialog class="dev-overlay" isOpen>
217
217
  <div>
218
218
  <DialogOverlay class="dev-overlay-background"/>
219
- <DialogPanel ref={setPanel} class="dev-overlay-panel">
220
- <div class="dev-overlay-navbar">
221
- <div class="dev-overlay-navbar-left">
222
- <div class="dev-overlay-version">
223
- <div>
224
- <SolidStartIcon title="Solid Start Version" />
225
- </div>
226
- <span>{info.version as string}</span>
227
- </div>
228
- <Show when={props.errors.length > 1}>
229
- <div class="dev-overlay-pagination">
230
- <button class="dev-overlay-button" onClick={goPrev} type="button">
231
- <ArrowLeftIcon title="Go Previous" />
232
- </button>
233
- <div class="dev-overlay-page-counter">
234
- {`${truncated()} of ${props.errors.length}`}
219
+ <DialogPanel ref={setPanel} class="dev-overlay-panel-container">
220
+ <div class="dev-overlay-panel">
221
+ <div class="dev-overlay-navbar">
222
+ <div class="dev-overlay-navbar-left">
223
+ <div class="dev-overlay-version">
224
+ <div>
225
+ <SolidStartIcon title="Solid Start Version" />
235
226
  </div>
236
- <button class="dev-overlay-button" onClick={goNext} type="button">
237
- <ArrowRightIcon title="Go Next" />
238
- </button>
227
+ <span>{info.version as string}</span>
239
228
  </div>
240
- </Show>
241
- </div>
242
- <div class="dev-overlay-controls">
243
- <button class="dev-overlay-button" onClick={redirectToGithub} type="button">
244
- <GithubIcon title="Create an issue thread on Github" />
245
- </button>
246
- <button class="dev-overlay-button" onClick={redirectToDiscord} type="button">
247
- <DiscordIcon title="Join our Discord Channel" />
248
- </button>
249
- <button class="dev-overlay-button" onClick={downloadScreenshot} type="button">
250
- <CameraIcon title="Capture Error Overlay" />
251
- </button>
252
- <button class="dev-overlay-button" onClick={toggleIsCompiled} type="button">
253
- <Show when={isCompiled()} fallback={(
254
- <ViewOriginalIcon title="View Original Source" />
255
- )}>
256
- <ViewCompiledIcon title="View Compiled Source" />
229
+ <Show when={props.errors.length > 1}>
230
+ <div class="dev-overlay-pagination">
231
+ <button class="dev-overlay-button" onClick={goPrev} type="button">
232
+ <ArrowLeftIcon title="Go Previous" />
233
+ </button>
234
+ <div class="dev-overlay-page-counter">
235
+ {`${truncated()} of ${props.errors.length}`}
236
+ </div>
237
+ <button class="dev-overlay-button" onClick={goNext} type="button">
238
+ <ArrowRightIcon title="Go Next" />
239
+ </button>
240
+ </div>
257
241
  </Show>
258
- </button>
259
- <button class="dev-overlay-button" onClick={props.resetError} type="button">
260
- <RefreshIcon title="Reset Error" />
261
- </button>
242
+ </div>
243
+ <div class="dev-overlay-controls">
244
+ <button class="dev-overlay-button" onClick={redirectToGithub} type="button">
245
+ <GithubIcon title="Create an issue thread on Github" />
246
+ </button>
247
+ <button class="dev-overlay-button" onClick={redirectToDiscord} type="button">
248
+ <DiscordIcon title="Join our Discord Channel" />
249
+ </button>
250
+ <button class="dev-overlay-button" onClick={downloadScreenshot} type="button">
251
+ <CameraIcon title="Capture Error Overlay" />
252
+ </button>
253
+ <button class="dev-overlay-button" onClick={toggleIsCompiled} type="button">
254
+ <Show when={isCompiled()} fallback={(
255
+ <ViewOriginalIcon title="View Original Source" />
256
+ )}>
257
+ <ViewCompiledIcon title="View Compiled Source" />
258
+ </Show>
259
+ </button>
260
+ <button class="dev-overlay-button" onClick={props.resetError} type="button">
261
+ <RefreshIcon title="Reset Error" />
262
+ </button>
263
+ </div>
262
264
  </div>
265
+ <Show when={props.errors[truncated() - 1]} keyed>
266
+ {(current) => (
267
+ <div class="dev-overlay-content">
268
+ <ErrorInfo error={current} />
269
+ <StackFrames error={current} isCompiled={isCompiled()} />
270
+ </div>
271
+ )}
272
+ </Show>
263
273
  </div>
264
- <Show when={props.errors[truncated() - 1]} keyed>
265
- {(current) => (
266
- <div class="dev-overlay-content">
267
- <ErrorInfo error={current} />
268
- <StackFrames error={current} isCompiled={isCompiled()} />
269
- </div>
270
- )}
271
- </Show>
272
274
  </DialogPanel>
273
275
  </div>
274
276
  </Dialog>
@@ -9,6 +9,13 @@ export interface StackFrameSource {
9
9
  column: number;
10
10
  }
11
11
 
12
+ function getActualFileSource(path: string): string {
13
+ if (path.startsWith('file://')) {
14
+ return '/_build/@fs' + path.substring('file://'.length);
15
+ }
16
+ return path;
17
+ }
18
+
12
19
  export function createStackFrame(
13
20
  stackframe: StackFrame,
14
21
  isCompiled: () => boolean,
@@ -24,7 +31,7 @@ export function createStackFrame(
24
31
  if (!source.fileName) {
25
32
  return null;
26
33
  }
27
- const response = await fetch(source.fileName);
34
+ const response = await fetch(getActualFileSource(source.fileName));
28
35
  if (!response.ok) {
29
36
  return null;
30
37
  }
@@ -51,6 +58,7 @@ export function createStackFrame(
51
58
  line: source.line,
52
59
  column: source.column,
53
60
  });
61
+ console.log('sourcemap', result);
54
62
 
55
63
  return {
56
64
  ...result,
@@ -25,12 +25,16 @@
25
25
  width: 75vw;
26
26
  margin-top: 2rem;
27
27
  margin-bottom: 2rem;
28
- z-index: 10;
29
28
  gap: 0.5rem;
30
29
  color: rgb(17 24 39);
31
30
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
32
31
  }
33
32
 
33
+ .dev-overlay-panel-container {
34
+ margin: 2rem;
35
+ z-index: 10;
36
+ }
37
+
34
38
  .dev-overlay-navbar {
35
39
  display: flex;
36
40
  flex-direction: row;
@@ -281,4 +285,8 @@
281
285
  display: inline-block;
282
286
  text-align: right;
283
287
  color: rgba(115, 138, 148, .4)
288
+ }
289
+
290
+ .dev-overlay-error-line {
291
+ background-color: rgba(239, 68, 68, .4);
284
292
  }
package/shared/index.tsx CHANGED
@@ -1,5 +1,6 @@
1
- export { default as clientOnly } from "./clientOnly";
2
1
  export { FileRoutes } from "./FileRoutes";
2
+ export { GET } from "./GET";
3
3
  export { HttpHeader } from "./HttpHeader";
4
4
  export { HttpStatusCode } from "./HttpStatusCode";
5
+ export { default as clientOnly } from "./clientOnly";
5
6
 
@@ -1,60 +1,54 @@
1
1
  /// <reference types="vinxi/types/client" />
2
2
  import { createComponent, lazy, onCleanup } from "solid-js";
3
- import { appendStyles, cleanupStyles, updateStyles } from "vinxi/css";
3
+ import { appendStyles, cleanupStyles, updateStyles, preloadStyles } from "vinxi/css";
4
4
 
5
5
  import { renderAsset } from "../server/renderAsset";
6
6
 
7
- export default function lazyRoute(
8
- component,
9
- clientManifest,
10
- serverManifest,
11
- exported = "default",
12
- ) {
13
- return lazy(async () => {
14
- if (import.meta.env.DEV) {
15
- let manifest = import.meta.env.SSR ? serverManifest : clientManifest;
7
+ export default function lazyRoute(component, clientManifest, serverManifest, exported = "default") {
8
+ return lazy(async () => {
9
+ if (import.meta.env.DEV) {
10
+ let manifest = import.meta.env.SSR ? serverManifest : clientManifest;
16
11
 
17
- const mod = await manifest.inputs[component.src].import();
18
- if (!mod[exported]) console.error(`Module ${component.src} does not export ${exported}`)
19
- const Component = mod[exported];
20
- let assets = await clientManifest.inputs?.[component.src].assets();
21
- const styles = assets.filter((asset) => asset.tag === "style");
12
+ const mod = await manifest.inputs[component.src].import();
13
+ if (!mod[exported]) console.error(`Module ${component.src} does not export ${exported}`);
14
+ const Component = mod[exported];
15
+ let assets = await clientManifest.inputs?.[component.src].assets();
16
+ const styles = assets.filter(asset => asset.tag === "style");
22
17
 
23
- if (typeof window !== "undefined" && import.meta.hot) {
24
- import.meta.hot.on("css-update", (data) => {
25
- updateStyles(styles, data);
26
- });
27
- }
18
+ if (typeof window !== "undefined" && import.meta.hot) {
19
+ import.meta.hot.on("css-update", data => {
20
+ updateStyles(styles, data);
21
+ });
22
+ }
28
23
 
29
- const Comp = (props) => {
30
- if (typeof window !== "undefined") {
31
- appendStyles(styles);
32
- }
24
+ const Comp = props => {
25
+ if (typeof window !== "undefined") {
26
+ appendStyles(styles);
27
+ }
33
28
 
34
- onCleanup(() => {
35
- if (typeof window !== "undefined") {
36
- // remove style tags added by vite when a CSS file is imported
37
- cleanupStyles(styles);
38
- }
39
- });
40
- return [
41
- ...assets.map((asset) => renderAsset(asset)),
42
- createComponent(Component, props),
43
- ];
44
- };
45
- return { default: Comp };
46
- } else {
47
- const mod = await component.import();
48
- const Component = mod[exported];
49
- let assets = await clientManifest.inputs?.[component.src].assets();
50
- const styles = assets.filter((asset) => asset.tag === "style" || asset.attrs.rel === "stylesheet");
51
- const Comp = (props) => {
52
- return [
53
- ...styles.map((asset) => renderAsset(asset)),
54
- createComponent(Component, props),
55
- ];
56
- };
57
- return { default: Comp };
58
- }
59
- });
29
+ onCleanup(() => {
30
+ if (typeof window !== "undefined") {
31
+ // remove style tags added by vite when a CSS file is imported
32
+ cleanupStyles(styles);
33
+ }
34
+ });
35
+ return [...assets.map(asset => renderAsset(asset)), createComponent(Component, props)];
36
+ };
37
+ return { default: Comp };
38
+ } else {
39
+ const mod = await component.import();
40
+ const Component = mod[exported];
41
+ let assets = await clientManifest.inputs?.[component.src].assets();
42
+ const styles = assets.filter(
43
+ asset => asset.tag === "style" || asset.attrs.rel === "stylesheet"
44
+ );
45
+ if (typeof window !== "undefined") {
46
+ preloadStyles(styles);
47
+ }
48
+ const Comp = props => {
49
+ return [...styles.map(asset => renderAsset(asset)), createComponent(Component, props)];
50
+ };
51
+ return { default: Comp };
52
+ }
53
+ });
60
54
  }