@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 +15 -12
- package/config/server-fns-runtime.jsx +2 -0
- package/config/server-handler.js +83 -38
- package/config/server-runtime.jsx +67 -54
- package/package.json +7 -7
- package/server/middleware.ts +3 -9
- package/server/page-event.tsx +8 -13
- package/shared/GET.ts +5 -0
- package/shared/dev-overlay/CodeView.tsx +5 -4
- package/shared/dev-overlay/DevOverlayDialog.tsx +51 -49
- package/shared/dev-overlay/createStackFrame.ts +9 -1
- package/shared/dev-overlay/styles.css +9 -1
- package/shared/index.tsx +2 -1
- package/shared/lazyRoute.tsx +44 -50
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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();
|
package/config/server-handler.js
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
12
|
+
URLSearchParamsPlugin
|
|
13
|
+
} from "seroval-plugins/web";
|
|
14
14
|
import { createIslandReference } from "../server/islands";
|
|
15
15
|
|
|
16
|
-
class
|
|
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
|
|
34
|
+
async next() {
|
|
35
35
|
// Check if the buffer is empty
|
|
36
|
-
if (this.buffer ===
|
|
37
|
-
// if we are already
|
|
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
|
-
//
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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(
|
|
58
|
+
throw new Error("Malformed server function stream.");
|
|
70
59
|
}
|
|
71
|
-
//
|
|
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
|
|
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 (
|
|
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(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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.
|
|
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.
|
|
47
|
-
"vinxi": "^0.1.
|
|
46
|
+
"solid-js": "^1.8.12",
|
|
47
|
+
"vinxi": "^0.1.6"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@vinxi/plugin-directives": "^0.1.
|
|
51
|
-
"@vinxi/server-components": "^0.1.
|
|
52
|
-
"@vinxi/server-functions": "^0.1.
|
|
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.
|
|
62
|
+
"vite-plugin-solid": "^2.9.1"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"typecheck": "tsc --noEmit --checkJs false --skipLibCheck"
|
package/server/middleware.ts
CHANGED
|
@@ -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
|
|
22
|
-
event
|
|
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:
|
|
23
|
+
request: request,
|
|
30
24
|
clientAddress: getRequestIP(event),
|
|
31
25
|
locals: {},
|
|
32
26
|
// @ts-ignore
|
package/server/page-event.tsx
CHANGED
|
@@ -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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
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
|
@@ -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.
|
|
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(
|
|
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-
|
|
221
|
-
<div class="dev-overlay-navbar
|
|
222
|
-
<div class="dev-overlay-
|
|
223
|
-
<div>
|
|
224
|
-
<
|
|
225
|
-
|
|
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
|
-
<
|
|
237
|
-
<ArrowRightIcon title="Go Next" />
|
|
238
|
-
</button>
|
|
227
|
+
<span>{info.version as string}</span>
|
|
239
228
|
</div>
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
</
|
|
259
|
-
<
|
|
260
|
-
<
|
|
261
|
-
|
|
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
|
|
package/shared/lazyRoute.tsx
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
const Comp = props => {
|
|
25
|
+
if (typeof window !== "undefined") {
|
|
26
|
+
appendStyles(styles);
|
|
27
|
+
}
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
}
|