solidstep 0.2.0 → 0.3.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/README.md +10 -0
- package/package.json +70 -70
- package/server.d.ts +1 -1
- package/server.d.ts.map +1 -1
- package/server.js +380 -401
- package/utils/cache.js +3 -3
- package/utils/path-router.d.ts +70 -0
- package/utils/path-router.d.ts.map +1 -0
- package/utils/path-router.js +97 -0
- package/utils/server-action.server.d.ts.map +1 -1
- package/utils/server-action.server.js +2 -1
package/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eventHandler, toWebRequest,
|
|
1
|
+
import { eventHandler, toWebRequest, setHeader, setResponseStatus, } from 'vinxi/http';
|
|
2
2
|
import { getManifest } from 'vinxi/manifest';
|
|
3
3
|
import { generateHydrationScript, renderToString } from 'solid-js/web';
|
|
4
4
|
import fileRoutes, {} from 'vinxi/routes';
|
|
@@ -8,58 +8,80 @@ import { handleServerFunction } from './utils/server-action.server';
|
|
|
8
8
|
import { readFile } from 'node:fs/promises';
|
|
9
9
|
import { dirname } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
import { createNode, insertRoute, matchRoute, } from './utils/path-router';
|
|
12
|
+
// Module cache for dynamically imported modules
|
|
13
|
+
const moduleCache = new Map();
|
|
14
|
+
const getCachedModule = async (importFn) => {
|
|
15
|
+
const key = importFn.src;
|
|
16
|
+
if (moduleCache.has(key)) {
|
|
17
|
+
return moduleCache.get(key);
|
|
18
|
+
}
|
|
19
|
+
const module = await importFn.import();
|
|
20
|
+
moduleCache.set(key, module);
|
|
21
|
+
return module;
|
|
22
|
+
};
|
|
23
|
+
const isPageFile = (file) => file.endsWith('page.tsx') ||
|
|
24
|
+
file.endsWith('page.jsx') ||
|
|
25
|
+
file.endsWith('page.ts') ||
|
|
26
|
+
file.endsWith('page.js');
|
|
15
27
|
const isRouteFile = (file) => file.endsWith('route.ts') || file.endsWith('route.js');
|
|
16
|
-
const
|
|
28
|
+
const getNormalizedPath = (path, clean) => {
|
|
29
|
+
const segments = path.split('/').slice(2);
|
|
30
|
+
if (clean)
|
|
31
|
+
return `/${segments.filter((s) => !s.startsWith('(')).join('/')}`;
|
|
32
|
+
return `/${segments.join('/')}`;
|
|
33
|
+
};
|
|
17
34
|
const createRouteManifest = async () => {
|
|
18
|
-
const
|
|
35
|
+
const rootNode = createNode();
|
|
19
36
|
const allRoutes = [];
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
37
|
+
const layoutsMap = new Map();
|
|
38
|
+
const loadingPagesMap = new Map();
|
|
39
|
+
const errorPagesMap = new Map();
|
|
40
|
+
const groupsMap = new Map();
|
|
24
41
|
let notFoundPage;
|
|
25
42
|
for (const fileRoute of fileRoutes) {
|
|
26
43
|
if (fileRoute.type === 'route') {
|
|
27
44
|
allRoutes.push(fileRoute);
|
|
28
45
|
}
|
|
29
46
|
if (fileRoute.type === 'layout') {
|
|
30
|
-
|
|
47
|
+
const path = getNormalizedPath(fileRoute.path);
|
|
48
|
+
layoutsMap.set(path, fileRoute);
|
|
31
49
|
}
|
|
32
50
|
if (fileRoute.type === 'not-found') {
|
|
33
51
|
notFoundPage = fileRoute;
|
|
34
52
|
}
|
|
35
53
|
if (fileRoute.type === 'loading') {
|
|
36
|
-
|
|
54
|
+
const path = getNormalizedPath(fileRoute.path);
|
|
55
|
+
loadingPagesMap.set(path, fileRoute);
|
|
37
56
|
}
|
|
38
57
|
if (fileRoute.type === 'error') {
|
|
39
|
-
|
|
58
|
+
const path = getNormalizedPath(fileRoute.path);
|
|
59
|
+
errorPagesMap.set(path, fileRoute);
|
|
40
60
|
}
|
|
41
61
|
if (fileRoute.type === 'group') {
|
|
42
|
-
|
|
62
|
+
const parentPath = fileRoute.parent
|
|
63
|
+
? getNormalizedPath(fileRoute.parent)
|
|
64
|
+
: '';
|
|
65
|
+
const existing = groupsMap.get(parentPath) || [];
|
|
66
|
+
existing.push(fileRoute);
|
|
67
|
+
groupsMap.set(parentPath, existing);
|
|
43
68
|
}
|
|
44
69
|
}
|
|
70
|
+
const regex = /\?(?:pick=.*)*/g;
|
|
45
71
|
for (const fileRoute of allRoutes) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const regex = /\?(?:pick=.*)*/g;
|
|
72
|
+
const routePath = getNormalizedPath(fileRoute.path, true);
|
|
73
|
+
const routeMatcherPath = getNormalizedPath(fileRoute.path);
|
|
49
74
|
const src = fileRoute.$handler?.src.replace(regex, '');
|
|
50
75
|
if (src && isPageFile(src)) {
|
|
51
|
-
const loadingPage =
|
|
52
|
-
|
|
53
|
-
return path === routePath;
|
|
54
|
-
});
|
|
55
|
-
const matchedGroups = allGroups.filter(route => {
|
|
56
|
-
const parentPath = route.parent ? `/${route.parent.split('/').slice(2).map(parseSegment).join('/')}` : '';
|
|
57
|
-
return parentPath === routePath;
|
|
58
|
-
});
|
|
76
|
+
const loadingPage = loadingPagesMap.get(routeMatcherPath);
|
|
77
|
+
const matchedGroups = groupsMap.get(routePath);
|
|
59
78
|
const groups = {};
|
|
60
79
|
if (matchedGroups && matchedGroups.length > 0) {
|
|
61
80
|
for (const group of matchedGroups) {
|
|
62
|
-
const groupName = group.path
|
|
81
|
+
const groupName = group.path
|
|
82
|
+
.split('/')
|
|
83
|
+
.filter((s) => !s.startsWith('('))
|
|
84
|
+
.at(-1);
|
|
63
85
|
if (!groupName)
|
|
64
86
|
continue;
|
|
65
87
|
groups[groupName] = {
|
|
@@ -69,20 +91,18 @@ const createRouteManifest = async () => {
|
|
|
69
91
|
};
|
|
70
92
|
}
|
|
71
93
|
}
|
|
94
|
+
const segments = routeMatcherPath.split('/').filter(Boolean);
|
|
72
95
|
let errorPage;
|
|
73
96
|
const layouts = [];
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
// We need to traverse from root to leaf to build layouts order correctly?
|
|
98
|
+
// Original code: i = segments.length down to 0. unshift matches.
|
|
99
|
+
// i=length: /a/b/c. i=0: /.
|
|
100
|
+
for (let i = segments.length; i >= 0; i--) {
|
|
101
|
+
const path = i === 0 ? '/' : `/${segments.slice(0, i).join('/')}`;
|
|
76
102
|
if (!errorPage) {
|
|
77
|
-
errorPage =
|
|
78
|
-
const routePath = `/${route.path.split('/').slice(2).map(parseSegment).join('/')}`;
|
|
79
|
-
return routePath === path;
|
|
80
|
-
});
|
|
103
|
+
errorPage = errorPagesMap.get(path);
|
|
81
104
|
}
|
|
82
|
-
const layout =
|
|
83
|
-
const routePath = `/${route.path.split('/').slice(2).map(parseSegment).join('/')}`;
|
|
84
|
-
return routePath === path;
|
|
85
|
-
});
|
|
105
|
+
const layout = layoutsMap.get(path);
|
|
86
106
|
if (layout) {
|
|
87
107
|
layouts.unshift({
|
|
88
108
|
manifestPath: layout.path,
|
|
@@ -92,7 +112,7 @@ const createRouteManifest = async () => {
|
|
|
92
112
|
});
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
|
-
|
|
115
|
+
const entry = {
|
|
96
116
|
type: 'page',
|
|
97
117
|
mainPage: {
|
|
98
118
|
manifestPath: fileRoute.path,
|
|
@@ -101,69 +121,50 @@ const createRouteManifest = async () => {
|
|
|
101
121
|
generateMeta: fileRoute.$generateMeta,
|
|
102
122
|
options: fileRoute.$options,
|
|
103
123
|
},
|
|
104
|
-
loadingPage: loadingPage
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
loadingPage: loadingPage
|
|
125
|
+
? {
|
|
126
|
+
page: loadingPage.$component,
|
|
127
|
+
generateMeta: loadingPage.$generateMeta,
|
|
128
|
+
manifestPath: loadingPage.path,
|
|
129
|
+
}
|
|
130
|
+
: undefined,
|
|
131
|
+
errorPage: errorPage
|
|
132
|
+
? {
|
|
133
|
+
page: errorPage.$component,
|
|
134
|
+
generateMeta: errorPage.$generateMeta,
|
|
135
|
+
manifestPath: errorPage.path,
|
|
136
|
+
}
|
|
137
|
+
: undefined,
|
|
138
|
+
notFoundPage: routePath === '/' && notFoundPage
|
|
139
|
+
? {
|
|
140
|
+
page: notFoundPage.$component,
|
|
141
|
+
generateMeta: notFoundPage.$generateMeta,
|
|
142
|
+
manifestPath: notFoundPage.path,
|
|
143
|
+
}
|
|
144
|
+
: undefined,
|
|
119
145
|
layouts: layouts,
|
|
120
146
|
groups: groups,
|
|
121
147
|
};
|
|
148
|
+
insertRoute(rootNode, routePath, entry);
|
|
122
149
|
}
|
|
123
150
|
else if (src && isRouteFile(src)) {
|
|
124
|
-
|
|
151
|
+
const entry = {
|
|
125
152
|
type: 'route',
|
|
153
|
+
routePath,
|
|
126
154
|
handler: fileRoute.$handler,
|
|
127
155
|
manifestPath: fileRoute.path,
|
|
128
156
|
};
|
|
157
|
+
insertRoute(rootNode, routePath, entry);
|
|
129
158
|
}
|
|
130
159
|
}
|
|
131
|
-
return
|
|
160
|
+
return rootNode;
|
|
132
161
|
};
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const routeSeg = routeSegments[i];
|
|
140
|
-
const urlSeg = urlSegments[i];
|
|
141
|
-
const isDynamic = routeSeg.startsWith('[') && routeSeg.endsWith(']');
|
|
142
|
-
if (isDynamic) {
|
|
143
|
-
if (routeSeg.includes('...')) {
|
|
144
|
-
// Catch-all parameter
|
|
145
|
-
const isCatchAll = routeSeg.startsWith('[[') && routeSeg.endsWith(']]');
|
|
146
|
-
const paramName = routeSeg.slice(isCatchAll ? 5 : 4, isCatchAll ? -2 : -1);
|
|
147
|
-
params[paramName] = urlSegments.slice(i);
|
|
148
|
-
break; // No more segments to match
|
|
149
|
-
}
|
|
150
|
-
const paramName = routeSeg.slice(1, -1);
|
|
151
|
-
params[paramName] = urlSeg;
|
|
152
|
-
}
|
|
153
|
-
else if (routeSeg !== urlSeg) {
|
|
154
|
-
matched = false;
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (matched)
|
|
159
|
-
return { route, params };
|
|
160
|
-
};
|
|
161
|
-
const template = `
|
|
162
|
-
<!DOCTYPE html>
|
|
163
|
-
<html lang="en">
|
|
164
|
-
<head><!--app-head--></head>
|
|
165
|
-
<!--app-body-->
|
|
166
|
-
</html>
|
|
162
|
+
const template = `
|
|
163
|
+
<!DOCTYPE html>
|
|
164
|
+
<html lang="en">
|
|
165
|
+
<head><!--app-head--></head>
|
|
166
|
+
<!--app-body-->
|
|
167
|
+
</html>
|
|
167
168
|
`;
|
|
168
169
|
const generateHtmlHead = (meta) => {
|
|
169
170
|
const head = Object.entries(meta)
|
|
@@ -177,7 +178,9 @@ const generateHtmlHead = (meta) => {
|
|
|
177
178
|
.join(' ');
|
|
178
179
|
return `<meta ${attrs}>`;
|
|
179
180
|
}
|
|
180
|
-
if (value.type === 'link' ||
|
|
181
|
+
if (value.type === 'link' ||
|
|
182
|
+
value.type === 'style' ||
|
|
183
|
+
value.type === 'script') {
|
|
181
184
|
const attrs = Object.entries(value.attributes)
|
|
182
185
|
.map(([attrKey, attrValue]) => `${attrKey}="${attrValue}"`)
|
|
183
186
|
.join(' ');
|
|
@@ -188,45 +191,11 @@ const generateHtmlHead = (meta) => {
|
|
|
188
191
|
.join('\n');
|
|
189
192
|
return head;
|
|
190
193
|
};
|
|
191
|
-
const
|
|
192
|
-
// Set status code
|
|
193
|
-
res.statusCode = response.status;
|
|
194
|
-
// Set headers
|
|
195
|
-
response.headers.forEach((value, key) => {
|
|
196
|
-
res.setHeader(key, value);
|
|
197
|
-
});
|
|
198
|
-
// Stream the body
|
|
199
|
-
if (response.body) {
|
|
200
|
-
const reader = response.body.getReader();
|
|
201
|
-
const push = async () => {
|
|
202
|
-
const { done, value } = await reader.read();
|
|
203
|
-
if (done) {
|
|
204
|
-
res.end();
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
res.write(Buffer.from(value));
|
|
208
|
-
await push();
|
|
209
|
-
};
|
|
210
|
-
await push();
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
const text = await response.text();
|
|
214
|
-
res.end(text);
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonce, }) => {
|
|
194
|
+
const render = async ({ toRender, entry, routeParams, searchParams, req, pageOptions, cspNonce, error, }) => {
|
|
218
195
|
const url = new URL(req.url);
|
|
219
196
|
const path = url.pathname;
|
|
220
197
|
const cachedEntry = getCache(path);
|
|
221
198
|
if (cachedEntry && toRender === 'main') {
|
|
222
|
-
const { options } = entry.mainPage.options ? await entry.mainPage.options.import() : { options: {} };
|
|
223
|
-
if (options?.responseHeaders) {
|
|
224
|
-
const headers = options.responseHeaders;
|
|
225
|
-
const event = getEvent();
|
|
226
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
227
|
-
setResponseHeader(event, key, value);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
199
|
return {
|
|
231
200
|
rendered: cachedEntry.rendered,
|
|
232
201
|
documentMeta: cachedEntry.documentMeta,
|
|
@@ -234,7 +203,6 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
234
203
|
loaderData: cachedEntry.loaderData,
|
|
235
204
|
};
|
|
236
205
|
}
|
|
237
|
-
let cachingOptions = undefined;
|
|
238
206
|
let meta = {};
|
|
239
207
|
const loaderData = {};
|
|
240
208
|
const clientManifest = getManifest('client');
|
|
@@ -242,12 +210,14 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
242
210
|
const compose = entry.layouts.reduceRight((children, layout, index) => async () => {
|
|
243
211
|
const moduleSrc = `${layout.layout.src}&pick=$css`;
|
|
244
212
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const { generateMeta: generateMetaPage } = layout.generateMeta
|
|
213
|
+
assets.push(...moduleAssets);
|
|
214
|
+
const { default: layoutModule } = await getCachedModule(layout.layout);
|
|
215
|
+
const { loader: layoutLoader } = layout.loader
|
|
216
|
+
? await getCachedModule(layout.loader)
|
|
217
|
+
: { loader: null };
|
|
218
|
+
const { generateMeta: generateMetaPage } = layout.generateMeta
|
|
219
|
+
? await getCachedModule(layout.generateMeta)
|
|
220
|
+
: { generateMeta: null };
|
|
251
221
|
let data = {};
|
|
252
222
|
if (generateMetaPage) {
|
|
253
223
|
const metaData = await generateMetaPage({
|
|
@@ -257,7 +227,7 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
257
227
|
if (metaData) {
|
|
258
228
|
meta = {
|
|
259
229
|
...meta,
|
|
260
|
-
...metaData
|
|
230
|
+
...metaData,
|
|
261
231
|
};
|
|
262
232
|
}
|
|
263
233
|
}
|
|
@@ -275,11 +245,11 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
275
245
|
slotPromises.push((async () => {
|
|
276
246
|
const moduleSrc = `${group.page.src}&pick=$css`;
|
|
277
247
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
248
|
+
assets.push(...moduleAssets);
|
|
249
|
+
const { default: groupPage } = await getCachedModule(group.page);
|
|
250
|
+
const { loader: groupLoader } = group.loader
|
|
251
|
+
? await getCachedModule(group.loader)
|
|
252
|
+
: { loader: null };
|
|
283
253
|
let data = {};
|
|
284
254
|
if (groupLoader) {
|
|
285
255
|
const result = await groupLoader.loader(req);
|
|
@@ -289,7 +259,7 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
289
259
|
slots[groupName.replace('@', '')] = () => groupPage({
|
|
290
260
|
routeParams,
|
|
291
261
|
searchParams,
|
|
292
|
-
loaderData: data
|
|
262
|
+
loaderData: data,
|
|
293
263
|
});
|
|
294
264
|
})());
|
|
295
265
|
}
|
|
@@ -303,7 +273,7 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
303
273
|
slots: slots,
|
|
304
274
|
locals: {
|
|
305
275
|
cspNonce: cspNonce,
|
|
306
|
-
}
|
|
276
|
+
},
|
|
307
277
|
});
|
|
308
278
|
}, async () => {
|
|
309
279
|
const pageToRender = toRender === 'loading'
|
|
@@ -315,23 +285,14 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
315
285
|
: entry.mainPage;
|
|
316
286
|
const moduleSrc = `${pageToRender.page.src}&pick=$css`;
|
|
317
287
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const { generateMeta } = pageToRender.generateMeta
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
cachingOptions = options.cache;
|
|
327
|
-
}
|
|
328
|
-
if (options?.responseHeaders) {
|
|
329
|
-
const headers = options.responseHeaders;
|
|
330
|
-
const event = getEvent();
|
|
331
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
332
|
-
setResponseHeader(event, key, value);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
288
|
+
assets.push(...moduleAssets);
|
|
289
|
+
const { default: page } = await getCachedModule(pageToRender.page);
|
|
290
|
+
const { loader: pageLoader } = pageToRender.loader
|
|
291
|
+
? await getCachedModule(pageToRender.loader)
|
|
292
|
+
: { loader: null };
|
|
293
|
+
const { generateMeta } = pageToRender.generateMeta
|
|
294
|
+
? await getCachedModule(pageToRender.generateMeta)
|
|
295
|
+
: { generateMeta: null };
|
|
335
296
|
let data = {};
|
|
336
297
|
if (pageLoader) {
|
|
337
298
|
const result = await pageLoader.loader(req);
|
|
@@ -346,23 +307,27 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
346
307
|
if (metaData) {
|
|
347
308
|
meta = {
|
|
348
309
|
...meta,
|
|
349
|
-
...metaData
|
|
310
|
+
...metaData,
|
|
350
311
|
};
|
|
351
312
|
}
|
|
352
313
|
}
|
|
353
|
-
|
|
314
|
+
const props = {
|
|
354
315
|
routeParams,
|
|
355
316
|
searchParams,
|
|
356
317
|
loaderData: data,
|
|
357
318
|
locals: {
|
|
358
319
|
cspNonce: cspNonce,
|
|
359
|
-
}
|
|
360
|
-
}
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
if (toRender === 'error') {
|
|
323
|
+
props.error = error;
|
|
324
|
+
}
|
|
325
|
+
return () => page(props);
|
|
361
326
|
});
|
|
362
327
|
const composed = await compose();
|
|
363
328
|
const rendered = await renderToString(() => composed());
|
|
364
329
|
if (toRender === 'main') {
|
|
365
|
-
const options =
|
|
330
|
+
const options = pageOptions?.cache;
|
|
366
331
|
setCache(path, {
|
|
367
332
|
rendered: rendered,
|
|
368
333
|
documentMeta: meta,
|
|
@@ -377,10 +342,12 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
377
342
|
loaderData: loaderData,
|
|
378
343
|
};
|
|
379
344
|
};
|
|
380
|
-
let routeManifest =
|
|
345
|
+
let routeManifest = null;
|
|
381
346
|
const hydrationScript = ({ nonce, }) => {
|
|
382
347
|
const script = generateHydrationScript();
|
|
383
|
-
return nonce
|
|
348
|
+
return nonce
|
|
349
|
+
? script.replace('<script', `<script nonce="${nonce}"`)
|
|
350
|
+
: script;
|
|
384
351
|
};
|
|
385
352
|
const onStart = async () => {
|
|
386
353
|
try {
|
|
@@ -398,58 +365,34 @@ const onStart = async () => {
|
|
|
398
365
|
}
|
|
399
366
|
};
|
|
400
367
|
onStart();
|
|
368
|
+
const clientManifest = getManifest('client');
|
|
401
369
|
const handler = eventHandler(async (event) => {
|
|
402
|
-
const req = event
|
|
403
|
-
const res = event.node.res;
|
|
370
|
+
const req = toWebRequest(event);
|
|
404
371
|
try {
|
|
405
372
|
if (req.url?.includes('_server')) {
|
|
406
373
|
return handleServerFunction(event);
|
|
407
374
|
}
|
|
408
|
-
|
|
409
|
-
if (!routeManifest || Object.keys(routeManifest).length === 0) {
|
|
375
|
+
if (!routeManifest) {
|
|
410
376
|
routeManifest = await createRouteManifest();
|
|
411
377
|
}
|
|
412
378
|
const cspNonce = event.locals?.cspNonce;
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
for (const param of searchParamPart.split('&')) {
|
|
420
|
-
const [key, value] = param.split('=');
|
|
421
|
-
searchParams[key] = decodeURIComponent(value || '');
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const matched = Object.entries(routeManifest).find(([path, entry]) => {
|
|
425
|
-
const pattern = path
|
|
426
|
-
.replace(/:\[\*[^/\]]+\]/g, '?(.*)?') // [[...slug]] -> (.*)?
|
|
427
|
-
.replace(/:\*[^/]*/g, '.*') // :*slug or :* -> .*
|
|
428
|
-
.replace(/:[^/]+/g, '[^/]+'); // :post -> [^/]+
|
|
429
|
-
const re = new RegExp(`^${pattern}$`);
|
|
430
|
-
return re.test(pathnamePart);
|
|
431
|
-
})?.[1];
|
|
432
|
-
const routePath = matched && matched.type === 'route'
|
|
433
|
-
? matched.manifestPath.split('/').slice(2).join('/')
|
|
434
|
-
: matched && matched.type === 'page'
|
|
435
|
-
? matched.mainPage.manifestPath.split('/').slice(2).join('/')
|
|
436
|
-
: '/';
|
|
437
|
-
const routeParams = extractRouteParams(routePath, pathnamePart);
|
|
438
|
-
if (routeParams) {
|
|
439
|
-
Object.assign(params, routeParams.params);
|
|
440
|
-
}
|
|
379
|
+
const urlObj = new URL(req.url);
|
|
380
|
+
const pathnamePart = urlObj.pathname;
|
|
381
|
+
const searchParams = Object.fromEntries(urlObj.searchParams);
|
|
382
|
+
const match = matchRoute(routeManifest, pathnamePart);
|
|
383
|
+
const matched = match?.handler;
|
|
384
|
+
const params = match?.params || {};
|
|
441
385
|
if (matched && matched.type === 'route') {
|
|
442
|
-
const routeModule = await matched.handler
|
|
386
|
+
const routeModule = await getCachedModule(matched.handler);
|
|
443
387
|
const reqMethod = req.method?.toUpperCase();
|
|
444
388
|
if (reqMethod) {
|
|
445
389
|
const handler = routeModule[reqMethod];
|
|
446
390
|
if (typeof handler === 'function') {
|
|
447
|
-
const result = await handler(
|
|
391
|
+
const result = await handler(req, {
|
|
448
392
|
params: params,
|
|
449
393
|
searchParams: searchParams,
|
|
450
394
|
});
|
|
451
|
-
|
|
452
|
-
return;
|
|
395
|
+
return result;
|
|
453
396
|
}
|
|
454
397
|
throw new Error(`Method ${reqMethod} not implemented in ${matched.handler.src}`);
|
|
455
398
|
}
|
|
@@ -461,245 +404,281 @@ const handler = eventHandler(async (event) => {
|
|
|
461
404
|
charset: {
|
|
462
405
|
type: 'meta',
|
|
463
406
|
attributes: {
|
|
464
|
-
charset: 'UTF-8'
|
|
465
|
-
}
|
|
407
|
+
charset: 'UTF-8',
|
|
408
|
+
},
|
|
466
409
|
},
|
|
467
410
|
viewport: {
|
|
468
411
|
type: 'meta',
|
|
469
412
|
attributes: {
|
|
470
413
|
name: 'viewport',
|
|
471
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
472
|
-
}
|
|
414
|
+
content: 'width=device-width, initial-scale=1.0',
|
|
415
|
+
},
|
|
473
416
|
},
|
|
474
417
|
title: {
|
|
475
418
|
type: 'title',
|
|
476
419
|
attributes: {},
|
|
477
|
-
content: 'SolidStep'
|
|
420
|
+
content: 'SolidStep',
|
|
478
421
|
},
|
|
479
422
|
build_time: {
|
|
480
423
|
type: 'meta',
|
|
481
424
|
attributes: {
|
|
482
425
|
name: 'x-build-time',
|
|
483
426
|
content: Date.now().toString(),
|
|
484
|
-
description: 'IMPORTANT: This tag indicates the build time of the application and should not be removed.'
|
|
427
|
+
description: 'IMPORTANT: This tag indicates the build time of the application and should not be removed.',
|
|
485
428
|
},
|
|
486
|
-
}
|
|
429
|
+
},
|
|
487
430
|
};
|
|
488
431
|
const assets = await clientManifest.inputs[clientManifest.handler].assets();
|
|
489
432
|
const manifestHtml = `<script ${cspNonce ? `nonce="${cspNonce}"` : ''}>window.manifest=${JSON.stringify(await clientManifest.json())}</script>`;
|
|
490
433
|
let clientHydrationScript = undefined;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
434
|
+
setHeader('Content-Type', 'text/html');
|
|
435
|
+
setHeader('Cache-Control', 'no-cache');
|
|
436
|
+
const stream = new ReadableStream({
|
|
437
|
+
async start(controller) {
|
|
438
|
+
const encoder = new TextEncoder();
|
|
439
|
+
const push = (text) => controller.enqueue(encoder.encode(text));
|
|
495
440
|
try {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
441
|
+
if (!matched) {
|
|
442
|
+
try {
|
|
443
|
+
const match = matchRoute(routeManifest, '/');
|
|
444
|
+
const notFoundEntry = match.handler;
|
|
445
|
+
if (!notFoundEntry) {
|
|
446
|
+
throw new Error('No not-found page configured');
|
|
447
|
+
}
|
|
448
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
449
|
+
toRender: 'not-found',
|
|
450
|
+
entry: notFoundEntry,
|
|
451
|
+
routeParams: {},
|
|
452
|
+
searchParams: {},
|
|
453
|
+
req: req,
|
|
454
|
+
pageOptions: {},
|
|
455
|
+
cspNonce,
|
|
456
|
+
});
|
|
457
|
+
assets.push(...documentAssets);
|
|
458
|
+
clientHydrationScript = `
|
|
459
|
+
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
460
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
461
|
+
main('/not-found/',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
462
|
+
</script>
|
|
463
|
+
`;
|
|
464
|
+
html = rendered;
|
|
465
|
+
meta = {
|
|
466
|
+
...meta,
|
|
467
|
+
...documentMeta,
|
|
468
|
+
};
|
|
469
|
+
setResponseStatus(404);
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
console.error('404 module not found:', e);
|
|
473
|
+
setResponseStatus(404);
|
|
474
|
+
push('Not Found');
|
|
475
|
+
controller.close();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
const { options } = matched
|
|
481
|
+
.mainPage.options
|
|
482
|
+
? await getCachedModule(matched.mainPage
|
|
483
|
+
.options)
|
|
484
|
+
: { options: {} };
|
|
485
|
+
if (options?.responseHeaders) {
|
|
486
|
+
const headers = options.responseHeaders;
|
|
487
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
488
|
+
setHeader(key, value);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
if (!matched.loadingPage) {
|
|
493
|
+
throw new Error('No loading page');
|
|
494
|
+
}
|
|
495
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
496
|
+
toRender: 'loading',
|
|
497
|
+
entry: matched,
|
|
498
|
+
routeParams: params,
|
|
499
|
+
searchParams,
|
|
500
|
+
req: req,
|
|
501
|
+
pageOptions: options,
|
|
502
|
+
cspNonce,
|
|
503
|
+
});
|
|
504
|
+
const assetsHtml = assets
|
|
505
|
+
.concat(documentAssets)
|
|
506
|
+
.map((asset) => {
|
|
507
|
+
const attributeString = Object.entries(asset.attrs)
|
|
508
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
509
|
+
.join(' ');
|
|
510
|
+
if (asset.tag === 'script') {
|
|
511
|
+
return `<script ${attributeString}></script>`;
|
|
512
|
+
}
|
|
513
|
+
if (asset.tag === 'link') {
|
|
514
|
+
return `<link ${attributeString}>`;
|
|
515
|
+
}
|
|
516
|
+
if (asset.tag === 'style') {
|
|
517
|
+
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
.join('\n');
|
|
521
|
+
const html = `
|
|
522
|
+
<!doctype html>
|
|
523
|
+
<html lang="en">
|
|
524
|
+
<head>
|
|
525
|
+
${generateHtmlHead({
|
|
526
|
+
...meta,
|
|
527
|
+
...documentMeta,
|
|
528
|
+
})}
|
|
529
|
+
${assetsHtml}
|
|
530
|
+
${hydrationScript({ nonce: cspNonce })}
|
|
531
|
+
</head>
|
|
532
|
+
<noscript>
|
|
533
|
+
Please enable JavaScript to view the content.<br/>
|
|
534
|
+
</noscript>
|
|
535
|
+
${rendered}
|
|
536
|
+
</html>
|
|
537
|
+
`;
|
|
538
|
+
push(html);
|
|
539
|
+
push(`
|
|
540
|
+
<script type="module" data-hydration="loading" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
541
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
542
|
+
main('${matched.loadingPage?.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
543
|
+
</script>
|
|
544
|
+
`);
|
|
545
|
+
loading = true;
|
|
546
|
+
}
|
|
547
|
+
catch (e) {
|
|
548
|
+
// skip
|
|
549
|
+
}
|
|
550
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
551
|
+
toRender: 'main',
|
|
552
|
+
entry: matched,
|
|
553
|
+
routeParams: params,
|
|
554
|
+
searchParams,
|
|
555
|
+
req: req,
|
|
556
|
+
pageOptions: options,
|
|
557
|
+
cspNonce,
|
|
558
|
+
});
|
|
559
|
+
assets.push(...documentAssets);
|
|
560
|
+
clientHydrationScript = `
|
|
561
|
+
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
562
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
563
|
+
main('${matched.mainPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
564
|
+
</script>
|
|
565
|
+
`;
|
|
566
|
+
html = rendered;
|
|
567
|
+
meta = {
|
|
568
|
+
...meta,
|
|
569
|
+
...documentMeta,
|
|
570
|
+
};
|
|
571
|
+
setResponseStatus(200);
|
|
507
572
|
}
|
|
508
|
-
clientHydrationScript = `
|
|
509
|
-
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
510
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
511
|
-
main('/not-found/',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
512
|
-
</script>
|
|
513
|
-
`;
|
|
514
|
-
html = rendered;
|
|
515
|
-
meta = {
|
|
516
|
-
...meta,
|
|
517
|
-
...documentMeta
|
|
518
|
-
};
|
|
519
|
-
res.statusCode = 404;
|
|
520
|
-
}
|
|
521
|
-
catch (e) {
|
|
522
|
-
console.error('404 module not found:', e);
|
|
523
|
-
res.statusCode = 404;
|
|
524
|
-
return res.end('Not Found');
|
|
525
573
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
574
|
+
catch (e1) {
|
|
575
|
+
if (e1 instanceof RedirectError ||
|
|
576
|
+
e1.name === 'RedirectError') {
|
|
577
|
+
setHeader('Location', e1.message);
|
|
578
|
+
setResponseStatus(302);
|
|
579
|
+
controller.close();
|
|
580
|
+
return;
|
|
531
581
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
582
|
+
if (import.meta.env.DEV) {
|
|
583
|
+
console.error(e1);
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
const errorPage = matched
|
|
587
|
+
.errorPage;
|
|
588
|
+
if (!errorPage) {
|
|
589
|
+
throw e1;
|
|
590
|
+
}
|
|
591
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
592
|
+
toRender: 'error',
|
|
593
|
+
entry: matched,
|
|
594
|
+
routeParams: params,
|
|
595
|
+
searchParams,
|
|
596
|
+
req: req,
|
|
597
|
+
pageOptions: {},
|
|
598
|
+
cspNonce,
|
|
599
|
+
error: e1,
|
|
600
|
+
});
|
|
601
|
+
assets.push(...documentAssets);
|
|
602
|
+
clientHydrationScript = `
|
|
603
|
+
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
604
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
605
|
+
main('${errorPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
606
|
+
</script>
|
|
607
|
+
`;
|
|
608
|
+
html = rendered;
|
|
609
|
+
meta = {
|
|
610
|
+
...meta,
|
|
611
|
+
...documentMeta,
|
|
612
|
+
};
|
|
613
|
+
// statusCode = 500;
|
|
614
|
+
setResponseStatus(500);
|
|
615
|
+
}
|
|
616
|
+
catch (e2) {
|
|
617
|
+
throw e1;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (loading) {
|
|
621
|
+
const assetsHtml = assets
|
|
622
|
+
.map((asset) => {
|
|
541
623
|
const attributeString = Object.entries(asset.attrs)
|
|
542
624
|
.map(([key, value]) => `${key}="${value}"`)
|
|
543
625
|
.join(' ');
|
|
544
|
-
if (asset.tag === 'script') {
|
|
545
|
-
return `<script ${attributeString}></script>`;
|
|
546
|
-
}
|
|
547
626
|
if (asset.tag === 'link') {
|
|
548
627
|
return `<link ${attributeString}>`;
|
|
549
628
|
}
|
|
550
629
|
if (asset.tag === 'style') {
|
|
551
630
|
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
552
631
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
${rendered}
|
|
569
|
-
</html>
|
|
570
|
-
`;
|
|
571
|
-
res.write(html);
|
|
572
|
-
res.write(`
|
|
573
|
-
<script type="module" data-hydration="loading" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
574
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
575
|
-
main('${matched.loadingPage?.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
576
|
-
</script>
|
|
632
|
+
return '';
|
|
633
|
+
})
|
|
634
|
+
.join('\n');
|
|
635
|
+
push(`
|
|
636
|
+
<script ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
637
|
+
const head = document.querySelector('head');
|
|
638
|
+
const scripts = Array.from(head.querySelectorAll('script'));
|
|
639
|
+
head.innerHTML = \`${generateHtmlHead(meta) + assetsHtml}\`;
|
|
640
|
+
scripts.forEach(script => {
|
|
641
|
+
head.appendChild(script);
|
|
642
|
+
});
|
|
643
|
+
document.querySelector('script[data-hydration="loading"]')?.remove();
|
|
644
|
+
const loading = document.querySelector('body');
|
|
645
|
+
loading.innerHTML = \`${html}\`;
|
|
646
|
+
</script>
|
|
577
647
|
`);
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
584
|
-
toRender: 'main',
|
|
585
|
-
entry: matched,
|
|
586
|
-
routeParams: params,
|
|
587
|
-
searchParams,
|
|
588
|
-
req: toWebRequest(event),
|
|
589
|
-
cspNonce,
|
|
590
|
-
});
|
|
591
|
-
for (const asset of documentAssets) {
|
|
592
|
-
assets.push(asset);
|
|
593
|
-
}
|
|
594
|
-
clientHydrationScript = `
|
|
595
|
-
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
596
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
597
|
-
main('${matched.mainPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
598
|
-
</script>
|
|
599
|
-
`;
|
|
600
|
-
html = rendered;
|
|
601
|
-
meta = {
|
|
602
|
-
...meta,
|
|
603
|
-
...documentMeta
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
catch (e1) {
|
|
608
|
-
if (e1 instanceof RedirectError ||
|
|
609
|
-
e1.name === 'RedirectError') {
|
|
610
|
-
throw e1;
|
|
611
|
-
}
|
|
612
|
-
try {
|
|
613
|
-
const errorPage = matched.errorPage;
|
|
614
|
-
if (!errorPage) {
|
|
615
|
-
throw e1;
|
|
616
|
-
}
|
|
617
|
-
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
618
|
-
toRender: 'error',
|
|
619
|
-
entry: matched,
|
|
620
|
-
routeParams: params,
|
|
621
|
-
searchParams,
|
|
622
|
-
req: toWebRequest(event),
|
|
623
|
-
cspNonce,
|
|
624
|
-
});
|
|
625
|
-
for (const asset of documentAssets) {
|
|
626
|
-
assets.push(asset);
|
|
627
|
-
}
|
|
628
|
-
clientHydrationScript = `
|
|
629
|
-
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
630
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
631
|
-
main('${errorPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
632
|
-
</script>
|
|
633
|
-
`;
|
|
634
|
-
html = rendered;
|
|
635
|
-
meta = {
|
|
636
|
-
...meta,
|
|
637
|
-
...documentMeta
|
|
638
|
-
};
|
|
639
|
-
res.statusCode = 500;
|
|
640
|
-
}
|
|
641
|
-
catch (e2) {
|
|
642
|
-
throw e1;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
if (loading) {
|
|
646
|
-
const assetsHtml = assets.map((asset) => {
|
|
647
|
-
const attributeString = Object.entries(asset.attrs)
|
|
648
|
-
.map(([key, value]) => `${key}="${value}"`)
|
|
649
|
-
.join(' ');
|
|
650
|
-
if (asset.tag === 'link') {
|
|
651
|
-
return `<link ${attributeString}>`;
|
|
652
|
-
}
|
|
653
|
-
if (asset.tag === 'style') {
|
|
654
|
-
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
648
|
+
push(manifestHtml);
|
|
649
|
+
push(clientHydrationScript);
|
|
650
|
+
controller.close();
|
|
651
|
+
return;
|
|
655
652
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (asset.tag === 'link') {
|
|
682
|
-
return `<link ${attributeString}>`;
|
|
683
|
-
}
|
|
684
|
-
if (asset.tag === 'style') {
|
|
685
|
-
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
686
|
-
}
|
|
687
|
-
}).join('\n');
|
|
688
|
-
const transformHtml = template
|
|
689
|
-
.replace('<!--app-head-->', `${generateHtmlHead(meta)}\n${assetsHtml}\n${hydrationScript({ nonce: cspNonce })}`)
|
|
690
|
-
.replace('<!--app-body-->', (html ?? '') + manifestHtml + clientHydrationScript);
|
|
691
|
-
return res.end(transformHtml);
|
|
653
|
+
const assetsHtml = assets
|
|
654
|
+
.map((asset) => {
|
|
655
|
+
const attributeString = Object.entries(asset.attrs)
|
|
656
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
657
|
+
.join(' ');
|
|
658
|
+
if (asset.tag === 'script') {
|
|
659
|
+
return `<script ${attributeString} ${cspNonce ? `nonce="${cspNonce}"` : ''}></script>`;
|
|
660
|
+
}
|
|
661
|
+
if (asset.tag === 'link') {
|
|
662
|
+
return `<link ${attributeString}>`;
|
|
663
|
+
}
|
|
664
|
+
if (asset.tag === 'style') {
|
|
665
|
+
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
666
|
+
}
|
|
667
|
+
})
|
|
668
|
+
.join('\n');
|
|
669
|
+
const transformHtml = template
|
|
670
|
+
.replace('<!--app-head-->', `${generateHtmlHead(meta)}\n${assetsHtml}\n${hydrationScript({ nonce: cspNonce })}`)
|
|
671
|
+
.replace('<!--app-body-->', (html ?? '') + manifestHtml + clientHydrationScript);
|
|
672
|
+
push(transformHtml);
|
|
673
|
+
controller.close();
|
|
674
|
+
return;
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
return stream;
|
|
692
678
|
}
|
|
693
679
|
catch (e) {
|
|
694
|
-
if (e instanceof RedirectError ||
|
|
695
|
-
e.name === 'RedirectError') {
|
|
696
|
-
res.statusCode = 302;
|
|
697
|
-
res.setHeader('Location', e.message);
|
|
698
|
-
return res.end('Redirecting...');
|
|
699
|
-
}
|
|
700
680
|
console.error(e);
|
|
701
|
-
|
|
702
|
-
return res.end('Internal Server Error');
|
|
681
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
703
682
|
}
|
|
704
683
|
});
|
|
705
684
|
export default handler;
|