@solidjs/start 1.0.0-rc.0 → 1.0.0
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/fs-router.js +15 -10
- package/config/index.js +13 -4
- package/dist/router/FileRoutes.js +1 -1
- package/dist/router/routes.d.ts +1 -0
- package/dist/router/routes.js +3 -3
- package/dist/runtime/server-handler.js +11 -3
- package/dist/runtime/server-runtime.js +11 -7
- package/dist/server/StartServer.jsx +8 -7
- package/dist/server/fetchEvent.js +2 -2
- package/dist/server/handler.d.ts +6 -1
- package/dist/server/handler.js +34 -19
- package/dist/server/pageEvent.js +2 -0
- package/dist/server/renderAsset.d.ts +1 -1
- package/dist/server/renderAsset.jsx +2 -2
- package/dist/server/spa/handler.js +1 -0
- package/dist/server/types.d.ts +1 -0
- package/dist/shared/HttpHeader.jsx +1 -1
- package/dist/shared/HttpStatusCode.jsx +1 -1
- package/package.json +4 -4
package/config/fs-router.js
CHANGED
|
@@ -24,6 +24,7 @@ export class SolidStartClientFileRouter extends BaseFileSystemRouter {
|
|
|
24
24
|
|
|
25
25
|
if (src.endsWith(".md") || src.endsWith(".mdx")) {
|
|
26
26
|
return {
|
|
27
|
+
page: true,
|
|
27
28
|
$component: {
|
|
28
29
|
src: src,
|
|
29
30
|
pick: ["$css"]
|
|
@@ -35,10 +36,11 @@ export class SolidStartClientFileRouter extends BaseFileSystemRouter {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const [_, exports] = analyzeModule(src);
|
|
38
|
-
const hasDefault = exports.find(e => e.n === "default");
|
|
39
|
-
const hasRouteConfig = exports.find(e => e.n === "route");
|
|
39
|
+
const hasDefault = !!exports.find(e => e.n === "default");
|
|
40
|
+
const hasRouteConfig = !!exports.find(e => e.n === "route");
|
|
40
41
|
if (hasDefault) {
|
|
41
42
|
return {
|
|
43
|
+
page: true,
|
|
42
44
|
$component: {
|
|
43
45
|
src: src,
|
|
44
46
|
pick: ["default", "$css"]
|
|
@@ -93,6 +95,7 @@ export class SolidStartServerFileRouter extends BaseFileSystemRouter {
|
|
|
93
95
|
let path = this.toPath(src);
|
|
94
96
|
if (src.endsWith(".md") || src.endsWith(".mdx")) {
|
|
95
97
|
return {
|
|
98
|
+
page: true,
|
|
96
99
|
$component: {
|
|
97
100
|
src: src,
|
|
98
101
|
pick: ["$css"]
|
|
@@ -105,16 +108,18 @@ export class SolidStartServerFileRouter extends BaseFileSystemRouter {
|
|
|
105
108
|
|
|
106
109
|
const [_, exports] = analyzeModule(src);
|
|
107
110
|
const hasRouteConfig = exports.find(e => e.n === "route");
|
|
108
|
-
const hasDefault = exports.find(e => e.n === "default");
|
|
109
|
-
const hasAPIRoutes = exports.find(exp => HTTP_METHODS.includes(exp.n));
|
|
111
|
+
const hasDefault = !!exports.find(e => e.n === "default");
|
|
112
|
+
const hasAPIRoutes = !!exports.find(exp => HTTP_METHODS.includes(exp.n));
|
|
110
113
|
if (hasDefault || hasAPIRoutes) {
|
|
111
114
|
return {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
page: hasDefault,
|
|
116
|
+
$component:
|
|
117
|
+
!this.config.dataOnly && hasDefault
|
|
118
|
+
? {
|
|
119
|
+
src: src,
|
|
120
|
+
pick: ["default", "$css"]
|
|
121
|
+
}
|
|
122
|
+
: undefined,
|
|
118
123
|
$$route: hasRouteConfig
|
|
119
124
|
? {
|
|
120
125
|
src: src,
|
package/config/index.js
CHANGED
|
@@ -30,7 +30,8 @@ function solidStartServerFsRouter(config) {
|
|
|
30
30
|
new SolidStartServerFileRouter(
|
|
31
31
|
{
|
|
32
32
|
dir: resolve.absolute(config.dir, router.root),
|
|
33
|
-
extensions: config.extensions ?? DEFAULT_EXTENSIONS
|
|
33
|
+
extensions: config.extensions ?? DEFAULT_EXTENSIONS,
|
|
34
|
+
dataOnly: config.dataOnly
|
|
34
35
|
},
|
|
35
36
|
router,
|
|
36
37
|
app
|
|
@@ -90,7 +91,7 @@ export function defineConfig(baseConfig = {}) {
|
|
|
90
91
|
},
|
|
91
92
|
handler: `${start.appRoot}/entry-server${entryExtension}`,
|
|
92
93
|
middleware: start.middleware,
|
|
93
|
-
routes: solidStartServerFsRouter({ dir: routeDir, extensions }),
|
|
94
|
+
routes: solidStartServerFsRouter({ dir: routeDir, extensions, dataOnly: !start.ssr }),
|
|
94
95
|
extensions,
|
|
95
96
|
target: "server",
|
|
96
97
|
plugins: async () => {
|
|
@@ -133,6 +134,9 @@ export function defineConfig(baseConfig = {}) {
|
|
|
133
134
|
},
|
|
134
135
|
cacheDir: "node_modules/.vinxi/server",
|
|
135
136
|
define: {
|
|
137
|
+
"import.meta.env.START_APP": JSON.stringify(
|
|
138
|
+
`${start.appRoot}/app${entryExtension}`
|
|
139
|
+
),
|
|
136
140
|
"import.meta.env.START_ISLANDS": JSON.stringify(start.experimental.islands),
|
|
137
141
|
"import.meta.env.SSR": JSON.stringify(true),
|
|
138
142
|
"import.meta.env.START_SSR": JSON.stringify(start.ssr),
|
|
@@ -200,6 +204,9 @@ export function defineConfig(baseConfig = {}) {
|
|
|
200
204
|
},
|
|
201
205
|
cacheDir: "node_modules/.vinxi/client",
|
|
202
206
|
define: {
|
|
207
|
+
"import.meta.env.START_APP": JSON.stringify(
|
|
208
|
+
`${start.appRoot}/app${entryExtension}`
|
|
209
|
+
),
|
|
203
210
|
"import.meta.env.START_ISLANDS": JSON.stringify(start.experimental.islands),
|
|
204
211
|
"import.meta.env.SSR": JSON.stringify(false),
|
|
205
212
|
"import.meta.env.START_SSR": JSON.stringify(start.ssr),
|
|
@@ -211,7 +218,6 @@ export function defineConfig(baseConfig = {}) {
|
|
|
211
218
|
];
|
|
212
219
|
}
|
|
213
220
|
},
|
|
214
|
-
|
|
215
221
|
{
|
|
216
222
|
name: "server-fns",
|
|
217
223
|
type: "http",
|
|
@@ -221,7 +227,7 @@ export function defineConfig(baseConfig = {}) {
|
|
|
221
227
|
),
|
|
222
228
|
middleware: start.middleware,
|
|
223
229
|
target: "server",
|
|
224
|
-
routes: solidStartServerFsRouter({ dir: routeDir, extensions }),
|
|
230
|
+
routes: solidStartServerFsRouter({ dir: routeDir, extensions, dataOnly: !start.ssr }),
|
|
225
231
|
plugins: async () => {
|
|
226
232
|
const userConfig =
|
|
227
233
|
typeof vite === "function" ? await vite({ router: "server-function" }) : { ...vite };
|
|
@@ -261,6 +267,9 @@ export function defineConfig(baseConfig = {}) {
|
|
|
261
267
|
}
|
|
262
268
|
},
|
|
263
269
|
define: {
|
|
270
|
+
"import.meta.env.START_APP": JSON.stringify(
|
|
271
|
+
`${start.appRoot}/app${entryExtension}`
|
|
272
|
+
),
|
|
264
273
|
"import.meta.env.START_ISLANDS": JSON.stringify(start.experimental.islands),
|
|
265
274
|
"import.meta.env.SSR": JSON.stringify(true),
|
|
266
275
|
"import.meta.env.START_SSR": JSON.stringify(start.ssr),
|
|
@@ -10,7 +10,7 @@ export function createRoutes() {
|
|
|
10
10
|
...(route.$$route ? route.$$route.require().route.info : {}),
|
|
11
11
|
filesystem: true
|
|
12
12
|
},
|
|
13
|
-
component: lazyRoute(route.$component, import.meta.env.START_ISLANDS
|
|
13
|
+
component: route.$component && lazyRoute(route.$component, import.meta.env.START_ISLANDS
|
|
14
14
|
? import.meta.env.MANIFEST["ssr"]
|
|
15
15
|
: import.meta.env.MANIFEST["client"], import.meta.env.MANIFEST["ssr"]),
|
|
16
16
|
children: route.children ? route.children.map(createRoute) : undefined
|
package/dist/router/routes.d.ts
CHANGED
package/dist/router/routes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRouter } from "radix3";
|
|
2
2
|
import fileRoutes from "vinxi/routes";
|
|
3
|
-
export const pageRoutes = defineRoutes(fileRoutes.filter(o => o
|
|
3
|
+
export const pageRoutes = defineRoutes(fileRoutes.filter(o => o.page));
|
|
4
4
|
function defineRoutes(fileRoutes) {
|
|
5
5
|
function processRoute(routes, route, id, full) {
|
|
6
6
|
const parentRoute = Object.values(routes).find(o => {
|
|
@@ -21,7 +21,7 @@ function defineRoutes(fileRoutes) {
|
|
|
21
21
|
}
|
|
22
22
|
export function matchAPIRoute(path, method) {
|
|
23
23
|
const match = router.lookup(path);
|
|
24
|
-
if (match) {
|
|
24
|
+
if (match && match.route) {
|
|
25
25
|
const handler = match.route[`$${method}`];
|
|
26
26
|
if (handler === undefined)
|
|
27
27
|
return;
|
|
@@ -38,7 +38,7 @@ const router = createRouter({
|
|
|
38
38
|
routes: fileRoutes.reduce((memo, route) => {
|
|
39
39
|
if (!containsHTTP(route))
|
|
40
40
|
return memo;
|
|
41
|
-
let path = route.path.replace(/\([^)/]+\)/g, "").replace(/\*([^/]*)/g, (_, m) => `**:${m}`);
|
|
41
|
+
let path = route.path.replace(/\/\([^)/]+\)/g, "").replace(/\([^)/]+\)/g, "").replace(/\*([^/]*)/g, (_, m) => `**:${m}`);
|
|
42
42
|
if (/:[^/]*\?/g.test(path)) {
|
|
43
43
|
throw new Error(`Optional parameters are not supported in API routes: ${path}`);
|
|
44
44
|
}
|
|
@@ -9,11 +9,17 @@ import { eventHandler, setHeader, setResponseStatus } from "vinxi/http";
|
|
|
9
9
|
import invariant from "vinxi/lib/invariant";
|
|
10
10
|
import { cloneEvent, getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
|
|
11
11
|
import { createPageEvent } from "../server/pageEvent";
|
|
12
|
+
import { getExpectedRedirectStatus } from "../server/handler";
|
|
12
13
|
function createChunk(data) {
|
|
13
|
-
const
|
|
14
|
+
const encodeData = new TextEncoder().encode(data);
|
|
15
|
+
const bytes = encodeData.length;
|
|
14
16
|
const baseHex = bytes.toString(16);
|
|
15
17
|
const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit
|
|
16
|
-
|
|
18
|
+
const head = new TextEncoder().encode(`;0x${totalHex};`);
|
|
19
|
+
const chunk = new Uint8Array(12 + bytes);
|
|
20
|
+
chunk.set(head);
|
|
21
|
+
chunk.set(encodeData, 12);
|
|
22
|
+
return chunk;
|
|
17
23
|
}
|
|
18
24
|
function serializeToStream(id, value) {
|
|
19
25
|
return new ReadableStream({
|
|
@@ -150,11 +156,13 @@ async function handleServerFunction(h3Event) {
|
|
|
150
156
|
if (!instance) {
|
|
151
157
|
const isError = result instanceof Error;
|
|
152
158
|
let redirectUrl = new URL(request.headers.get("referer")).toString();
|
|
159
|
+
let statusCode = 302;
|
|
153
160
|
if (result instanceof Response && result.headers.has("Location")) {
|
|
154
161
|
redirectUrl = new URL(result.headers.get("Location"), new URL(request.url).origin + import.meta.env.SERVER_BASE_URL).toString();
|
|
162
|
+
statusCode = getExpectedRedirectStatus(result);
|
|
155
163
|
}
|
|
156
164
|
return new Response(null, {
|
|
157
|
-
status:
|
|
165
|
+
status: statusCode,
|
|
158
166
|
headers: {
|
|
159
167
|
Location: redirectUrl,
|
|
160
168
|
...(result
|
|
@@ -7,7 +7,7 @@ class SerovalChunkReader {
|
|
|
7
7
|
done;
|
|
8
8
|
constructor(stream) {
|
|
9
9
|
this.reader = stream.getReader();
|
|
10
|
-
this.buffer =
|
|
10
|
+
this.buffer = new Uint8Array(0);
|
|
11
11
|
this.done = false;
|
|
12
12
|
}
|
|
13
13
|
async readChunk() {
|
|
@@ -15,7 +15,10 @@ class SerovalChunkReader {
|
|
|
15
15
|
const chunk = await this.reader.read();
|
|
16
16
|
if (!chunk.done) {
|
|
17
17
|
// repopulate the buffer
|
|
18
|
-
|
|
18
|
+
let newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
|
|
19
|
+
newBuffer.set(this.buffer);
|
|
20
|
+
newBuffer.set(chunk.value, this.buffer.length);
|
|
21
|
+
this.buffer = newBuffer;
|
|
19
22
|
}
|
|
20
23
|
else {
|
|
21
24
|
this.done = true;
|
|
@@ -23,7 +26,7 @@ class SerovalChunkReader {
|
|
|
23
26
|
}
|
|
24
27
|
async next() {
|
|
25
28
|
// Check if the buffer is empty
|
|
26
|
-
if (this.buffer ===
|
|
29
|
+
if (this.buffer.length === 0) {
|
|
27
30
|
// if we are already done...
|
|
28
31
|
if (this.done) {
|
|
29
32
|
return {
|
|
@@ -39,7 +42,8 @@ class SerovalChunkReader {
|
|
|
39
42
|
// The byte header tells us how big the expected data is
|
|
40
43
|
// so we know how much data we should wait before we
|
|
41
44
|
// deserialize the data
|
|
42
|
-
const
|
|
45
|
+
const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
|
|
46
|
+
const bytes = Number.parseInt(head, 16); // ;0x00000000;
|
|
43
47
|
// Check if the buffer has enough bytes to be parsed
|
|
44
48
|
while (bytes > this.buffer.length - 12) {
|
|
45
49
|
// If it's not enough, and the reader is done
|
|
@@ -51,9 +55,9 @@ class SerovalChunkReader {
|
|
|
51
55
|
await this.readChunk();
|
|
52
56
|
}
|
|
53
57
|
// Extract the exact chunk as defined by the byte header
|
|
54
|
-
const partial = this.buffer.
|
|
58
|
+
const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes));
|
|
55
59
|
// The rest goes to the buffer
|
|
56
|
-
this.buffer = this.buffer.
|
|
60
|
+
this.buffer = this.buffer.subarray(12 + bytes);
|
|
57
61
|
// Deserialize the chunk
|
|
58
62
|
return {
|
|
59
63
|
done: false,
|
|
@@ -163,7 +167,7 @@ export function createServerReference(fn, id, name) {
|
|
|
163
167
|
const fn = async (...args) => {
|
|
164
168
|
const encodeArgs = options.method && options.method.toUpperCase() === "GET";
|
|
165
169
|
return fetchServerFunction(encodeArgs
|
|
166
|
-
? url + (args.length ? `&args=${JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins })))}` : "")
|
|
170
|
+
? url + (args.length ? `&args=${encodeURIComponent(JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))))}` : "")
|
|
167
171
|
: `${baseURL}/_server`, `${id}#${name}`, options, encodeArgs ? [] : args);
|
|
168
172
|
};
|
|
169
173
|
fn.url = url;
|
|
@@ -26,8 +26,9 @@ export function StartServer(props) {
|
|
|
26
26
|
const context = getRequestEvent();
|
|
27
27
|
// @ts-ignore
|
|
28
28
|
const nonce = context.nonce;
|
|
29
|
-
let assets
|
|
29
|
+
let assets;
|
|
30
30
|
Promise.resolve().then(async () => {
|
|
31
|
+
let assetPromises = [];
|
|
31
32
|
// @ts-ignore
|
|
32
33
|
if (context.router && context.router.matches) {
|
|
33
34
|
// @ts-ignore
|
|
@@ -40,25 +41,25 @@ export function StartServer(props) {
|
|
|
40
41
|
const segment = matched[i];
|
|
41
42
|
const part = import.meta.env.MANIFEST[import.meta.env.START_ISLANDS ? "ssr" : "client"]
|
|
42
43
|
.inputs[segment["$component"].src];
|
|
43
|
-
|
|
44
|
-
assets.push.apply(assets, asset);
|
|
44
|
+
assetPromises.push(part.assets());
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
else
|
|
47
|
+
else if (import.meta.env.DEV)
|
|
48
48
|
console.warn("No route matched for preloading js assets");
|
|
49
49
|
}
|
|
50
|
+
assets = await Promise.all(assetPromises).then(a =>
|
|
50
51
|
// dedupe assets
|
|
51
|
-
|
|
52
|
+
[...new Map(a.flat().map(item => [item.attrs.key, item])).values()].filter(asset => import.meta.env.START_ISLANDS
|
|
52
53
|
? false
|
|
53
54
|
: asset.attrs.rel === "modulepreload" &&
|
|
54
|
-
!context.assets.find((a) => a.attrs.key === asset.attrs.key));
|
|
55
|
+
!context.assets.find((a) => a.attrs.key === asset.attrs.key)));
|
|
55
56
|
});
|
|
56
57
|
useAssets(() => (assets.length ? assets.map(m => renderAsset(m)) : undefined));
|
|
57
58
|
return (<NoHydration>
|
|
58
59
|
{docType}
|
|
59
60
|
<props.document assets={<>
|
|
60
61
|
<HydrationScript />
|
|
61
|
-
{context.assets.map((m) => renderAsset(m))}
|
|
62
|
+
{context.assets.map((m) => renderAsset(m, nonce))}
|
|
62
63
|
</>} scripts={nonce ? (<>
|
|
63
64
|
<script nonce={nonce} innerHTML={`window.manifest = ${JSON.stringify(context.manifest)}`}/>
|
|
64
65
|
<script type="module" nonce={nonce} async src={import.meta.env.MANIFEST["client"].inputs[import.meta.env.MANIFEST["client"].handler].output.path}/>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendResponseHeader, getRequestIP, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getWebRequest, removeResponseHeader,
|
|
1
|
+
import { appendResponseHeader, getRequestIP, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getWebRequest, removeResponseHeader, setResponseHeader, setResponseStatus } from "vinxi/http";
|
|
2
2
|
const fetchEventSymbol = Symbol("fetchEvent");
|
|
3
3
|
export function createFetchEvent(event) {
|
|
4
4
|
return {
|
|
@@ -23,7 +23,7 @@ export function getFetchEvent(h3Event) {
|
|
|
23
23
|
}
|
|
24
24
|
export function mergeResponseHeaders(h3Event, headers) {
|
|
25
25
|
for (const [key, value] of headers.entries()) {
|
|
26
|
-
|
|
26
|
+
appendResponseHeader(h3Event, key, value);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
class HeaderProxy {
|
package/dist/server/handler.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import type { FetchEvent, HandlerOptions, PageEvent } from "./types";
|
|
1
|
+
import type { FetchEvent, HandlerOptions, PageEvent, ResponseStub } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Checks if user has set a redirect status in the response.
|
|
4
|
+
* If not, falls back to the 302 (temporary redirect)
|
|
5
|
+
*/
|
|
6
|
+
export declare function getExpectedRedirectStatus(response: ResponseStub): number;
|
|
2
7
|
export declare function createBaseHandler(fn: (context: PageEvent) => unknown, createPageEvent: (event: FetchEvent) => Promise<PageEvent>, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>)): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
3
8
|
export declare function createHandler(fn: (context: PageEvent) => unknown, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>)): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
package/dist/server/handler.js
CHANGED
|
@@ -5,6 +5,18 @@ import { eventHandler, sendRedirect, setHeader, setResponseStatus } from "vinxi/
|
|
|
5
5
|
import { matchAPIRoute } from "../router/routes";
|
|
6
6
|
import { getFetchEvent } from "./fetchEvent";
|
|
7
7
|
import { createPageEvent } from "./pageEvent";
|
|
8
|
+
// according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
|
|
9
|
+
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
10
|
+
/**
|
|
11
|
+
* Checks if user has set a redirect status in the response.
|
|
12
|
+
* If not, falls back to the 302 (temporary redirect)
|
|
13
|
+
*/
|
|
14
|
+
export function getExpectedRedirectStatus(response) {
|
|
15
|
+
if (response.status && validRedirectStatuses.has(response.status)) {
|
|
16
|
+
return response.status;
|
|
17
|
+
}
|
|
18
|
+
return 302;
|
|
19
|
+
}
|
|
8
20
|
export function createBaseHandler(fn, createPageEvent, options = {}) {
|
|
9
21
|
return eventHandler({
|
|
10
22
|
handler: (e) => {
|
|
@@ -27,47 +39,48 @@ export function createBaseHandler(fn, createPageEvent, options = {}) {
|
|
|
27
39
|
}
|
|
28
40
|
// render
|
|
29
41
|
const context = await createPageEvent(event);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const mode = options.mode || "stream";
|
|
42
|
+
const resolvedOptions = typeof options == "function" ? await options(context) : { ...options };
|
|
43
|
+
const mode = resolvedOptions.mode || "stream";
|
|
33
44
|
// @ts-ignore
|
|
34
|
-
if (
|
|
35
|
-
context.nonce =
|
|
45
|
+
if (resolvedOptions.nonce)
|
|
46
|
+
context.nonce = resolvedOptions.nonce;
|
|
36
47
|
if (mode === "sync" || !import.meta.env.START_SSR) {
|
|
37
48
|
const html = renderToString(() => {
|
|
38
49
|
sharedConfig.context.event = context;
|
|
39
50
|
return fn(context);
|
|
40
|
-
},
|
|
51
|
+
}, resolvedOptions);
|
|
52
|
+
context.complete = true;
|
|
41
53
|
if (context.response && context.response.headers.get("Location")) {
|
|
42
|
-
|
|
54
|
+
const status = getExpectedRedirectStatus(context.response);
|
|
55
|
+
return sendRedirect(e, context.response.headers.get("Location"), status);
|
|
43
56
|
}
|
|
44
57
|
return html;
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cloned.onCompleteAll = options => {
|
|
59
|
+
if (resolvedOptions.onCompleteAll) {
|
|
60
|
+
const og = resolvedOptions.onCompleteAll;
|
|
61
|
+
resolvedOptions.onCompleteAll = options => {
|
|
50
62
|
handleStreamCompleteRedirect(context)(options);
|
|
51
63
|
og(options);
|
|
52
64
|
};
|
|
53
65
|
}
|
|
54
66
|
else
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
const og =
|
|
58
|
-
|
|
67
|
+
resolvedOptions.onCompleteAll = handleStreamCompleteRedirect(context);
|
|
68
|
+
if (resolvedOptions.onCompleteShell) {
|
|
69
|
+
const og = resolvedOptions.onCompleteShell;
|
|
70
|
+
resolvedOptions.onCompleteShell = options => {
|
|
59
71
|
handleShellCompleteRedirect(context, e)();
|
|
60
72
|
og(options);
|
|
61
73
|
};
|
|
62
74
|
}
|
|
63
75
|
else
|
|
64
|
-
|
|
76
|
+
resolvedOptions.onCompleteShell = handleShellCompleteRedirect(context, e);
|
|
65
77
|
const stream = renderToStream(() => {
|
|
66
78
|
sharedConfig.context.event = context;
|
|
67
79
|
return fn(context);
|
|
68
|
-
},
|
|
80
|
+
}, resolvedOptions);
|
|
69
81
|
if (context.response && context.response.headers.get("Location")) {
|
|
70
|
-
|
|
82
|
+
const status = getExpectedRedirectStatus(context.response);
|
|
83
|
+
return sendRedirect(e, context.response.headers.get("Location"), status);
|
|
71
84
|
}
|
|
72
85
|
if (mode === "async")
|
|
73
86
|
return stream;
|
|
@@ -82,13 +95,15 @@ export function createBaseHandler(fn, createPageEvent, options = {}) {
|
|
|
82
95
|
function handleShellCompleteRedirect(context, e) {
|
|
83
96
|
return () => {
|
|
84
97
|
if (context.response && context.response.headers.get("Location")) {
|
|
85
|
-
|
|
98
|
+
const status = getExpectedRedirectStatus(context.response);
|
|
99
|
+
setResponseStatus(e, status);
|
|
86
100
|
setHeader(e, "Location", context.response.headers.get("Location"));
|
|
87
101
|
}
|
|
88
102
|
};
|
|
89
103
|
}
|
|
90
104
|
function handleStreamCompleteRedirect(context) {
|
|
91
105
|
return ({ write }) => {
|
|
106
|
+
context.complete = true;
|
|
92
107
|
const to = context.response && context.response.headers.get("Location");
|
|
93
108
|
to && write(`<script>window.location="${to}"</script>`);
|
|
94
109
|
};
|
package/dist/server/pageEvent.js
CHANGED
|
@@ -26,6 +26,7 @@ export async function createPageEvent(ctx) {
|
|
|
26
26
|
manifest: await clientManifest.json(),
|
|
27
27
|
assets: [
|
|
28
28
|
...(await clientManifest.inputs[clientManifest.handler].assets()),
|
|
29
|
+
...(import.meta.env.DEV ? await clientManifest.inputs[import.meta.env.START_APP].assets() : []),
|
|
29
30
|
...(import.meta.env.START_ISLANDS
|
|
30
31
|
? (await serverManifest.inputs[serverManifest.handler].assets()).filter(s => s.attrs.rel !== "modulepreload")
|
|
31
32
|
: [])
|
|
@@ -37,6 +38,7 @@ export async function createPageEvent(ctx) {
|
|
|
37
38
|
// prevUrl: prevPath || "",
|
|
38
39
|
// mutation: mutation,
|
|
39
40
|
// $type: FETCH_EVENT,
|
|
41
|
+
complete: false,
|
|
40
42
|
$islands: new Set()
|
|
41
43
|
});
|
|
42
44
|
return pageEvent;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { Asset } from "./types";
|
|
2
|
-
export declare function renderAsset(asset: Asset): any;
|
|
2
|
+
export declare function renderAsset(asset: Asset, nonce?: string): any;
|
|
@@ -7,7 +7,7 @@ const assetMap = {
|
|
|
7
7
|
</script>) : null;
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
|
-
export function renderAsset(asset) {
|
|
10
|
+
export function renderAsset(asset, nonce) {
|
|
11
11
|
let { tag, attrs: { key, ...attrs } = { key: undefined }, children } = asset;
|
|
12
|
-
return assetMap[tag]({ attrs, key, children });
|
|
12
|
+
return assetMap[tag]({ attrs: { ...attrs, nonce }, key, children });
|
|
13
13
|
}
|
|
@@ -8,6 +8,7 @@ export async function createPageEvent(ctx) {
|
|
|
8
8
|
manifest: await clientManifest.json(),
|
|
9
9
|
assets: [...(await clientManifest.inputs[clientManifest.handler].assets())],
|
|
10
10
|
routes: [],
|
|
11
|
+
complete: false,
|
|
11
12
|
// prevUrl: "",
|
|
12
13
|
// mutation: false,
|
|
13
14
|
// $type: FETCH_EVENT,
|
package/dist/server/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const HttpHeader = isServer
|
|
|
9
9
|
else
|
|
10
10
|
event.response.headers.set(props.name, props.value);
|
|
11
11
|
onCleanup(() => {
|
|
12
|
-
if (event.nativeEvent.handled)
|
|
12
|
+
if (event.nativeEvent.handled || event.complete)
|
|
13
13
|
return;
|
|
14
14
|
const value = event.response.headers.get(props.name);
|
|
15
15
|
if (!value)
|
|
@@ -6,7 +6,7 @@ export const HttpStatusCode = isServer
|
|
|
6
6
|
const event = getRequestEvent();
|
|
7
7
|
event.response.status = props.code;
|
|
8
8
|
event.response.statusText = props.text;
|
|
9
|
-
onCleanup(() => !event.nativeEvent.handled && (event.response.status = 200));
|
|
9
|
+
onCleanup(() => !event.nativeEvent.handled && !event.complete && (event.response.status = 200));
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
12
|
: (_props) => null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/start",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"solid-js": "^1.8.
|
|
55
|
-
"vinxi": "^0.3.
|
|
54
|
+
"solid-js": "^1.8.17",
|
|
55
|
+
"vinxi": "^0.3.11",
|
|
56
56
|
"typescript": "^5.4.2"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"source-map-js": "^1.0.2",
|
|
71
71
|
"terracotta": "^1.0.4",
|
|
72
72
|
"vite-plugin-inspect": "^0.7.33",
|
|
73
|
-
"vite-plugin-solid": "
|
|
73
|
+
"vite-plugin-solid": "^2.10.2"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"typecheck": "tsc --noEmit",
|