solidstep 0.2.0 → 0.3.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/README.md +10 -0
- package/package.json +1 -1
- package/server.d.ts +1 -1
- package/server.d.ts.map +1 -1
- package/server.js +398 -352
- package/utils/server-action.server.d.ts.map +1 -1
- package/utils/server-action.server.js +2 -1
package/README.md
CHANGED
|
@@ -805,6 +805,16 @@ As SolidStep is built using Vite, it follows the same guide as stated in [Vite d
|
|
|
805
805
|
- Advanced caching strategies
|
|
806
806
|
- WebSocket support
|
|
807
807
|
|
|
808
|
+
## Testing
|
|
809
|
+
|
|
810
|
+
SolidStep does not include a built-in testing framework. However, we recommend setting up testing using Vitest ecosystem. You can use [Vitest](https://vitest.dev/) for unit and integration tests, and [Playwright](https://playwright.dev/) for end-to-end testing.
|
|
811
|
+
|
|
812
|
+
### Testing Server Actions
|
|
813
|
+
|
|
814
|
+
When testing server actions, you can use Vitest to accomplish this. Just test as you would with any other async function.
|
|
815
|
+
|
|
816
|
+
When testing pages (e2e tests), you can trigger server actions by simulating user interactions that would call those actions. If needed, you can also intercept network requests to directly test the action endpoints. Use the testing framework's capabilities to intercept the requests and ensure the responses have the expected results. If the server action returns json data, stringify it and add it to the response body as well as setting the content-type header to 'application/json'. If the action has a more complex return type, use seroval to serialize the response before sending it back.
|
|
817
|
+
|
|
808
818
|
## License
|
|
809
819
|
|
|
810
820
|
MIT
|
package/package.json
CHANGED
package/server.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
declare const handler: import("vinxi/http").EventHandler<import("vinxi/http").EventHandlerRequest, Promise<
|
|
1
|
+
declare const handler: import("vinxi/http").EventHandler<import("vinxi/http").EventHandlerRequest, Promise<any>>;
|
|
2
2
|
export default handler;
|
|
3
3
|
//# sourceMappingURL=server.d.ts.map
|
package/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"AA4kBA,QAAA,MAAM,OAAO,2FA0XX,CAAC;AAEH,eAAe,OAAO,CAAC"}
|
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,102 @@ 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
|
-
const isPageFile = (file) => file.endsWith('page.tsx')
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const isPageFile = (file) => file.endsWith('page.tsx') ||
|
|
12
|
+
file.endsWith('page.jsx') ||
|
|
13
|
+
file.endsWith('page.ts') ||
|
|
14
|
+
file.endsWith('page.js');
|
|
15
15
|
const isRouteFile = (file) => file.endsWith('route.ts') || file.endsWith('route.js');
|
|
16
|
-
const parseSegment = (part) =>
|
|
16
|
+
const parseSegment = (part) => {
|
|
17
|
+
if (part.startsWith('[[') && part.endsWith(']]')) {
|
|
18
|
+
return `:^*${part.slice(5, -2)}`;
|
|
19
|
+
}
|
|
20
|
+
return part.startsWith('[')
|
|
21
|
+
? `:${part.slice(1, -1).replace(/\.\.\./, '*')}`
|
|
22
|
+
: part;
|
|
23
|
+
};
|
|
24
|
+
const createMatcher = (routePath) => {
|
|
25
|
+
const keys = [];
|
|
26
|
+
const segments = routePath.split('/').map((part) => {
|
|
27
|
+
if (part.startsWith(':')) {
|
|
28
|
+
if (part.startsWith(':^*')) {
|
|
29
|
+
// Catch-all inclusive
|
|
30
|
+
keys.push(part.slice(3));
|
|
31
|
+
return '?(.*)?';
|
|
32
|
+
}
|
|
33
|
+
if (part.startsWith(':*')) {
|
|
34
|
+
// Catch-all
|
|
35
|
+
keys.push(part.slice(2));
|
|
36
|
+
return '.*';
|
|
37
|
+
}
|
|
38
|
+
// Standard param
|
|
39
|
+
keys.push(part.slice(1));
|
|
40
|
+
return '([^/]+)';
|
|
41
|
+
}
|
|
42
|
+
return part;
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
regex: new RegExp(`^${segments.join('/')}$`),
|
|
46
|
+
keys,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const getNormalizedPath = (path, clean) => {
|
|
50
|
+
const segments = path.split('/').slice(2).map(parseSegment);
|
|
51
|
+
if (clean)
|
|
52
|
+
return `/${segments.filter((s) => !s.startsWith('(')).join('/')}`;
|
|
53
|
+
return `/${segments.join('/')}`;
|
|
54
|
+
};
|
|
17
55
|
const createRouteManifest = async () => {
|
|
18
56
|
const entries = {};
|
|
19
57
|
const allRoutes = [];
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
58
|
+
const layoutsMap = new Map();
|
|
59
|
+
const loadingPagesMap = new Map();
|
|
60
|
+
const errorPagesMap = new Map();
|
|
61
|
+
const groupsMap = new Map();
|
|
24
62
|
let notFoundPage;
|
|
25
63
|
for (const fileRoute of fileRoutes) {
|
|
26
64
|
if (fileRoute.type === 'route') {
|
|
27
65
|
allRoutes.push(fileRoute);
|
|
28
66
|
}
|
|
29
67
|
if (fileRoute.type === 'layout') {
|
|
30
|
-
|
|
68
|
+
const path = getNormalizedPath(fileRoute.path);
|
|
69
|
+
layoutsMap.set(path, fileRoute);
|
|
31
70
|
}
|
|
32
71
|
if (fileRoute.type === 'not-found') {
|
|
33
72
|
notFoundPage = fileRoute;
|
|
34
73
|
}
|
|
35
74
|
if (fileRoute.type === 'loading') {
|
|
36
|
-
|
|
75
|
+
const path = getNormalizedPath(fileRoute.path);
|
|
76
|
+
loadingPagesMap.set(path, fileRoute);
|
|
37
77
|
}
|
|
38
78
|
if (fileRoute.type === 'error') {
|
|
39
|
-
|
|
79
|
+
const path = getNormalizedPath(fileRoute.path);
|
|
80
|
+
errorPagesMap.set(path, fileRoute);
|
|
40
81
|
}
|
|
41
82
|
if (fileRoute.type === 'group') {
|
|
42
|
-
|
|
83
|
+
const parentPath = fileRoute.parent
|
|
84
|
+
? getNormalizedPath(fileRoute.parent)
|
|
85
|
+
: '';
|
|
86
|
+
const existing = groupsMap.get(parentPath) || [];
|
|
87
|
+
existing.push(fileRoute);
|
|
88
|
+
groupsMap.set(parentPath, existing);
|
|
43
89
|
}
|
|
44
90
|
}
|
|
91
|
+
const regex = /\?(?:pick=.*)*/g;
|
|
45
92
|
for (const fileRoute of allRoutes) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const regex = /\?(?:pick=.*)*/g;
|
|
93
|
+
const routePath = getNormalizedPath(fileRoute.path, true);
|
|
94
|
+
const routeMatcherPath = getNormalizedPath(fileRoute.path);
|
|
49
95
|
const src = fileRoute.$handler?.src.replace(regex, '');
|
|
50
96
|
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
|
-
});
|
|
97
|
+
const loadingPage = loadingPagesMap.get(routeMatcherPath);
|
|
98
|
+
const matchedGroups = groupsMap.get(routePath);
|
|
59
99
|
const groups = {};
|
|
60
100
|
if (matchedGroups && matchedGroups.length > 0) {
|
|
61
101
|
for (const group of matchedGroups) {
|
|
62
|
-
const groupName = group.path
|
|
102
|
+
const groupName = group.path
|
|
103
|
+
.split('/')
|
|
104
|
+
.filter((s) => !s.startsWith('('))
|
|
105
|
+
.map(parseSegment)
|
|
106
|
+
.at(-1);
|
|
63
107
|
if (!groupName)
|
|
64
108
|
continue;
|
|
65
109
|
groups[groupName] = {
|
|
@@ -69,20 +113,18 @@ const createRouteManifest = async () => {
|
|
|
69
113
|
};
|
|
70
114
|
}
|
|
71
115
|
}
|
|
116
|
+
const segments = routeMatcherPath.split('/').filter(Boolean);
|
|
72
117
|
let errorPage;
|
|
73
118
|
const layouts = [];
|
|
74
|
-
|
|
75
|
-
|
|
119
|
+
// We need to traverse from root to leaf to build layouts order correctly?
|
|
120
|
+
// Original code: i = segments.length down to 0. unshift matches.
|
|
121
|
+
// i=length: /a/b/c. i=0: /.
|
|
122
|
+
for (let i = segments.length; i >= 0; i--) {
|
|
123
|
+
const path = i === 0 ? '/' : `/${segments.slice(0, i).join('/')}`;
|
|
76
124
|
if (!errorPage) {
|
|
77
|
-
errorPage =
|
|
78
|
-
const routePath = `/${route.path.split('/').slice(2).map(parseSegment).join('/')}`;
|
|
79
|
-
return routePath === path;
|
|
80
|
-
});
|
|
125
|
+
errorPage = errorPagesMap.get(path);
|
|
81
126
|
}
|
|
82
|
-
const layout =
|
|
83
|
-
const routePath = `/${route.path.split('/').slice(2).map(parseSegment).join('/')}`;
|
|
84
|
-
return routePath === path;
|
|
85
|
-
});
|
|
127
|
+
const layout = layoutsMap.get(path);
|
|
86
128
|
if (layout) {
|
|
87
129
|
layouts.unshift({
|
|
88
130
|
manifestPath: layout.path,
|
|
@@ -92,8 +134,11 @@ const createRouteManifest = async () => {
|
|
|
92
134
|
});
|
|
93
135
|
}
|
|
94
136
|
}
|
|
137
|
+
const { regex: matcherRegex, keys } = createMatcher(routePath);
|
|
95
138
|
entries[routePath] = {
|
|
96
139
|
type: 'page',
|
|
140
|
+
regex: matcherRegex,
|
|
141
|
+
paramKeys: keys,
|
|
97
142
|
mainPage: {
|
|
98
143
|
manifestPath: fileRoute.path,
|
|
99
144
|
page: fileRoute.$component,
|
|
@@ -101,28 +146,37 @@ const createRouteManifest = async () => {
|
|
|
101
146
|
generateMeta: fileRoute.$generateMeta,
|
|
102
147
|
options: fileRoute.$options,
|
|
103
148
|
},
|
|
104
|
-
loadingPage: loadingPage
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
loadingPage: loadingPage
|
|
150
|
+
? {
|
|
151
|
+
page: loadingPage.$component,
|
|
152
|
+
generateMeta: loadingPage.$generateMeta,
|
|
153
|
+
manifestPath: loadingPage.path,
|
|
154
|
+
}
|
|
155
|
+
: undefined,
|
|
156
|
+
errorPage: errorPage
|
|
157
|
+
? {
|
|
158
|
+
page: errorPage.$component,
|
|
159
|
+
generateMeta: errorPage.$generateMeta,
|
|
160
|
+
manifestPath: errorPage.path,
|
|
161
|
+
}
|
|
162
|
+
: undefined,
|
|
163
|
+
notFoundPage: routePath === '/' && notFoundPage
|
|
164
|
+
? {
|
|
165
|
+
page: notFoundPage.$component,
|
|
166
|
+
generateMeta: notFoundPage.$generateMeta,
|
|
167
|
+
manifestPath: notFoundPage.path,
|
|
168
|
+
}
|
|
169
|
+
: undefined,
|
|
119
170
|
layouts: layouts,
|
|
120
171
|
groups: groups,
|
|
121
172
|
};
|
|
122
173
|
}
|
|
123
174
|
else if (src && isRouteFile(src)) {
|
|
175
|
+
const { regex: matcherRegex, keys } = createMatcher(routePath);
|
|
124
176
|
entries[routePath] = {
|
|
125
177
|
type: 'route',
|
|
178
|
+
regex: matcherRegex,
|
|
179
|
+
paramKeys: keys,
|
|
126
180
|
handler: fileRoute.$handler,
|
|
127
181
|
manifestPath: fileRoute.path,
|
|
128
182
|
};
|
|
@@ -158,12 +212,12 @@ const extractRouteParams = (route, url) => {
|
|
|
158
212
|
if (matched)
|
|
159
213
|
return { route, params };
|
|
160
214
|
};
|
|
161
|
-
const template = `
|
|
162
|
-
<!DOCTYPE html>
|
|
163
|
-
<html lang="en">
|
|
164
|
-
<head><!--app-head--></head>
|
|
165
|
-
<!--app-body-->
|
|
166
|
-
</html>
|
|
215
|
+
const template = `
|
|
216
|
+
<!DOCTYPE html>
|
|
217
|
+
<html lang="en">
|
|
218
|
+
<head><!--app-head--></head>
|
|
219
|
+
<!--app-body-->
|
|
220
|
+
</html>
|
|
167
221
|
`;
|
|
168
222
|
const generateHtmlHead = (meta) => {
|
|
169
223
|
const head = Object.entries(meta)
|
|
@@ -177,7 +231,9 @@ const generateHtmlHead = (meta) => {
|
|
|
177
231
|
.join(' ');
|
|
178
232
|
return `<meta ${attrs}>`;
|
|
179
233
|
}
|
|
180
|
-
if (value.type === 'link' ||
|
|
234
|
+
if (value.type === 'link' ||
|
|
235
|
+
value.type === 'style' ||
|
|
236
|
+
value.type === 'script') {
|
|
181
237
|
const attrs = Object.entries(value.attributes)
|
|
182
238
|
.map(([attrKey, attrValue]) => `${attrKey}="${attrValue}"`)
|
|
183
239
|
.join(' ');
|
|
@@ -188,45 +244,11 @@ const generateHtmlHead = (meta) => {
|
|
|
188
244
|
.join('\n');
|
|
189
245
|
return head;
|
|
190
246
|
};
|
|
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, }) => {
|
|
247
|
+
const render = async ({ toRender, entry, routeParams, searchParams, req, pageOptions, cspNonce, error }) => {
|
|
218
248
|
const url = new URL(req.url);
|
|
219
249
|
const path = url.pathname;
|
|
220
250
|
const cachedEntry = getCache(path);
|
|
221
251
|
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
252
|
return {
|
|
231
253
|
rendered: cachedEntry.rendered,
|
|
232
254
|
documentMeta: cachedEntry.documentMeta,
|
|
@@ -234,7 +256,6 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
234
256
|
loaderData: cachedEntry.loaderData,
|
|
235
257
|
};
|
|
236
258
|
}
|
|
237
|
-
let cachingOptions = undefined;
|
|
238
259
|
let meta = {};
|
|
239
260
|
const loaderData = {};
|
|
240
261
|
const clientManifest = getManifest('client');
|
|
@@ -242,12 +263,14 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
242
263
|
const compose = entry.layouts.reduceRight((children, layout, index) => async () => {
|
|
243
264
|
const moduleSrc = `${layout.layout.src}&pick=$css`;
|
|
244
265
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
245
|
-
|
|
246
|
-
assets.push(asset);
|
|
247
|
-
}
|
|
266
|
+
assets.push(...moduleAssets);
|
|
248
267
|
const { default: layoutModule } = await layout.layout.import();
|
|
249
|
-
const { loader: layoutLoader } = layout.loader
|
|
250
|
-
|
|
268
|
+
const { loader: layoutLoader } = layout.loader
|
|
269
|
+
? await layout.loader.import()
|
|
270
|
+
: { loader: null };
|
|
271
|
+
const { generateMeta: generateMetaPage } = layout.generateMeta
|
|
272
|
+
? await layout.generateMeta.import()
|
|
273
|
+
: { generateMeta: null };
|
|
251
274
|
let data = {};
|
|
252
275
|
if (generateMetaPage) {
|
|
253
276
|
const metaData = await generateMetaPage({
|
|
@@ -257,7 +280,7 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
257
280
|
if (metaData) {
|
|
258
281
|
meta = {
|
|
259
282
|
...meta,
|
|
260
|
-
...metaData
|
|
283
|
+
...metaData,
|
|
261
284
|
};
|
|
262
285
|
}
|
|
263
286
|
}
|
|
@@ -275,11 +298,11 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
275
298
|
slotPromises.push((async () => {
|
|
276
299
|
const moduleSrc = `${group.page.src}&pick=$css`;
|
|
277
300
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
278
|
-
|
|
279
|
-
assets.push(asset);
|
|
280
|
-
}
|
|
301
|
+
assets.push(...moduleAssets);
|
|
281
302
|
const { default: groupPage } = await group.page.import();
|
|
282
|
-
const { loader: groupLoader } = group.loader
|
|
303
|
+
const { loader: groupLoader } = group.loader
|
|
304
|
+
? await group.loader.import()
|
|
305
|
+
: { loader: null };
|
|
283
306
|
let data = {};
|
|
284
307
|
if (groupLoader) {
|
|
285
308
|
const result = await groupLoader.loader(req);
|
|
@@ -289,7 +312,7 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
289
312
|
slots[groupName.replace('@', '')] = () => groupPage({
|
|
290
313
|
routeParams,
|
|
291
314
|
searchParams,
|
|
292
|
-
loaderData: data
|
|
315
|
+
loaderData: data,
|
|
293
316
|
});
|
|
294
317
|
})());
|
|
295
318
|
}
|
|
@@ -303,7 +326,7 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
303
326
|
slots: slots,
|
|
304
327
|
locals: {
|
|
305
328
|
cspNonce: cspNonce,
|
|
306
|
-
}
|
|
329
|
+
},
|
|
307
330
|
});
|
|
308
331
|
}, async () => {
|
|
309
332
|
const pageToRender = toRender === 'loading'
|
|
@@ -315,23 +338,14 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
315
338
|
: entry.mainPage;
|
|
316
339
|
const moduleSrc = `${pageToRender.page.src}&pick=$css`;
|
|
317
340
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
318
|
-
|
|
319
|
-
assets.push(asset);
|
|
320
|
-
}
|
|
341
|
+
assets.push(...moduleAssets);
|
|
321
342
|
const { default: page } = await pageToRender.page.import();
|
|
322
|
-
const { loader: pageLoader } = pageToRender.loader
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
}
|
|
343
|
+
const { loader: pageLoader } = pageToRender.loader
|
|
344
|
+
? await pageToRender.loader.import()
|
|
345
|
+
: { loader: null };
|
|
346
|
+
const { generateMeta } = pageToRender.generateMeta
|
|
347
|
+
? await pageToRender.generateMeta.import()
|
|
348
|
+
: { generateMeta: null };
|
|
335
349
|
let data = {};
|
|
336
350
|
if (pageLoader) {
|
|
337
351
|
const result = await pageLoader.loader(req);
|
|
@@ -346,23 +360,27 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
346
360
|
if (metaData) {
|
|
347
361
|
meta = {
|
|
348
362
|
...meta,
|
|
349
|
-
...metaData
|
|
363
|
+
...metaData,
|
|
350
364
|
};
|
|
351
365
|
}
|
|
352
366
|
}
|
|
353
|
-
|
|
367
|
+
const props = {
|
|
354
368
|
routeParams,
|
|
355
369
|
searchParams,
|
|
356
370
|
loaderData: data,
|
|
357
371
|
locals: {
|
|
358
372
|
cspNonce: cspNonce,
|
|
359
|
-
}
|
|
360
|
-
}
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
if (toRender === 'error') {
|
|
376
|
+
props.error = error;
|
|
377
|
+
}
|
|
378
|
+
return () => page(props);
|
|
361
379
|
});
|
|
362
380
|
const composed = await compose();
|
|
363
381
|
const rendered = await renderToString(() => composed());
|
|
364
382
|
if (toRender === 'main') {
|
|
365
|
-
const options =
|
|
383
|
+
const options = pageOptions?.cache;
|
|
366
384
|
setCache(path, {
|
|
367
385
|
rendered: rendered,
|
|
368
386
|
documentMeta: meta,
|
|
@@ -380,7 +398,9 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
380
398
|
let routeManifest = {};
|
|
381
399
|
const hydrationScript = ({ nonce, }) => {
|
|
382
400
|
const script = generateHydrationScript();
|
|
383
|
-
return nonce
|
|
401
|
+
return nonce
|
|
402
|
+
? script.replace('<script', `<script nonce="${nonce}"`)
|
|
403
|
+
: script;
|
|
384
404
|
};
|
|
385
405
|
const onStart = async () => {
|
|
386
406
|
try {
|
|
@@ -399,8 +419,7 @@ const onStart = async () => {
|
|
|
399
419
|
};
|
|
400
420
|
onStart();
|
|
401
421
|
const handler = eventHandler(async (event) => {
|
|
402
|
-
const req = event
|
|
403
|
-
const res = event.node.res;
|
|
422
|
+
const req = toWebRequest(event);
|
|
404
423
|
try {
|
|
405
424
|
if (req.url?.includes('_server')) {
|
|
406
425
|
return handleServerFunction(event);
|
|
@@ -410,9 +429,9 @@ const handler = eventHandler(async (event) => {
|
|
|
410
429
|
routeManifest = await createRouteManifest();
|
|
411
430
|
}
|
|
412
431
|
const cspNonce = event.locals?.cspNonce;
|
|
413
|
-
const url = req.url
|
|
432
|
+
const url = new URL(req.url).pathname;
|
|
414
433
|
// extract route params and search params
|
|
415
|
-
|
|
434
|
+
let params = {};
|
|
416
435
|
const searchParams = {};
|
|
417
436
|
const [pathnamePart, searchParamPart] = url.split('?');
|
|
418
437
|
if (searchParamPart) {
|
|
@@ -421,22 +440,21 @@ const handler = eventHandler(async (event) => {
|
|
|
421
440
|
searchParams[key] = decodeURIComponent(value || '');
|
|
422
441
|
}
|
|
423
442
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
Object.assign(params, routeParams.params);
|
|
443
|
+
let matched;
|
|
444
|
+
for (const entry of Object.values(routeManifest)) {
|
|
445
|
+
const match = entry.regex.exec(pathnamePart);
|
|
446
|
+
if (match) {
|
|
447
|
+
matched = entry;
|
|
448
|
+
if (entry.paramKeys) {
|
|
449
|
+
const routePath = matched && matched.type === 'route'
|
|
450
|
+
? matched.manifestPath.split('/').slice(2).join('/')
|
|
451
|
+
: matched && matched.type === 'page'
|
|
452
|
+
? matched.mainPage.manifestPath.split('/').slice(2).join('/')
|
|
453
|
+
: '/';
|
|
454
|
+
params = extractRouteParams(routePath, pathnamePart)?.params || {};
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
440
458
|
}
|
|
441
459
|
if (matched && matched.type === 'route') {
|
|
442
460
|
const routeModule = await matched.handler.import();
|
|
@@ -444,12 +462,11 @@ const handler = eventHandler(async (event) => {
|
|
|
444
462
|
if (reqMethod) {
|
|
445
463
|
const handler = routeModule[reqMethod];
|
|
446
464
|
if (typeof handler === 'function') {
|
|
447
|
-
const result = await handler(
|
|
465
|
+
const result = await handler(req, {
|
|
448
466
|
params: params,
|
|
449
467
|
searchParams: searchParams,
|
|
450
468
|
});
|
|
451
|
-
|
|
452
|
-
return;
|
|
469
|
+
return result;
|
|
453
470
|
}
|
|
454
471
|
throw new Error(`Method ${reqMethod} not implemented in ${matched.handler.src}`);
|
|
455
472
|
}
|
|
@@ -461,245 +478,274 @@ const handler = eventHandler(async (event) => {
|
|
|
461
478
|
charset: {
|
|
462
479
|
type: 'meta',
|
|
463
480
|
attributes: {
|
|
464
|
-
charset: 'UTF-8'
|
|
465
|
-
}
|
|
481
|
+
charset: 'UTF-8',
|
|
482
|
+
},
|
|
466
483
|
},
|
|
467
484
|
viewport: {
|
|
468
485
|
type: 'meta',
|
|
469
486
|
attributes: {
|
|
470
487
|
name: 'viewport',
|
|
471
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
472
|
-
}
|
|
488
|
+
content: 'width=device-width, initial-scale=1.0',
|
|
489
|
+
},
|
|
473
490
|
},
|
|
474
491
|
title: {
|
|
475
492
|
type: 'title',
|
|
476
493
|
attributes: {},
|
|
477
|
-
content: 'SolidStep'
|
|
494
|
+
content: 'SolidStep',
|
|
478
495
|
},
|
|
479
496
|
build_time: {
|
|
480
497
|
type: 'meta',
|
|
481
498
|
attributes: {
|
|
482
499
|
name: 'x-build-time',
|
|
483
500
|
content: Date.now().toString(),
|
|
484
|
-
description: 'IMPORTANT: This tag indicates the build time of the application and should not be removed.'
|
|
501
|
+
description: 'IMPORTANT: This tag indicates the build time of the application and should not be removed.',
|
|
485
502
|
},
|
|
486
|
-
}
|
|
503
|
+
},
|
|
487
504
|
};
|
|
488
505
|
const assets = await clientManifest.inputs[clientManifest.handler].assets();
|
|
489
506
|
const manifestHtml = `<script ${cspNonce ? `nonce="${cspNonce}"` : ''}>window.manifest=${JSON.stringify(await clientManifest.json())}</script>`;
|
|
490
507
|
let clientHydrationScript = undefined;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
508
|
+
setHeader('Content-Type', 'text/html');
|
|
509
|
+
setHeader('Cache-Control', 'no-cache');
|
|
510
|
+
const stream = new ReadableStream({
|
|
511
|
+
async start(controller) {
|
|
512
|
+
const encoder = new TextEncoder();
|
|
513
|
+
const push = (text) => controller.enqueue(encoder.encode(text));
|
|
495
514
|
try {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
515
|
+
if (!matched) {
|
|
516
|
+
try {
|
|
517
|
+
const notFoundPage = routeManifest['/'];
|
|
518
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
519
|
+
toRender: 'not-found',
|
|
520
|
+
entry: notFoundPage,
|
|
521
|
+
routeParams: {},
|
|
522
|
+
searchParams: {},
|
|
523
|
+
req: req,
|
|
524
|
+
pageOptions: {},
|
|
525
|
+
cspNonce,
|
|
526
|
+
});
|
|
527
|
+
assets.push(...documentAssets);
|
|
528
|
+
clientHydrationScript = `
|
|
529
|
+
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
530
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
531
|
+
main('/not-found/',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
532
|
+
</script>
|
|
533
|
+
`;
|
|
534
|
+
html = rendered;
|
|
535
|
+
meta = {
|
|
536
|
+
...meta,
|
|
537
|
+
...documentMeta,
|
|
538
|
+
};
|
|
539
|
+
setResponseStatus(404);
|
|
540
|
+
}
|
|
541
|
+
catch (e) {
|
|
542
|
+
console.error('404 module not found:', e);
|
|
543
|
+
setResponseStatus(404);
|
|
544
|
+
push('Not Found');
|
|
545
|
+
controller.close();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
const { options } = matched.mainPage.options
|
|
551
|
+
? await matched.mainPage.options.import()
|
|
552
|
+
: { options: {} };
|
|
553
|
+
if (options?.responseHeaders) {
|
|
554
|
+
const headers = options.responseHeaders;
|
|
555
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
556
|
+
setHeader(key, value);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
if (!matched.loadingPage) {
|
|
561
|
+
throw new Error('No loading page');
|
|
562
|
+
}
|
|
563
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
564
|
+
toRender: 'loading',
|
|
565
|
+
entry: matched,
|
|
566
|
+
routeParams: params,
|
|
567
|
+
searchParams,
|
|
568
|
+
req: req,
|
|
569
|
+
pageOptions: options,
|
|
570
|
+
cspNonce,
|
|
571
|
+
});
|
|
572
|
+
const assetsHtml = assets
|
|
573
|
+
.concat(documentAssets)
|
|
574
|
+
.map((asset) => {
|
|
575
|
+
const attributeString = Object.entries(asset.attrs)
|
|
576
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
577
|
+
.join(' ');
|
|
578
|
+
if (asset.tag === 'script') {
|
|
579
|
+
return `<script ${attributeString}></script>`;
|
|
580
|
+
}
|
|
581
|
+
if (asset.tag === 'link') {
|
|
582
|
+
return `<link ${attributeString}>`;
|
|
583
|
+
}
|
|
584
|
+
if (asset.tag === 'style') {
|
|
585
|
+
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
586
|
+
}
|
|
587
|
+
})
|
|
588
|
+
.join('\n');
|
|
589
|
+
const html = `
|
|
590
|
+
<!doctype html>
|
|
591
|
+
<html lang="en">
|
|
592
|
+
<head>
|
|
593
|
+
${generateHtmlHead({
|
|
594
|
+
...meta,
|
|
595
|
+
...documentMeta,
|
|
596
|
+
})}
|
|
597
|
+
${assetsHtml}
|
|
598
|
+
${hydrationScript({ nonce: cspNonce })}
|
|
599
|
+
</head>
|
|
600
|
+
<noscript>
|
|
601
|
+
Please enable JavaScript to view the content.<br/>
|
|
602
|
+
</noscript>
|
|
603
|
+
${rendered}
|
|
604
|
+
</html>
|
|
605
|
+
`;
|
|
606
|
+
push(html);
|
|
607
|
+
push(`
|
|
608
|
+
<script type="module" data-hydration="loading" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
609
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
610
|
+
main('${matched.loadingPage?.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
611
|
+
</script>
|
|
612
|
+
`);
|
|
613
|
+
loading = true;
|
|
614
|
+
}
|
|
615
|
+
catch (e) {
|
|
616
|
+
// skip
|
|
617
|
+
}
|
|
618
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
619
|
+
toRender: 'main',
|
|
620
|
+
entry: matched,
|
|
621
|
+
routeParams: params,
|
|
622
|
+
searchParams,
|
|
623
|
+
req: req,
|
|
624
|
+
pageOptions: options,
|
|
625
|
+
cspNonce,
|
|
626
|
+
});
|
|
627
|
+
assets.push(...documentAssets);
|
|
628
|
+
clientHydrationScript = `
|
|
629
|
+
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
630
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
631
|
+
main('${matched.mainPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
632
|
+
</script>
|
|
633
|
+
`;
|
|
634
|
+
html = rendered;
|
|
635
|
+
meta = {
|
|
636
|
+
...meta,
|
|
637
|
+
...documentMeta,
|
|
638
|
+
};
|
|
639
|
+
setResponseStatus(200);
|
|
507
640
|
}
|
|
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
641
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
642
|
+
catch (e1) {
|
|
643
|
+
if (e1 instanceof RedirectError ||
|
|
644
|
+
e1.name === 'RedirectError') {
|
|
645
|
+
setHeader('Location', e1.message);
|
|
646
|
+
setResponseStatus(302);
|
|
647
|
+
controller.close();
|
|
648
|
+
return;
|
|
531
649
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
650
|
+
if (import.meta.env.DEV) {
|
|
651
|
+
console.error(e1);
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
const errorPage = matched.errorPage;
|
|
655
|
+
if (!errorPage) {
|
|
656
|
+
throw e1;
|
|
657
|
+
}
|
|
658
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
659
|
+
toRender: 'error',
|
|
660
|
+
entry: matched,
|
|
661
|
+
routeParams: params,
|
|
662
|
+
searchParams,
|
|
663
|
+
req: req,
|
|
664
|
+
pageOptions: {},
|
|
665
|
+
cspNonce,
|
|
666
|
+
error: e1
|
|
667
|
+
});
|
|
668
|
+
assets.push(...documentAssets);
|
|
669
|
+
clientHydrationScript = `
|
|
670
|
+
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
671
|
+
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
672
|
+
main('${errorPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
673
|
+
</script>
|
|
674
|
+
`;
|
|
675
|
+
html = rendered;
|
|
676
|
+
meta = {
|
|
677
|
+
...meta,
|
|
678
|
+
...documentMeta,
|
|
679
|
+
};
|
|
680
|
+
// statusCode = 500;
|
|
681
|
+
setResponseStatus(500);
|
|
682
|
+
}
|
|
683
|
+
catch (e2) {
|
|
684
|
+
throw e1;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (loading) {
|
|
688
|
+
const assetsHtml = assets
|
|
689
|
+
.map((asset) => {
|
|
541
690
|
const attributeString = Object.entries(asset.attrs)
|
|
542
691
|
.map(([key, value]) => `${key}="${value}"`)
|
|
543
692
|
.join(' ');
|
|
544
|
-
if (asset.tag === 'script') {
|
|
545
|
-
return `<script ${attributeString}></script>`;
|
|
546
|
-
}
|
|
547
693
|
if (asset.tag === 'link') {
|
|
548
694
|
return `<link ${attributeString}>`;
|
|
549
695
|
}
|
|
550
696
|
if (asset.tag === 'style') {
|
|
551
697
|
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
552
698
|
}
|
|
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>
|
|
699
|
+
return '';
|
|
700
|
+
})
|
|
701
|
+
.join('\n');
|
|
702
|
+
push(`
|
|
703
|
+
<script ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
704
|
+
const head = document.querySelector('head');
|
|
705
|
+
const scripts = Array.from(head.querySelectorAll('script'));
|
|
706
|
+
head.innerHTML = \`${generateHtmlHead(meta) + assetsHtml}\`;
|
|
707
|
+
scripts.forEach(script => {
|
|
708
|
+
head.appendChild(script);
|
|
709
|
+
});
|
|
710
|
+
document.querySelector('script[data-hydration="loading"]')?.remove();
|
|
711
|
+
const loading = document.querySelector('body');
|
|
712
|
+
loading.innerHTML = \`${html}\`;
|
|
713
|
+
</script>
|
|
577
714
|
`);
|
|
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>`;
|
|
715
|
+
push(manifestHtml);
|
|
716
|
+
push(clientHydrationScript);
|
|
717
|
+
controller.close();
|
|
718
|
+
return;
|
|
655
719
|
}
|
|
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);
|
|
720
|
+
const assetsHtml = assets
|
|
721
|
+
.map((asset) => {
|
|
722
|
+
const attributeString = Object.entries(asset.attrs)
|
|
723
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
724
|
+
.join(' ');
|
|
725
|
+
if (asset.tag === 'script') {
|
|
726
|
+
return `<script ${attributeString} ${cspNonce ? `nonce="${cspNonce}"` : ''}></script>`;
|
|
727
|
+
}
|
|
728
|
+
if (asset.tag === 'link') {
|
|
729
|
+
return `<link ${attributeString}>`;
|
|
730
|
+
}
|
|
731
|
+
if (asset.tag === 'style') {
|
|
732
|
+
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
733
|
+
}
|
|
734
|
+
})
|
|
735
|
+
.join('\n');
|
|
736
|
+
const transformHtml = template
|
|
737
|
+
.replace('<!--app-head-->', `${generateHtmlHead(meta)}\n${assetsHtml}\n${hydrationScript({ nonce: cspNonce })}`)
|
|
738
|
+
.replace('<!--app-body-->', (html ?? '') + manifestHtml + clientHydrationScript);
|
|
739
|
+
push(transformHtml);
|
|
740
|
+
controller.close();
|
|
741
|
+
return;
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
return stream;
|
|
692
745
|
}
|
|
693
746
|
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
747
|
console.error(e);
|
|
701
|
-
|
|
702
|
-
return res.end('Internal Server Error');
|
|
748
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
703
749
|
}
|
|
704
750
|
});
|
|
705
751
|
export default handler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-action.server.d.ts","sourceRoot":"","sources":["../../utils/server-action.server.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIN,KAAK,SAAS,EAWd,MAAM,YAAY,CAAC;AAuHpB,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"server-action.server.d.ts","sourceRoot":"","sources":["../../utils/server-action.server.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIN,KAAK,SAAS,EAWd,MAAM,YAAY,CAAC;AAuHpB,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,SAAS,oBA2M1D;;AAED,wBAAkD"}
|
|
@@ -244,9 +244,10 @@ export async function handleServerFunction(event) {
|
|
|
244
244
|
// Step 4: diff the cache with new html from server
|
|
245
245
|
const reqUrl = new URL(request.url);
|
|
246
246
|
const serverUrl = reqUrl.origin;
|
|
247
|
-
await fetch(serverUrl + revalidatePath, {
|
|
247
|
+
const response = await fetch(serverUrl + revalidatePath, {
|
|
248
248
|
method: 'GET'
|
|
249
249
|
}, false);
|
|
250
|
+
await response.text(); // ensure the fetch is completed and cache is populated
|
|
250
251
|
const newCacheValue = getCache(revalidatePath);
|
|
251
252
|
const newHtml = newCacheValue?.rendered;
|
|
252
253
|
const dd = createDiffDOM({
|