@solidjs/start 1.2.1 → 2.0.0-alpha.1
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/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/dist/client/StartClient.d.ts +0 -1
- package/dist/client/StartClient.jsx +2 -4
- package/dist/client/index.d.ts +2 -3
- package/dist/client/index.jsx +2 -4
- package/dist/client/mount.d.ts +1 -7
- package/dist/client/mount.js +1 -91
- package/dist/client/spa/index.d.ts +1 -2
- package/dist/client/spa/index.jsx +1 -2
- package/dist/config/constants.d.ts +16 -0
- package/dist/config/constants.js +16 -0
- package/dist/config/dev-server.d.ts +2 -0
- package/dist/config/dev-server.js +108 -0
- package/dist/config/fs-router.d.ts +37 -0
- package/dist/config/fs-router.js +141 -0
- package/dist/config/fs-routes/fs-watcher.d.ts +3 -0
- package/dist/config/fs-routes/fs-watcher.js +49 -0
- package/dist/config/fs-routes/index.d.ts +7 -0
- package/dist/config/fs-routes/index.js +107 -0
- package/dist/config/fs-routes/router.d.ts +37 -0
- package/dist/config/fs-routes/router.js +136 -0
- package/dist/config/fs-routes/tree-shake.d.ts +2 -0
- package/dist/config/fs-routes/tree-shake.js +348 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +214 -0
- package/dist/config/lazy.d.ts +3 -0
- package/dist/config/lazy.js +82 -0
- package/dist/config/manifest.d.ts +3 -0
- package/dist/config/manifest.js +74 -0
- package/dist/config/utils.d.ts +4 -0
- package/dist/config/utils.js +6 -0
- package/dist/config/vite-utils.d.ts +16 -0
- package/dist/config/vite-utils.js +81 -0
- package/dist/http/index.d.ts +81 -0
- package/dist/http/index.js +129 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.jsx +5 -5
- package/dist/middleware/index.d.ts +6 -6
- package/dist/middleware/index.js +39 -0
- package/dist/{router/FileRoutes.js → router.jsx} +7 -8
- package/dist/server/StartServer.d.ts +3 -3
- package/dist/server/StartServer.jsx +9 -63
- package/dist/server/assets/index.d.ts +2 -0
- package/dist/server/assets/index.js +52 -0
- package/dist/server/assets/render.d.ts +19 -0
- package/dist/server/{renderAsset.jsx → assets/render.jsx} +1 -1
- package/dist/server/collect-styles.d.ts +3 -0
- package/dist/server/collect-styles.js +69 -0
- package/dist/server/fetchEvent.d.ts +4 -3
- package/dist/server/fetchEvent.js +12 -74
- package/dist/server/fetchEvent.spec.js +18 -197
- package/dist/server/handler.d.ts +6 -12
- package/dist/server/handler.js +193 -96
- package/dist/server/index.d.ts +9 -6
- package/dist/server/index.jsx +14 -6
- package/dist/server/manifest/client-manifest.d.ts +9 -0
- package/dist/server/manifest/client-manifest.js +6 -0
- package/dist/server/manifest/dev-client-manifest.d.ts +4 -0
- package/dist/server/manifest/dev-client-manifest.js +24 -0
- package/dist/server/manifest/dev-ssr-manifest.d.ts +5 -0
- package/dist/server/manifest/dev-ssr-manifest.js +23 -0
- package/dist/server/manifest/prod-client-manifest.d.ts +5 -0
- package/dist/server/manifest/prod-client-manifest.js +26 -0
- package/dist/server/manifest/prod-ssr-manifest.d.ts +6 -0
- package/dist/server/manifest/prod-ssr-manifest.js +85 -0
- package/dist/server/manifest/ssr-manifest.d.ts +4 -0
- package/dist/server/manifest/ssr-manifest.js +6 -0
- package/dist/{router → server}/routes.d.ts +5 -10
- package/dist/{router → server}/routes.js +32 -33
- package/dist/{runtime → server}/server-fns-runtime.d.ts +1 -1
- package/dist/{runtime → server}/server-fns-runtime.js +8 -7
- package/dist/server/server-functions-handler.d.ts +2 -0
- package/dist/{runtime/server-handler.js → server/server-functions-handler.js} +57 -97
- package/dist/server/server-runtime.d.ts +3 -0
- package/dist/{runtime → server}/server-runtime.js +17 -24
- package/dist/server/spa/StartServer.d.ts +1 -1
- package/dist/server/spa/StartServer.jsx +7 -9
- package/dist/server/spa/handler.d.ts +3 -3
- package/dist/server/spa/handler.js +9 -11
- package/dist/server/spa/index.d.ts +4 -3
- package/dist/server/spa/index.jsx +4 -3
- package/dist/server/types.d.ts +7 -9
- package/dist/server/util.d.ts +2 -0
- package/dist/server/util.js +8 -0
- package/dist/shared/ErrorBoundary.jsx +2 -2
- package/dist/shared/HttpHeader.jsx +4 -2
- package/dist/shared/{HttpStatusCode.jsx → HttpStatusCode.js} +3 -1
- package/dist/shared/dev-overlay/CodeView.d.ts +1 -1
- package/dist/shared/dev-overlay/CodeView.jsx +23 -34
- package/dist/shared/dev-overlay/DevOverlayDialog.d.ts +2 -2
- package/dist/shared/dev-overlay/DevOverlayDialog.jsx +73 -64
- package/dist/shared/dev-overlay/createStackFrame.js +3 -3
- package/dist/shared/dev-overlay/get-source-map.d.ts +1 -1
- package/dist/shared/dev-overlay/get-source-map.js +5 -5
- package/dist/shared/dev-overlay/icons.d.ts +10 -10
- package/dist/shared/dev-overlay/icons.jsx +91 -91
- package/dist/shared/dev-overlay/index.jsx +6 -9
- package/dist/shared/lazy.d.ts +3 -0
- package/dist/shared/lazy.js +42 -0
- package/dist/shared/serverFunction.d.ts +1 -1
- package/env.d.ts +2 -19
- package/package.json +44 -65
- package/scripts/build.js +15 -0
- package/scripts/validate-imports.js +316 -0
- package/src/client/StartClient.tsx +34 -0
- package/src/client/index.tsx +2 -0
- package/src/client/mount.ts +1 -0
- package/src/client/spa/index.tsx +9 -0
- package/src/config/constants.ts +19 -0
- package/src/config/dev-server.ts +145 -0
- package/{config/fs-router.js → src/config/fs-router.ts} +49 -29
- package/src/config/fs-routes/fs-watcher.ts +77 -0
- package/src/config/fs-routes/index.ts +136 -0
- package/src/config/fs-routes/router.ts +169 -0
- package/src/config/fs-routes/tree-shake.ts +371 -0
- package/src/config/index.ts +233 -0
- package/src/config/lazy.ts +100 -0
- package/src/config/manifest.ts +84 -0
- package/src/config/utils.ts +5 -0
- package/src/config/vite-utils.ts +109 -0
- package/src/env.d.ts +17 -0
- package/src/http/index.ts +216 -0
- package/src/index.tsx +7 -0
- package/src/internal.d.ts +14 -0
- package/src/middleware/index.ts +72 -0
- package/src/router.tsx +37 -0
- package/src/server/StartServer.tsx +56 -0
- package/src/server/assets/index.ts +70 -0
- package/src/server/assets/render.tsx +46 -0
- package/src/server/collect-styles.ts +87 -0
- package/src/server/fetchEvent.spec.ts +85 -0
- package/src/server/fetchEvent.ts +36 -0
- package/src/server/handler.ts +257 -0
- package/src/server/index.tsx +29 -0
- package/src/server/manifest/client-manifest.ts +8 -0
- package/src/server/manifest/dev-client-manifest.ts +21 -0
- package/src/server/manifest/dev-ssr-manifest.ts +23 -0
- package/src/server/manifest/prod-client-manifest.ts +21 -0
- package/src/server/manifest/prod-ssr-manifest.ts +109 -0
- package/src/server/manifest/ssr-manifest.ts +10 -0
- package/src/server/routes.ts +126 -0
- package/src/server/server-fns-runtime.ts +31 -0
- package/src/server/server-functions-handler.ts +339 -0
- package/src/server/server-runtime.ts +240 -0
- package/src/server/spa/StartServer.tsx +40 -0
- package/src/server/spa/handler.ts +29 -0
- package/src/server/spa/index.tsx +5 -0
- package/src/server/types.ts +82 -0
- package/src/server/util.ts +12 -0
- package/src/shared/ErrorBoundary.tsx +52 -0
- package/src/shared/GET.ts +8 -0
- package/src/shared/HttpHeader.tsx +42 -0
- package/src/shared/HttpStatusCode.ts +27 -0
- package/src/shared/clientOnly.ts +44 -0
- package/src/shared/dev-overlay/CodeView.tsx +92 -0
- package/src/shared/dev-overlay/DevOverlayDialog.tsx +316 -0
- package/src/shared/dev-overlay/createStackFrame.ts +74 -0
- package/src/shared/dev-overlay/download.ts +6 -0
- package/src/shared/dev-overlay/env.d.ts +4 -0
- package/src/shared/dev-overlay/get-source-map.ts +33 -0
- package/src/shared/dev-overlay/icons.tsx +456 -0
- package/src/shared/dev-overlay/index.tsx +63 -0
- package/src/shared/dev-overlay/styles.css +292 -0
- package/src/shared/lazy.ts +52 -0
- package/src/shared/serverFunction.ts +10 -0
- package/src/virtual.d.ts +27 -0
- package/tsconfig.json +23 -0
- package/vitest.config.ts +9 -0
- package/config/index.d.ts +0 -40
- package/config/index.js +0 -304
- package/config/server-components.js +0 -106
- package/dist/client/islands.d.ts +0 -7
- package/dist/client/islands.jsx +0 -10
- package/dist/middleware/index.jsx +0 -35
- package/dist/router/index.d.ts +0 -1
- package/dist/router/index.jsx +0 -2
- package/dist/router/lazyRoute.d.ts +0 -6
- package/dist/router/lazyRoute.js +0 -53
- package/dist/runtime/server-handler.d.ts +0 -2
- package/dist/runtime/server-runtime.d.ts +0 -3
- package/dist/server/islands/index.d.ts +0 -19
- package/dist/server/islands/index.jsx +0 -62
- package/dist/server/islands/utils.d.ts +0 -1
- package/dist/server/islands/utils.jsx +0 -85
- package/dist/server/pageEvent.d.ts +0 -2
- package/dist/server/pageEvent.js +0 -56
- package/dist/server/renderAsset.d.ts +0 -2
- /package/dist/{router/FileRoutes.d.ts → router.d.ts} +0 -0
- /package/dist/shared/{clientOnly.jsx → clientOnly.js} +0 -0
- /package/dist/shared/{serverFunction.jsx → serverFunction.js} +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// @ts-ignore - seroval exports issue with NodeNext
|
|
2
|
+
import { join } from "pathe";
|
|
3
|
+
import { deserialize, toJSONAsync } from "seroval";
|
|
4
|
+
import {
|
|
5
|
+
CustomEventPlugin,
|
|
6
|
+
DOMExceptionPlugin,
|
|
7
|
+
EventPlugin,
|
|
8
|
+
FormDataPlugin,
|
|
9
|
+
HeadersPlugin,
|
|
10
|
+
ReadableStreamPlugin,
|
|
11
|
+
RequestPlugin,
|
|
12
|
+
ResponsePlugin,
|
|
13
|
+
URLPlugin,
|
|
14
|
+
URLSearchParamsPlugin,
|
|
15
|
+
} from "seroval-plugins/web";
|
|
16
|
+
import { type Component } from "solid-js";
|
|
17
|
+
|
|
18
|
+
class SerovalChunkReader {
|
|
19
|
+
reader: ReadableStreamDefaultReader<Uint8Array>;
|
|
20
|
+
buffer: Uint8Array;
|
|
21
|
+
done: boolean;
|
|
22
|
+
constructor(stream: ReadableStream<Uint8Array>) {
|
|
23
|
+
this.reader = stream.getReader();
|
|
24
|
+
this.buffer = new Uint8Array(0);
|
|
25
|
+
this.done = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async readChunk() {
|
|
29
|
+
// if there's no chunk, read again
|
|
30
|
+
const chunk = await this.reader.read();
|
|
31
|
+
if (!chunk.done) {
|
|
32
|
+
// repopulate the buffer
|
|
33
|
+
let newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
34
|
+
newBuffer.set(this.buffer);
|
|
35
|
+
newBuffer.set(chunk.value, this.buffer.length);
|
|
36
|
+
this.buffer = newBuffer;
|
|
37
|
+
} else {
|
|
38
|
+
this.done = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async next(): Promise<any> {
|
|
43
|
+
// Check if the buffer is empty
|
|
44
|
+
if (this.buffer.length === 0) {
|
|
45
|
+
// if we are already done...
|
|
46
|
+
if (this.done) {
|
|
47
|
+
return {
|
|
48
|
+
done: true,
|
|
49
|
+
value: undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Otherwise, read a new chunk
|
|
53
|
+
await this.readChunk();
|
|
54
|
+
return await this.next();
|
|
55
|
+
}
|
|
56
|
+
// Read the "byte header"
|
|
57
|
+
// The byte header tells us how big the expected data is
|
|
58
|
+
// so we know how much data we should wait before we
|
|
59
|
+
// deserialize the data
|
|
60
|
+
const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
|
|
61
|
+
const bytes = Number.parseInt(head, 16); // ;0x00000000;
|
|
62
|
+
// Check if the buffer has enough bytes to be parsed
|
|
63
|
+
while (bytes > this.buffer.length - 12) {
|
|
64
|
+
// If it's not enough, and the reader is done
|
|
65
|
+
// then the chunk is invalid.
|
|
66
|
+
if (this.done) {
|
|
67
|
+
throw new Error("Malformed server function stream.");
|
|
68
|
+
}
|
|
69
|
+
// Otherwise, we read more chunks
|
|
70
|
+
await this.readChunk();
|
|
71
|
+
}
|
|
72
|
+
// Extract the exact chunk as defined by the byte header
|
|
73
|
+
const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes));
|
|
74
|
+
// The rest goes to the buffer
|
|
75
|
+
this.buffer = this.buffer.subarray(12 + bytes);
|
|
76
|
+
|
|
77
|
+
// Deserialize the chunk
|
|
78
|
+
return {
|
|
79
|
+
done: false,
|
|
80
|
+
value: deserialize(partial),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async drain() {
|
|
85
|
+
while (true) {
|
|
86
|
+
const result = await this.next();
|
|
87
|
+
if (result.done) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function deserializeStream(id: string, response: Response) {
|
|
95
|
+
if (!response.body) {
|
|
96
|
+
throw new Error("missing body");
|
|
97
|
+
}
|
|
98
|
+
const reader = new SerovalChunkReader(response.body);
|
|
99
|
+
|
|
100
|
+
const result = await reader.next();
|
|
101
|
+
|
|
102
|
+
if (!result.done) {
|
|
103
|
+
reader.drain().then(
|
|
104
|
+
() => {
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
delete $R[id];
|
|
107
|
+
},
|
|
108
|
+
() => {
|
|
109
|
+
// no-op
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result.value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let INSTANCE = 0;
|
|
118
|
+
|
|
119
|
+
function createRequest(base: string, id: string, instance: string, options: RequestInit) {
|
|
120
|
+
return fetch(base, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
...options,
|
|
123
|
+
headers: {
|
|
124
|
+
...options.headers,
|
|
125
|
+
"X-Server-Id": id,
|
|
126
|
+
"X-Server-Instance": instance,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const plugins = [
|
|
132
|
+
CustomEventPlugin,
|
|
133
|
+
DOMExceptionPlugin,
|
|
134
|
+
EventPlugin,
|
|
135
|
+
FormDataPlugin,
|
|
136
|
+
HeadersPlugin,
|
|
137
|
+
ReadableStreamPlugin,
|
|
138
|
+
RequestPlugin,
|
|
139
|
+
ResponsePlugin,
|
|
140
|
+
URLSearchParamsPlugin,
|
|
141
|
+
URLPlugin,
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
async function fetchServerFunction(
|
|
145
|
+
base: string,
|
|
146
|
+
id: string,
|
|
147
|
+
options: Omit<RequestInit, "body">,
|
|
148
|
+
args: any[],
|
|
149
|
+
) {
|
|
150
|
+
const instance = `server-fn:${INSTANCE++}`;
|
|
151
|
+
const response = await (args.length === 0
|
|
152
|
+
? createRequest(base, id, instance, options)
|
|
153
|
+
: args.length === 1 && args[0] instanceof FormData
|
|
154
|
+
? createRequest(base, id, instance, { ...options, body: args[0] })
|
|
155
|
+
: args.length === 1 && args[0] instanceof URLSearchParams
|
|
156
|
+
? createRequest(base, id, instance, {
|
|
157
|
+
...options,
|
|
158
|
+
body: args[0],
|
|
159
|
+
headers: { ...options.headers, "Content-Type": "application/x-www-form-urlencoded" },
|
|
160
|
+
})
|
|
161
|
+
: createRequest(base, id, instance, {
|
|
162
|
+
...options,
|
|
163
|
+
body: JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))),
|
|
164
|
+
headers: { ...options.headers, "Content-Type": "application/json" },
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
response.headers.has("Location") ||
|
|
169
|
+
response.headers.has("X-Revalidate") ||
|
|
170
|
+
response.headers.has("X-Single-Flight")
|
|
171
|
+
) {
|
|
172
|
+
if (response.body) {
|
|
173
|
+
/* @ts-ignore-next-line */
|
|
174
|
+
response.customBody = () => {
|
|
175
|
+
return deserializeStream(instance, response);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return response;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const contentType = response.headers.get("Content-Type");
|
|
182
|
+
let result;
|
|
183
|
+
if (contentType && contentType.startsWith("text/plain")) {
|
|
184
|
+
result = await response.text();
|
|
185
|
+
} else if (contentType && contentType.startsWith("application/json")) {
|
|
186
|
+
result = await response.json();
|
|
187
|
+
} else {
|
|
188
|
+
result = await deserializeStream(instance, response);
|
|
189
|
+
}
|
|
190
|
+
if (response.headers.has("X-Error")) {
|
|
191
|
+
throw result;
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createServerReference(id: string) {
|
|
197
|
+
let baseURL = import.meta.env.BASE_URL ?? "/";
|
|
198
|
+
if (!baseURL.endsWith("/")) baseURL += "/";
|
|
199
|
+
|
|
200
|
+
const fn = (...args: any[]) => fetchServerFunction(`${baseURL}_server`, id, {}, args);
|
|
201
|
+
|
|
202
|
+
return new Proxy(fn, {
|
|
203
|
+
get(target, prop, receiver) {
|
|
204
|
+
if (prop === "url") {
|
|
205
|
+
return `${baseURL}_server?id=${encodeURIComponent(id)}`;
|
|
206
|
+
}
|
|
207
|
+
if (prop === "GET") {
|
|
208
|
+
return receiver.withOptions({ method: "GET" });
|
|
209
|
+
}
|
|
210
|
+
if (prop === "withOptions") {
|
|
211
|
+
const url = `${baseURL}_server?id=${encodeURIComponent(id)}`;
|
|
212
|
+
return (options: RequestInit) => {
|
|
213
|
+
const fn = async (...args: any[]) => {
|
|
214
|
+
const encodeArgs = options.method && options.method.toUpperCase() === "GET";
|
|
215
|
+
return fetchServerFunction(
|
|
216
|
+
encodeArgs
|
|
217
|
+
? url +
|
|
218
|
+
(args.length
|
|
219
|
+
? `&args=${encodeURIComponent(
|
|
220
|
+
JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))),
|
|
221
|
+
)}`
|
|
222
|
+
: "")
|
|
223
|
+
: `${baseURL}_server`,
|
|
224
|
+
id,
|
|
225
|
+
options,
|
|
226
|
+
encodeArgs ? [] : args,
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
fn.url = url;
|
|
230
|
+
return fn;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return (target as any)[prop];
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function createClientReference(Component: Component<any>, id: string) {
|
|
239
|
+
return Component;
|
|
240
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @refresh skip
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import type { Component } from "solid-js";
|
|
4
|
+
import { NoHydration, getRequestEvent, ssr } from "solid-js/web";
|
|
5
|
+
import { getSsrManifest } from "../manifest/ssr-manifest.ts";
|
|
6
|
+
|
|
7
|
+
import { TopErrorBoundary } from "../../shared/ErrorBoundary.tsx";
|
|
8
|
+
import { useAssets } from "../assets/index.ts";
|
|
9
|
+
import type { DocumentComponentProps, PageEvent } from "../types.ts";
|
|
10
|
+
|
|
11
|
+
const docType = ssr("<!DOCTYPE html>");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* Read more: https://docs.solidjs.com/solid-start/reference/server/start-server
|
|
16
|
+
*/
|
|
17
|
+
export function StartServer(props: { document: Component<DocumentComponentProps> }) {
|
|
18
|
+
const context = getRequestEvent() as PageEvent;
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
const nonce = context.nonce;
|
|
21
|
+
useAssets(context.assets, nonce);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<NoHydration>
|
|
25
|
+
{docType as unknown as any}
|
|
26
|
+
<TopErrorBoundary>
|
|
27
|
+
<props.document
|
|
28
|
+
scripts={
|
|
29
|
+
<>
|
|
30
|
+
<script
|
|
31
|
+
type="module"
|
|
32
|
+
src={getSsrManifest("client").path(import.meta.env.START_CLIENT_ENTRY)}
|
|
33
|
+
/>
|
|
34
|
+
</>
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
</TopErrorBoundary>
|
|
38
|
+
</NoHydration>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
|
|
3
|
+
import { createBaseHandler } from "../handler.ts";
|
|
4
|
+
import { getSsrManifest } from "../manifest/ssr-manifest.ts";
|
|
5
|
+
import type { FetchEvent, HandlerOptions, PageEvent } from "../types.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* Read more: https://docs.solidjs.com/solid-start/reference/server/create-handler
|
|
10
|
+
*/
|
|
11
|
+
export function createHandler(
|
|
12
|
+
fn: (context: PageEvent) => JSX.Element,
|
|
13
|
+
options?: HandlerOptions | ((context: PageEvent) => HandlerOptions),
|
|
14
|
+
) {
|
|
15
|
+
return createBaseHandler(createPageEvent, fn, options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function createPageEvent(ctx: FetchEvent) {
|
|
19
|
+
const manifest = getSsrManifest("ssr");
|
|
20
|
+
const pageEvent: PageEvent = Object.assign(ctx, {
|
|
21
|
+
manifest: "json" in manifest ? await manifest.json() : {},
|
|
22
|
+
assets: await manifest.getAssets(import.meta.env.START_CLIENT_ENTRY),
|
|
23
|
+
routes: [],
|
|
24
|
+
complete: false,
|
|
25
|
+
$islands: new Set<string>(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return pageEvent;
|
|
29
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import type { JSX } from "solid-js";
|
|
3
|
+
import type { RequestEvent } from "solid-js/web";
|
|
4
|
+
|
|
5
|
+
// export const FETCH_EVENT = "$FETCH";
|
|
6
|
+
|
|
7
|
+
export type DocumentComponentProps = {
|
|
8
|
+
assets?: JSX.Element;
|
|
9
|
+
scripts: JSX.Element;
|
|
10
|
+
children?: JSX.Element;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Asset =
|
|
14
|
+
| {
|
|
15
|
+
tag: "style";
|
|
16
|
+
attrs: JSX.StyleHTMLAttributes<HTMLStyleElement> & { key?: string };
|
|
17
|
+
children?: JSX.Element;
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
tag: "script";
|
|
21
|
+
attrs: JSX.ScriptHTMLAttributes<HTMLScriptElement> & { key?: string };
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
tag: "link";
|
|
25
|
+
attrs: JSX.LinkHTMLAttributes<HTMLLinkElement> & { key?: string };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type HandlerOptions = {
|
|
29
|
+
mode?: "sync" | "async" | "stream";
|
|
30
|
+
nonce?: string;
|
|
31
|
+
renderId?: string;
|
|
32
|
+
onCompleteAll?: (options: { write: (v: any) => void }) => void;
|
|
33
|
+
onCompleteShell?: (options: { write: (v: any) => void }) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ContextMatches = {
|
|
37
|
+
originalPath: string;
|
|
38
|
+
pattern: string;
|
|
39
|
+
path: string;
|
|
40
|
+
params: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export interface ResponseStub {
|
|
44
|
+
status?: number;
|
|
45
|
+
statusText?: string;
|
|
46
|
+
headers: Headers;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface FetchEvent {
|
|
50
|
+
request: Request;
|
|
51
|
+
response: ResponseStub;
|
|
52
|
+
clientAddress?: string;
|
|
53
|
+
locals: App.RequestEventLocals;
|
|
54
|
+
nativeEvent: H3Event;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PageEvent extends RequestEvent {
|
|
58
|
+
assets: any[];
|
|
59
|
+
routes: any[];
|
|
60
|
+
// prevUrl: string | null;
|
|
61
|
+
// $type: typeof FETCH_EVENT;
|
|
62
|
+
$islands: Set<string>;
|
|
63
|
+
complete: boolean;
|
|
64
|
+
nonce?: string;
|
|
65
|
+
// mutation: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface APIEvent extends FetchEvent {
|
|
69
|
+
params: { [key: string]: string };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type APIHandler = (event: APIEvent) => Promise<any>;
|
|
73
|
+
|
|
74
|
+
export interface ServerFunctionMeta {
|
|
75
|
+
id: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare module "solid-js/web" {
|
|
79
|
+
interface RequestEvent extends FetchEvent {
|
|
80
|
+
serverOnly?: boolean;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ResponseStub } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
// according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
|
|
4
|
+
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
5
|
+
|
|
6
|
+
export function getExpectedRedirectStatus(response: ResponseStub): number {
|
|
7
|
+
if (response.status && validRedirectStatuses.has(response.status)) {
|
|
8
|
+
return response.status;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return 302;
|
|
12
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @refresh skip
|
|
2
|
+
import { ErrorBoundary as DefaultErrorBoundary, catchError, type ParentProps } from "solid-js";
|
|
3
|
+
import { isServer } from "solid-js/web";
|
|
4
|
+
import { HttpStatusCode } from "./HttpStatusCode.ts";
|
|
5
|
+
import { DevOverlay } from "./dev-overlay/index.tsx";
|
|
6
|
+
|
|
7
|
+
export const ErrorBoundary =
|
|
8
|
+
import.meta.env.DEV && import.meta.env.START_DEV_OVERLAY
|
|
9
|
+
? (props: ParentProps) => <DevOverlay>{props.children}</DevOverlay>
|
|
10
|
+
: (props: ParentProps) => {
|
|
11
|
+
const message = isServer
|
|
12
|
+
? "500 | Internal Server Error"
|
|
13
|
+
: "Error | Uncaught Client Exception";
|
|
14
|
+
return (
|
|
15
|
+
<DefaultErrorBoundary
|
|
16
|
+
fallback={error => {
|
|
17
|
+
console.error(error);
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<span style="font-size:1.5em;text-align:center;position:fixed;left:0px;bottom:55%;width:100%;">
|
|
21
|
+
{message}
|
|
22
|
+
</span>
|
|
23
|
+
<HttpStatusCode code={500} />
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
{props.children}
|
|
29
|
+
</DefaultErrorBoundary>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const TopErrorBoundary = (props: ParentProps) => {
|
|
34
|
+
let isError = false;
|
|
35
|
+
const res = catchError(
|
|
36
|
+
() => props.children,
|
|
37
|
+
err => {
|
|
38
|
+
console.error(err);
|
|
39
|
+
isError = !!err;
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
return isError ? (
|
|
43
|
+
<>
|
|
44
|
+
<span style="font-size:1.5em;text-align:center;position:fixed;left:0px;bottom:55%;width:100%;">
|
|
45
|
+
500 | Internal Server Error
|
|
46
|
+
</span>
|
|
47
|
+
<HttpStatusCode code={500} />
|
|
48
|
+
</>
|
|
49
|
+
) : (
|
|
50
|
+
res
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @refresh skip
|
|
2
|
+
import { onCleanup } from "solid-js";
|
|
3
|
+
import { getRequestEvent, isServer } from "solid-js/web";
|
|
4
|
+
|
|
5
|
+
import type { PageEvent } from "../server/types.ts";
|
|
6
|
+
import { appendHeader, setHeader } from "../http/index.ts";
|
|
7
|
+
|
|
8
|
+
export interface HttpHeaderProps {
|
|
9
|
+
name: string;
|
|
10
|
+
value: string;
|
|
11
|
+
append?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* Read more: https://docs.solidjs.com/solid-start/reference/server/http-header
|
|
17
|
+
*/
|
|
18
|
+
export const HttpHeader = isServer
|
|
19
|
+
? (props: HttpHeaderProps) => {
|
|
20
|
+
const event = getRequestEvent() as PageEvent;
|
|
21
|
+
|
|
22
|
+
if (props.append) appendHeader(props.name, props.value);
|
|
23
|
+
else setHeader(props.name, props.value);
|
|
24
|
+
|
|
25
|
+
onCleanup(() => {
|
|
26
|
+
// @ts-expect-error
|
|
27
|
+
if (event.nativeEvent.handled || event.complete) return;
|
|
28
|
+
const value = event.response.headers.get(props.name);
|
|
29
|
+
if (!value) return;
|
|
30
|
+
if (!value.includes(", ")) {
|
|
31
|
+
if (value === props.value) event.response.headers.delete(props.name);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const values = value.split(", ");
|
|
35
|
+
const index = values.indexOf(props.value);
|
|
36
|
+
index !== -1 && values.splice(index, 1);
|
|
37
|
+
if (values.length) event.response.headers.set(props.name, values.join(","));
|
|
38
|
+
else event.response.headers.delete(props.name);
|
|
39
|
+
});
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
: (_props: HttpHeaderProps) => null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// @refresh skip
|
|
2
|
+
import { onCleanup } from "solid-js";
|
|
3
|
+
import { getRequestEvent, isServer } from "solid-js/web";
|
|
4
|
+
import type { PageEvent } from "../server/types.ts";
|
|
5
|
+
|
|
6
|
+
export interface HttpStatusCodeProps {
|
|
7
|
+
code: number;
|
|
8
|
+
text?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* Read more: https://docs.solidjs.com/solid-start/reference/server/http-status-code
|
|
14
|
+
*/
|
|
15
|
+
export const HttpStatusCode = isServer
|
|
16
|
+
? (props: HttpStatusCodeProps) => {
|
|
17
|
+
const event = getRequestEvent() as PageEvent;
|
|
18
|
+
event.response.status = props.code;
|
|
19
|
+
event.response.statusText = props.text;
|
|
20
|
+
onCleanup(
|
|
21
|
+
() =>
|
|
22
|
+
// !event.nativeEvent.handled &&
|
|
23
|
+
!event.complete && (event.response.status = 200),
|
|
24
|
+
);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
: (_props: HttpStatusCodeProps) => null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @refresh skip
|
|
2
|
+
import type { Component, ComponentProps, JSX, Setter } from "solid-js";
|
|
3
|
+
import { createMemo, createSignal, onMount, sharedConfig, splitProps, untrack } from "solid-js";
|
|
4
|
+
import { isServer } from "solid-js/web";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* Read more: https://docs.solidjs.com/solid-start/reference/client/client-only
|
|
9
|
+
*/
|
|
10
|
+
// not using Suspense
|
|
11
|
+
export default function clientOnly<T extends Component<any>>(
|
|
12
|
+
fn: () => Promise<{
|
|
13
|
+
default: T;
|
|
14
|
+
}>,
|
|
15
|
+
options: { lazy?: boolean } = {},
|
|
16
|
+
) {
|
|
17
|
+
if (isServer) return (props: ComponentProps<T> & { fallback?: JSX.Element }) => props.fallback;
|
|
18
|
+
|
|
19
|
+
const [comp, setComp] = createSignal<T>();
|
|
20
|
+
!options.lazy && load(fn, setComp);
|
|
21
|
+
return (props: ComponentProps<T>) => {
|
|
22
|
+
let Comp: T | undefined;
|
|
23
|
+
let m: boolean;
|
|
24
|
+
const [, rest] = splitProps(props, ["fallback"]);
|
|
25
|
+
options.lazy && load(fn, setComp);
|
|
26
|
+
if ((Comp = comp()) && !sharedConfig.context) return Comp(rest);
|
|
27
|
+
const [mounted, setMounted] = createSignal(!sharedConfig.context);
|
|
28
|
+
onMount(() => setMounted(true));
|
|
29
|
+
return createMemo(
|
|
30
|
+
() => (
|
|
31
|
+
(Comp = comp()), (m = mounted()), untrack(() => (Comp && m ? Comp(rest) : props.fallback))
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function load<T>(
|
|
38
|
+
fn: () => Promise<{
|
|
39
|
+
default: T;
|
|
40
|
+
}>,
|
|
41
|
+
setComp: Setter<T>,
|
|
42
|
+
) {
|
|
43
|
+
fn().then(m => setComp(() => m.default));
|
|
44
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// @refresh skip
|
|
2
|
+
import { getSingletonHighlighter, type BuiltinLanguage, type Highlighter } from "shiki";
|
|
3
|
+
import { loadWasm } from "shiki/engine/oniguruma";
|
|
4
|
+
import { createEffect, createResource, type JSX } from "solid-js";
|
|
5
|
+
|
|
6
|
+
import url from "shiki/onig.wasm?url";
|
|
7
|
+
|
|
8
|
+
import langJS from "shiki/langs/javascript.mjs";
|
|
9
|
+
import langJSX from "shiki/langs/jsx.mjs";
|
|
10
|
+
import langTSX from "shiki/langs/tsx.mjs";
|
|
11
|
+
import langTS from "shiki/langs/typescript.mjs";
|
|
12
|
+
import darkPlus from "shiki/themes/dark-plus.mjs";
|
|
13
|
+
|
|
14
|
+
let HIGHLIGHTER: Highlighter;
|
|
15
|
+
|
|
16
|
+
async function loadHighlighter() {
|
|
17
|
+
if (!HIGHLIGHTER) {
|
|
18
|
+
await loadWasm(await fetch(url));
|
|
19
|
+
HIGHLIGHTER = await getSingletonHighlighter({
|
|
20
|
+
themes: [darkPlus],
|
|
21
|
+
langs: [langJS, langJSX, langTS, langTSX],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return HIGHLIGHTER;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CodeViewProps {
|
|
28
|
+
fileName: string;
|
|
29
|
+
content: string;
|
|
30
|
+
line: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const RANGE = 8;
|
|
34
|
+
|
|
35
|
+
export function CodeView(props: CodeViewProps): JSX.Element | null {
|
|
36
|
+
const lines = () =>
|
|
37
|
+
props.content.split("\n").map((item, index) => ({
|
|
38
|
+
index: index + 1,
|
|
39
|
+
line: item,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const minLine = () => Math.max(props.line - (1 + RANGE), 0);
|
|
43
|
+
const maxLine = () => Math.min(props.line + RANGE, lines().length - 1);
|
|
44
|
+
|
|
45
|
+
let ref: HTMLDivElement | undefined;
|
|
46
|
+
|
|
47
|
+
const [data] = createResource(
|
|
48
|
+
() =>
|
|
49
|
+
lines()
|
|
50
|
+
.slice(minLine(), maxLine())
|
|
51
|
+
.map(item => item.line)
|
|
52
|
+
.join("\n"),
|
|
53
|
+
async value => {
|
|
54
|
+
const highlighter = await loadHighlighter();
|
|
55
|
+
const fileExtension = props.fileName.split(/[#?]/)[0]!.split(".").pop()?.trim();
|
|
56
|
+
let lang = fileExtension as BuiltinLanguage;
|
|
57
|
+
if (fileExtension === "mjs" || fileExtension === "cjs") {
|
|
58
|
+
lang = "js";
|
|
59
|
+
}
|
|
60
|
+
return highlighter.codeToHtml(value, {
|
|
61
|
+
theme: "dark-plus",
|
|
62
|
+
lang,
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
createEffect(() => {
|
|
68
|
+
const result = data();
|
|
69
|
+
if (ref && result) {
|
|
70
|
+
ref.innerHTML = result;
|
|
71
|
+
|
|
72
|
+
const lines = ref.querySelectorAll('span[class="line"]');
|
|
73
|
+
|
|
74
|
+
for (let i = 0, len = lines.length; i < len; i++) {
|
|
75
|
+
const el = lines[i] as HTMLElement;
|
|
76
|
+
if (props.line - minLine() - 1 === i) {
|
|
77
|
+
el.classList.add("dev-overlay-error-line");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
ref={ref}
|
|
86
|
+
class="dev-overlay-code-view"
|
|
87
|
+
style={{
|
|
88
|
+
"--dev-overlay-code-view-start": minLine() + 1,
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|