@solidjs/start 0.4.0-alpha.0 → 0.4.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/client/StartClient.tsx +4 -1
- package/config/index.js +11 -14
- package/config/server-fns-runtime.jsx +12 -0
- package/config/server-handler.js +112 -0
- package/config/server-runtime.jsx +78 -14
- package/env.d.ts +23 -0
- package/package.json +7 -6
- package/server/StartServer.tsx +12 -7
- package/server/handler.tsx +43 -26
- package/server/index.tsx +10 -1
- package/server/middleware.ts +0 -17
- package/server/page-event.tsx +26 -0
- package/server/spa/StartServer.tsx +2 -2
- package/server/types.ts +6 -25
- package/shared/FileRoutes.tsx +1 -0
- package/shared/index.tsx +0 -1
- package/shared/routes.ts +8 -2
package/client/StartClient.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
2
|
import App from "#start/app";
|
|
3
|
+
import { ErrorBoundary } from "../shared/ErrorBoundary";
|
|
3
4
|
import "./mount";
|
|
4
5
|
|
|
5
6
|
function Dummy(props) {
|
|
@@ -10,7 +11,9 @@ export function StartClient() {
|
|
|
10
11
|
return (
|
|
11
12
|
<Dummy>
|
|
12
13
|
<Dummy>
|
|
13
|
-
<
|
|
14
|
+
<ErrorBoundary>
|
|
15
|
+
<App />
|
|
16
|
+
</ErrorBoundary>
|
|
14
17
|
</Dummy>
|
|
15
18
|
</Dummy>
|
|
16
19
|
);
|
package/config/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { serverFunctions } from "@vinxi/server-functions/plugin";
|
|
2
|
+
import { serverTransform } from "@vinxi/server-functions/server";
|
|
2
3
|
import defu from "defu";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -36,14 +37,15 @@ function solidStartServerFsRouter(config) {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export function defineConfig(baseConfig = {}) {
|
|
39
|
-
let { plugins = [], start = {}, ...userConfig } = baseConfig;
|
|
40
|
+
let { plugins = [], ssr = true, start = {}, ...userConfig } = baseConfig;
|
|
40
41
|
const extensions = [...DEFAULT_EXTENSIONS, ...(start.extensions || [])];
|
|
41
42
|
start = defu(start, {
|
|
42
43
|
appRoot: "./src",
|
|
43
|
-
ssr
|
|
44
|
+
ssr,
|
|
44
45
|
islands: false,
|
|
45
46
|
serverPlugins: []
|
|
46
47
|
});
|
|
48
|
+
|
|
47
49
|
return createApp({
|
|
48
50
|
server: {
|
|
49
51
|
compressPublicAssets: {
|
|
@@ -63,14 +65,15 @@ export function defineConfig(baseConfig = {}) {
|
|
|
63
65
|
mode: "handler",
|
|
64
66
|
handler: `${start.appRoot}/entry-server.tsx`,
|
|
65
67
|
middleware: start.middleware,
|
|
66
|
-
|
|
67
|
-
? { routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }) }
|
|
68
|
-
: {}),
|
|
68
|
+
routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
|
|
69
69
|
extensions,
|
|
70
70
|
target: "server",
|
|
71
71
|
plugins: () => [
|
|
72
72
|
config("user", userConfig),
|
|
73
73
|
...plugins,
|
|
74
|
+
serverTransform({
|
|
75
|
+
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url)))
|
|
76
|
+
}),
|
|
74
77
|
start.islands ? serverComponents.server() : null,
|
|
75
78
|
solid({ ssr: true, extensions: extensions.map(ext => `.${ext}`) }),
|
|
76
79
|
config("app-server", {
|
|
@@ -138,16 +141,10 @@ export function defineConfig(baseConfig = {}) {
|
|
|
138
141
|
],
|
|
139
142
|
base: "/_build"
|
|
140
143
|
},
|
|
141
|
-
// {
|
|
142
|
-
// name: "server",
|
|
143
|
-
// mode: "handler",
|
|
144
|
-
// base: "/_server",
|
|
145
|
-
// handler: serverFunctions.handler,
|
|
146
|
-
// target: "server",
|
|
147
|
-
// ...(overrides ?? {}),
|
|
148
|
-
// plugins: () => [serverserver(), ...(overrides?.plugins?.() ?? [])],
|
|
149
|
-
// },
|
|
150
144
|
serverFunctions.router({
|
|
145
|
+
handler: normalize(fileURLToPath(new URL("./server-handler.js", import.meta.url))),
|
|
146
|
+
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.jsx", import.meta.url))),
|
|
147
|
+
// routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
|
|
151
148
|
plugins: () => [
|
|
152
149
|
config("user", userConfig),
|
|
153
150
|
...plugins,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function createServerReference(fn, id, name) {
|
|
2
|
+
return new Proxy(fn, {
|
|
3
|
+
get(target, prop, receiver) {
|
|
4
|
+
if (prop === "url") {
|
|
5
|
+
return `/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
apply(target, thisArg, args) {
|
|
9
|
+
return fn.apply(thisArg, args);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/// <reference types="vinxi/types/server" />
|
|
2
|
+
import { crossSerializeStream, fromJSON } from "seroval";
|
|
3
|
+
import { provideRequestEvent } from "solid-js/web/storage";
|
|
4
|
+
import invariant from "vinxi/lib/invariant";
|
|
5
|
+
import {
|
|
6
|
+
eventHandler,
|
|
7
|
+
getHeader,
|
|
8
|
+
getRequestURL,
|
|
9
|
+
readBody,
|
|
10
|
+
readFormData,
|
|
11
|
+
setHeader
|
|
12
|
+
} from "vinxi/server";
|
|
13
|
+
import { getFetchEvent } from "../server/middleware";
|
|
14
|
+
|
|
15
|
+
function serializeToStream(id, value) {
|
|
16
|
+
return new ReadableStream({
|
|
17
|
+
start(controller) {
|
|
18
|
+
crossSerializeStream(value, {
|
|
19
|
+
scopeId: id,
|
|
20
|
+
onSerialize(data, initial) {
|
|
21
|
+
const result = initial ? `($R["${id}"]=[],${data})` : data;
|
|
22
|
+
controller.enqueue(new TextEncoder().encode(`${result};\n`));
|
|
23
|
+
},
|
|
24
|
+
onDone() {
|
|
25
|
+
// controller.enqueue(`delete $R["${id}"];\n`);
|
|
26
|
+
controller.close();
|
|
27
|
+
},
|
|
28
|
+
onError(error) {
|
|
29
|
+
// controller.enqueue(`delete $R["${id}"];\n`);
|
|
30
|
+
controller.error(error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function handleServerFunction(event) {
|
|
38
|
+
invariant(event.method === "POST", `Invalid method ${event.method}. Expected POST.`);
|
|
39
|
+
|
|
40
|
+
const serverReference = getHeader(event, "x-server-id");
|
|
41
|
+
const instance = getHeader(event, "x-server-instance");
|
|
42
|
+
const url = getRequestURL(event);
|
|
43
|
+
let filepath, name;
|
|
44
|
+
if (serverReference) {
|
|
45
|
+
invariant(typeof serverReference === "string", "Invalid server function");
|
|
46
|
+
[filepath, name] = serverReference.split("#");
|
|
47
|
+
} else {
|
|
48
|
+
filepath = url.searchParams.get("id");
|
|
49
|
+
name = url.searchParams.get("name");
|
|
50
|
+
if (!filepath || !name) throw new Error("Invalid request");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const action = (
|
|
54
|
+
await import.meta.env.MANIFEST[import.meta.env.ROUTER_NAME].chunks[filepath].import()
|
|
55
|
+
)[name];
|
|
56
|
+
let parsed;
|
|
57
|
+
const contentType = getHeader(event, "content-type");
|
|
58
|
+
if (
|
|
59
|
+
contentType.startsWith("multipart/form-data") ||
|
|
60
|
+
contentType.startsWith("application/x-www-form-urlencoded")
|
|
61
|
+
) {
|
|
62
|
+
parsed = [await readFormData(event)];
|
|
63
|
+
} else {
|
|
64
|
+
parsed = fromJSON(await readBody(event));
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const result = await provideRequestEvent(getFetchEvent(event), () => action(...parsed));
|
|
68
|
+
|
|
69
|
+
// handle no JS success case
|
|
70
|
+
if (!instance) {
|
|
71
|
+
const isError = result instanceof Error;
|
|
72
|
+
const refererUrl = new URL(getHeader(event, "referer"));
|
|
73
|
+
if (result !== undefined)
|
|
74
|
+
refererUrl.searchParams.set(
|
|
75
|
+
"form",
|
|
76
|
+
JSON.stringify({
|
|
77
|
+
url: url.pathname + url.search,
|
|
78
|
+
result: isError ? result.message : result,
|
|
79
|
+
error: isError,
|
|
80
|
+
entries: [...parsed[0].entries()]
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
else refererUrl.searchParams.delete("form");
|
|
84
|
+
return new Response(null, {
|
|
85
|
+
status: 302,
|
|
86
|
+
headers: {
|
|
87
|
+
Location: refererUrl.pathname + refererUrl.search
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setHeader(event, "content-type", "text/javascript");
|
|
93
|
+
return serializeToStream(instance, result);
|
|
94
|
+
} catch (x) {
|
|
95
|
+
if (x instanceof Response && x.status === 302) {
|
|
96
|
+
return new Response(null, {
|
|
97
|
+
status: instance ? 204 : 302,
|
|
98
|
+
headers: {
|
|
99
|
+
Location: x.headers.get("Location")
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return new Response(serializeToStream(instance, x), {
|
|
104
|
+
status: 500,
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "text/javascript"
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default eventHandler(handleServerFunction);
|
|
@@ -1,25 +1,89 @@
|
|
|
1
|
+
import { deserialize, toJSONAsync } from "seroval";
|
|
1
2
|
import { createIslandReference } from "../server/islands";
|
|
2
3
|
|
|
3
|
-
async function
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
async function deserializeStream(id, response) {
|
|
5
|
+
if (!response.body) {
|
|
6
|
+
throw new Error("missing body");
|
|
7
|
+
}
|
|
8
|
+
const reader = response.body.getReader();
|
|
9
|
+
|
|
10
|
+
async function pop() {
|
|
11
|
+
const result = await reader.read();
|
|
12
|
+
if (!result.done) {
|
|
13
|
+
const serialized = new TextDecoder().decode(result.value);
|
|
14
|
+
const splits = serialized.split("\n");
|
|
15
|
+
for (const split of splits) {
|
|
16
|
+
if (split !== "") {
|
|
17
|
+
deserialize(split);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
await pop();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = await reader.read();
|
|
25
|
+
if (result.done) {
|
|
26
|
+
throw new Error("Unexpected end of body");
|
|
27
|
+
}
|
|
28
|
+
const serialized = new TextDecoder().decode(result.value);
|
|
29
|
+
let pending = true;
|
|
30
|
+
let revived;
|
|
31
|
+
const splits = serialized.split("\n");
|
|
32
|
+
for (const split of splits) {
|
|
33
|
+
if (split !== "") {
|
|
34
|
+
const current = deserialize(split);
|
|
35
|
+
if (pending) {
|
|
36
|
+
revived = current;
|
|
37
|
+
pending = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pop().then(
|
|
43
|
+
() => {
|
|
44
|
+
delete self.$R[id];
|
|
45
|
+
},
|
|
46
|
+
() => {
|
|
47
|
+
// no-op
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return revived;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let INSTANCE = 0;
|
|
55
|
+
|
|
56
|
+
function createRequest(base, id, instance, body, contentType) {
|
|
57
|
+
return fetch(base, {
|
|
6
58
|
method: "POST",
|
|
7
59
|
headers: {
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
60
|
+
"x-server-id": id,
|
|
61
|
+
"x-server-instance": instance,
|
|
62
|
+
...(contentType ? { "Content-Type": contentType } : {})
|
|
11
63
|
},
|
|
12
|
-
body
|
|
64
|
+
body
|
|
13
65
|
});
|
|
66
|
+
}
|
|
14
67
|
|
|
15
|
-
|
|
68
|
+
async function fetchServerFunction(base, id, args) {
|
|
69
|
+
const instance = `server-fn:${INSTANCE++}`;
|
|
70
|
+
const response = await (args.length === 1 && args[0] instanceof FormData
|
|
71
|
+
? createRequest(base, id, instance, args[0])
|
|
72
|
+
: createRequest(base, id, instance, JSON.stringify(await toJSONAsync(args)), "application/json"));
|
|
16
73
|
|
|
17
|
-
if (response.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
74
|
+
if (response.headers.get("Location")) throw response;
|
|
75
|
+
if (response.headers.get("X-Revalidate")) {
|
|
76
|
+
/* ts-ignore-next-line */
|
|
77
|
+
response.customBody = () => {
|
|
78
|
+
return deserializeStream(instance, response);
|
|
79
|
+
}
|
|
80
|
+
throw response;
|
|
81
|
+
}
|
|
82
|
+
const result = deserializeStream(instance, response);
|
|
83
|
+
if (response.ok) {
|
|
84
|
+
return result;
|
|
22
85
|
}
|
|
86
|
+
throw result;
|
|
23
87
|
}
|
|
24
88
|
|
|
25
89
|
export function createServerReference(fn, id, name) {
|
|
@@ -30,7 +94,7 @@ export function createServerReference(fn, id, name) {
|
|
|
30
94
|
}
|
|
31
95
|
},
|
|
32
96
|
apply(target, thisArg, args) {
|
|
33
|
-
return
|
|
97
|
+
return fetchServerFunction("/_server", `${id}#${name}`, args);
|
|
34
98
|
}
|
|
35
99
|
});
|
|
36
100
|
}
|
package/env.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// This file is an augmentation to the built-in ImportMeta interface
|
|
2
|
+
// Thus cannot contain any top-level imports
|
|
3
|
+
// <https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation>
|
|
4
|
+
|
|
5
|
+
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
|
6
|
+
|
|
7
|
+
interface ImportMetaEnv extends Record<`VITE_${string}`, any>, SolidStartMetaEnv {
|
|
8
|
+
BASE_URL: string;
|
|
9
|
+
MODE: string;
|
|
10
|
+
DEV: boolean;
|
|
11
|
+
PROD: boolean;
|
|
12
|
+
SSR: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SolidStartMetaEnv {
|
|
16
|
+
START_ISLANDS_ROUTER: string;
|
|
17
|
+
START_INDEX_HTML: string;
|
|
18
|
+
START_ENTRY_CLIENT: string;
|
|
19
|
+
START_SSR: string;
|
|
20
|
+
START_ISLANDS: string;
|
|
21
|
+
START_ENTRY_SERVER: string;
|
|
22
|
+
DEBUG: string;
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/start",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"publishConfig": {
|
|
@@ -38,14 +38,15 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"solid-js": "^1.8.
|
|
42
|
-
"vinxi": "0.0.
|
|
41
|
+
"solid-js": "^1.8.7",
|
|
42
|
+
"vinxi": "0.0.48"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@vinxi/plugin-directives": "0.0.
|
|
46
|
-
"@vinxi/server-
|
|
47
|
-
"@vinxi/server-
|
|
45
|
+
"@vinxi/plugin-directives": "0.0.44",
|
|
46
|
+
"@vinxi/server-components": "0.0.44",
|
|
47
|
+
"@vinxi/server-functions": "0.0.45",
|
|
48
48
|
"defu": "^6.1.2",
|
|
49
|
+
"seroval": "^0.14.1",
|
|
49
50
|
"vite-plugin-inspect": "^0.7.33",
|
|
50
51
|
"vite-plugin-solid": "^2.7.0"
|
|
51
52
|
}
|
package/server/StartServer.tsx
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
2
|
import App from "#start/app";
|
|
3
|
-
import {
|
|
3
|
+
import type { Component } from "solid-js";
|
|
4
|
+
import { Hydration, HydrationScript, NoHydration, getRequestEvent, ssr } from "solid-js/web";
|
|
5
|
+
import { ErrorBoundary } from "../shared/ErrorBoundary";
|
|
4
6
|
import { renderAsset } from "./renderAsset";
|
|
7
|
+
import type { DocumentComponentProps } from "./types";
|
|
5
8
|
|
|
6
9
|
const docType = ssr("<!DOCTYPE html>");
|
|
7
10
|
|
|
8
|
-
export function StartServer(props) {
|
|
9
|
-
const context =
|
|
10
|
-
const parsed = new URL(context.request.url);
|
|
11
|
-
const path = parsed.pathname + parsed.search;
|
|
11
|
+
export function StartServer(props: { document: Component<DocumentComponentProps> }) {
|
|
12
|
+
const context = getRequestEvent() as any;
|
|
12
13
|
return (
|
|
13
14
|
<NoHydration>
|
|
14
15
|
{docType as unknown as any}
|
|
@@ -31,10 +32,14 @@ export function StartServer(props) {
|
|
|
31
32
|
>
|
|
32
33
|
{!import.meta.env.START_ISLANDS ? (
|
|
33
34
|
<Hydration>
|
|
34
|
-
<
|
|
35
|
+
<ErrorBoundary>
|
|
36
|
+
<App />
|
|
37
|
+
</ErrorBoundary>
|
|
35
38
|
</Hydration>
|
|
36
39
|
) : (
|
|
37
|
-
<
|
|
40
|
+
<ErrorBoundary>
|
|
41
|
+
<App />
|
|
42
|
+
</ErrorBoundary>
|
|
38
43
|
)}
|
|
39
44
|
</props.document>
|
|
40
45
|
</NoHydration>
|
package/server/handler.tsx
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { renderToStream } from "solid-js/web";
|
|
2
2
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
eventHandler,
|
|
5
|
+
EventHandlerObject,
|
|
6
|
+
EventHandlerRequest,
|
|
7
|
+
H3Event,
|
|
8
|
+
setHeader,
|
|
9
|
+
setResponseStatus
|
|
10
|
+
} from "vinxi/server";
|
|
5
11
|
import { apiRoutes } from "../shared/routes";
|
|
6
12
|
import { getFetchEvent } from "./middleware";
|
|
7
|
-
import {
|
|
13
|
+
import { createPageEvent } from "./page-event";
|
|
14
|
+
import { FetchEvent, PageEvent } from "./types";
|
|
8
15
|
|
|
9
16
|
export function createHandler(
|
|
10
17
|
fn: (context: PageEvent) => unknown,
|
|
@@ -12,6 +19,8 @@ export function createHandler(
|
|
|
12
19
|
nonce?: string;
|
|
13
20
|
renderId?: string;
|
|
14
21
|
timeoutMs?: number;
|
|
22
|
+
onCompleteAll?: (options: { write: (v: any) => void }) => void;
|
|
23
|
+
onCompleteShell?: (options: { write: (v: any) => void }) => void;
|
|
15
24
|
createPageEvent?: (event: FetchEvent) => Promise<PageEvent>;
|
|
16
25
|
onRequest?: EventHandlerObject["onRequest"];
|
|
17
26
|
onBeforeResponse?: EventHandlerObject["onBeforeResponse"];
|
|
@@ -38,9 +47,24 @@ export function createHandler(
|
|
|
38
47
|
|
|
39
48
|
// render stream
|
|
40
49
|
const context = await createPageEvent(event);
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
50
|
+
let cloned = { ...options };
|
|
51
|
+
if (cloned.onCompleteAll) {
|
|
52
|
+
const og = cloned.onCompleteAll;
|
|
53
|
+
cloned.onCompleteAll = options => {
|
|
54
|
+
handleStreamCompleteRedirect(context)(options);
|
|
55
|
+
og(options);
|
|
56
|
+
};
|
|
57
|
+
} else cloned.onCompleteAll = handleStreamCompleteRedirect(context);
|
|
58
|
+
if (cloned.onCompleteShell) {
|
|
59
|
+
const og = cloned.onCompleteShell;
|
|
60
|
+
cloned.onCompleteShell = options => {
|
|
61
|
+
handleShellCompleteRedirect(context, e)();
|
|
62
|
+
og(options);
|
|
63
|
+
};
|
|
64
|
+
} else cloned.onCompleteShell = handleShellCompleteRedirect(context, e);
|
|
65
|
+
const stream = renderToStream(() => fn(context), cloned);
|
|
66
|
+
if (context.response && context.response.headers.get("Location")) {
|
|
67
|
+
return event.redirect(context.response.headers.get("Location"));
|
|
44
68
|
}
|
|
45
69
|
return { pipeTo: stream.pipeTo };
|
|
46
70
|
});
|
|
@@ -48,25 +72,18 @@ export function createHandler(
|
|
|
48
72
|
});
|
|
49
73
|
}
|
|
50
74
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
...(await clientManifest.inputs[clientManifest.handler].assets()),
|
|
60
|
-
...(import.meta.env.START_ISLANDS
|
|
61
|
-
? await serverManifest.inputs[serverManifest.handler].assets()
|
|
62
|
-
: [])
|
|
63
|
-
],
|
|
64
|
-
routes: createRoutes(),
|
|
65
|
-
prevUrl: prevPath || "",
|
|
66
|
-
mutation: mutation,
|
|
67
|
-
$type: FETCH_EVENT,
|
|
68
|
-
$islands: new Set<string>()
|
|
69
|
-
});
|
|
75
|
+
function handleShellCompleteRedirect(context: PageEvent, e: H3Event<EventHandlerRequest>) {
|
|
76
|
+
return () => {
|
|
77
|
+
if (context.response && context.response.headers.get("Location")) {
|
|
78
|
+
setResponseStatus(e, 302);
|
|
79
|
+
setHeader(e, "Location", context.response.headers.get("Location"));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
70
83
|
|
|
71
|
-
|
|
84
|
+
function handleStreamCompleteRedirect(context: PageEvent) {
|
|
85
|
+
return ({ write }: { write: (html: string) => void }) => {
|
|
86
|
+
const to = context.response && context.response.headers.get("Location");
|
|
87
|
+
to && write(`<script>window.location="${to}"</script>`);
|
|
88
|
+
};
|
|
72
89
|
}
|
package/server/index.tsx
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export { StartServer } from "./StartServer";
|
|
2
|
-
export { createHandler } from "./handler";
|
|
3
2
|
export * from "./middleware";
|
|
4
3
|
export type { DocumentComponentProps } from "./types";
|
|
4
|
+
import { createPageEvent } from "./page-event";
|
|
5
|
+
import { createHandler as createBaseHandler } from "./handler";
|
|
6
|
+
import type { PageEvent } from "./types";
|
|
7
|
+
|
|
8
|
+
export function createHandler(
|
|
9
|
+
fn: (context: PageEvent) => unknown,
|
|
10
|
+
options: Parameters<typeof createBaseHandler>[1] = {}
|
|
11
|
+
) {
|
|
12
|
+
return createBaseHandler(fn, { ...options, createPageEvent });
|
|
13
|
+
}
|
package/server/middleware.ts
CHANGED
|
@@ -52,28 +52,11 @@ export function getFetchEvent(h3Event: H3Event): FetchEvent {
|
|
|
52
52
|
return h3Event[fetchEventSymbol];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export type Middleware = (input: MiddlewareInput) => MiddlewareFn;
|
|
56
|
-
/** Input parameters for to an Exchange factory function. */
|
|
57
|
-
|
|
58
|
-
export interface MiddlewareInput {
|
|
59
|
-
forward: MiddlewareFn;
|
|
60
|
-
}
|
|
61
55
|
/** Function responsible for receiving an observable [operation]{@link Operation} and returning a [result]{@link OperationResult}. */
|
|
62
56
|
|
|
63
57
|
export type MiddlewareFn = (event: FetchEvent) => Promise<unknown> | unknown;
|
|
64
58
|
/** This composes an array of Exchanges into a single ExchangeIO function */
|
|
65
59
|
|
|
66
|
-
export const composeMiddleware =
|
|
67
|
-
(exchanges: Middleware[]) =>
|
|
68
|
-
({ forward }: MiddlewareInput) =>
|
|
69
|
-
exchanges.reduceRight(
|
|
70
|
-
(forward, exchange) =>
|
|
71
|
-
exchange({
|
|
72
|
-
forward
|
|
73
|
-
}),
|
|
74
|
-
forward
|
|
75
|
-
);
|
|
76
|
-
|
|
77
60
|
type RequestMiddleware = (event: FetchEvent) => Response | Promise<Response> | void | Promise<void>;
|
|
78
61
|
|
|
79
62
|
type ResponseMiddleware = (
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createRoutes } from "../shared/FileRoutes";
|
|
2
|
+
import { FetchEvent, PageEvent } from "./types";
|
|
3
|
+
|
|
4
|
+
export async function createPageEvent(ctx: FetchEvent) {
|
|
5
|
+
const clientManifest = import.meta.env.MANIFEST["client"];
|
|
6
|
+
const serverManifest = import.meta.env.MANIFEST["ssr"];
|
|
7
|
+
ctx.setResponseHeader("Content-Type", "text/html");
|
|
8
|
+
// const prevPath = ctx.request.headers.get("x-solid-referrer");
|
|
9
|
+
// const mutation = ctx.request.headers.get("x-solid-mutation") === "true";
|
|
10
|
+
const pageEvent: PageEvent = Object.assign(ctx, {
|
|
11
|
+
manifest: await clientManifest.json(),
|
|
12
|
+
assets: [
|
|
13
|
+
...(await clientManifest.inputs[clientManifest.handler].assets()),
|
|
14
|
+
...(import.meta.env.START_ISLANDS
|
|
15
|
+
? await serverManifest.inputs[serverManifest.handler].assets()
|
|
16
|
+
: [])
|
|
17
|
+
],
|
|
18
|
+
routes: createRoutes(),
|
|
19
|
+
// prevUrl: prevPath || "",
|
|
20
|
+
// mutation: mutation,
|
|
21
|
+
// $type: FETCH_EVENT,
|
|
22
|
+
$islands: new Set<string>()
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return pageEvent;
|
|
26
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
|
-
import { NoHydration, ssr } from "solid-js/web";
|
|
2
|
+
import { NoHydration, getRequestEvent, ssr } from "solid-js/web";
|
|
3
3
|
import { renderAsset } from "../renderAsset";
|
|
4
4
|
|
|
5
5
|
const docType = ssr("<!DOCTYPE html>");
|
|
6
6
|
|
|
7
7
|
export function StartServer(props) {
|
|
8
|
-
const context =
|
|
8
|
+
const context = getRequestEvent() as any;
|
|
9
9
|
return (
|
|
10
10
|
<NoHydration>
|
|
11
11
|
{docType as unknown as any}
|
package/server/types.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { JSX } from "solid-js";
|
|
2
2
|
|
|
3
|
-
export const FETCH_EVENT = "$FETCH";
|
|
3
|
+
// export const FETCH_EVENT = "$FETCH";
|
|
4
4
|
|
|
5
5
|
export type DocumentComponentProps = {
|
|
6
|
-
assets:
|
|
7
|
-
scripts:
|
|
6
|
+
assets: JSX.Element;
|
|
7
|
+
scripts: JSX.Element;
|
|
8
8
|
children: JSX.Element;
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -15,25 +15,6 @@ export type ContextMatches = {
|
|
|
15
15
|
params: unknown;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
type TagDescription = {
|
|
19
|
-
tag: string;
|
|
20
|
-
props: Record<string, unknown>;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type RouterContext = {
|
|
24
|
-
// router matches;
|
|
25
|
-
matches: ContextMatches[][];
|
|
26
|
-
// redirected url
|
|
27
|
-
url: string;
|
|
28
|
-
|
|
29
|
-
// server route fragments
|
|
30
|
-
replaceOutletId: string;
|
|
31
|
-
newOutletId: string;
|
|
32
|
-
partial: boolean;
|
|
33
|
-
nextRoute: any;
|
|
34
|
-
prevRoute: any;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
18
|
export interface FetchEvent {
|
|
38
19
|
request: Request;
|
|
39
20
|
clientAddress: string;
|
|
@@ -50,9 +31,9 @@ export interface PageEvent extends FetchEvent {
|
|
|
50
31
|
manifest: any;
|
|
51
32
|
assets: any;
|
|
52
33
|
routes: any[];
|
|
53
|
-
prevUrl: string | null;
|
|
54
|
-
$type: typeof FETCH_EVENT;
|
|
34
|
+
// prevUrl: string | null;
|
|
35
|
+
// $type: typeof FETCH_EVENT;
|
|
55
36
|
$islands: Set<string>;
|
|
56
|
-
mutation: boolean;
|
|
37
|
+
// mutation: boolean;
|
|
57
38
|
response?: Response;
|
|
58
39
|
}
|
package/shared/FileRoutes.tsx
CHANGED
package/shared/index.tsx
CHANGED
package/shared/routes.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import fileRoutes, {
|
|
1
|
+
import fileRoutes, { RouteModule } from "vinxi/routes";
|
|
2
|
+
|
|
3
|
+
interface Route {
|
|
4
|
+
path: string;
|
|
5
|
+
type: "api" | "page";
|
|
6
|
+
children?: Route[];
|
|
7
|
+
}
|
|
2
8
|
|
|
3
9
|
declare module "vinxi/routes" {
|
|
4
10
|
export interface Register {
|
|
@@ -10,7 +16,7 @@ declare module "vinxi/routes" {
|
|
|
10
16
|
}
|
|
11
17
|
}
|
|
12
18
|
|
|
13
|
-
const defineRoutes = (fileRoutes:
|
|
19
|
+
const defineRoutes = (fileRoutes: RouteModule[]) => {
|
|
14
20
|
function processRoute(routes: Route[], route: Route, id: string, full: string) {
|
|
15
21
|
const parentRoute = Object.values(routes).find(o => {
|
|
16
22
|
// if (o.id.endsWith("/index")) {
|