solidstep 0.1.8 → 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 +14 -0
- package/package.json +1 -1
- package/server.d.ts +1 -1
- package/server.d.ts.map +1 -1
- package/server.js +399 -352
- package/utils/cache.js +1 -1
- package/utils/server-action.server.d.ts.map +1 -1
- package/utils/server-action.server.js +2 -1
package/README.md
CHANGED
|
@@ -370,6 +370,10 @@ export const options = {
|
|
|
370
370
|
},
|
|
371
371
|
};
|
|
372
372
|
```
|
|
373
|
+
- Regarding caching, setting `ttl` to `0` or omitting it will disable caching for that page.
|
|
374
|
+
- Setting a positive integer value will cache the page for that duration in milliseconds.
|
|
375
|
+
- Invalidation of cached pages can be done using the `invalidateCache` and `revalidatePath` utilities.
|
|
376
|
+
- The `responseHeaders` option allows you to set custom HTTP headers for the page response.
|
|
373
377
|
|
|
374
378
|
## API Routes
|
|
375
379
|
|
|
@@ -801,6 +805,16 @@ As SolidStep is built using Vite, it follows the same guide as stated in [Vite d
|
|
|
801
805
|
- Advanced caching strategies
|
|
802
806
|
- WebSocket support
|
|
803
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
|
+
|
|
804
818
|
## License
|
|
805
819
|
|
|
806
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,28 +360,33 @@ 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') {
|
|
383
|
+
const options = pageOptions?.cache;
|
|
365
384
|
setCache(path, {
|
|
366
385
|
rendered: rendered,
|
|
367
386
|
documentMeta: meta,
|
|
368
387
|
documentAssets: assets,
|
|
369
388
|
loaderData: loaderData,
|
|
370
|
-
},
|
|
389
|
+
}, options?.ttl ? options.ttl : 0);
|
|
371
390
|
}
|
|
372
391
|
return {
|
|
373
392
|
rendered: rendered,
|
|
@@ -379,7 +398,9 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
|
|
|
379
398
|
let routeManifest = {};
|
|
380
399
|
const hydrationScript = ({ nonce, }) => {
|
|
381
400
|
const script = generateHydrationScript();
|
|
382
|
-
return nonce
|
|
401
|
+
return nonce
|
|
402
|
+
? script.replace('<script', `<script nonce="${nonce}"`)
|
|
403
|
+
: script;
|
|
383
404
|
};
|
|
384
405
|
const onStart = async () => {
|
|
385
406
|
try {
|
|
@@ -398,8 +419,7 @@ const onStart = async () => {
|
|
|
398
419
|
};
|
|
399
420
|
onStart();
|
|
400
421
|
const handler = eventHandler(async (event) => {
|
|
401
|
-
const req = event
|
|
402
|
-
const res = event.node.res;
|
|
422
|
+
const req = toWebRequest(event);
|
|
403
423
|
try {
|
|
404
424
|
if (req.url?.includes('_server')) {
|
|
405
425
|
return handleServerFunction(event);
|
|
@@ -409,9 +429,9 @@ const handler = eventHandler(async (event) => {
|
|
|
409
429
|
routeManifest = await createRouteManifest();
|
|
410
430
|
}
|
|
411
431
|
const cspNonce = event.locals?.cspNonce;
|
|
412
|
-
const url = req.url
|
|
432
|
+
const url = new URL(req.url).pathname;
|
|
413
433
|
// extract route params and search params
|
|
414
|
-
|
|
434
|
+
let params = {};
|
|
415
435
|
const searchParams = {};
|
|
416
436
|
const [pathnamePart, searchParamPart] = url.split('?');
|
|
417
437
|
if (searchParamPart) {
|
|
@@ -420,22 +440,21 @@ const handler = eventHandler(async (event) => {
|
|
|
420
440
|
searchParams[key] = decodeURIComponent(value || '');
|
|
421
441
|
}
|
|
422
442
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
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
|
+
}
|
|
439
458
|
}
|
|
440
459
|
if (matched && matched.type === 'route') {
|
|
441
460
|
const routeModule = await matched.handler.import();
|
|
@@ -443,12 +462,11 @@ const handler = eventHandler(async (event) => {
|
|
|
443
462
|
if (reqMethod) {
|
|
444
463
|
const handler = routeModule[reqMethod];
|
|
445
464
|
if (typeof handler === 'function') {
|
|
446
|
-
const result = await handler(
|
|
465
|
+
const result = await handler(req, {
|
|
447
466
|
params: params,
|
|
448
467
|
searchParams: searchParams,
|
|
449
468
|
});
|
|
450
|
-
|
|
451
|
-
return;
|
|
469
|
+
return result;
|
|
452
470
|
}
|
|
453
471
|
throw new Error(`Method ${reqMethod} not implemented in ${matched.handler.src}`);
|
|
454
472
|
}
|
|
@@ -460,245 +478,274 @@ const handler = eventHandler(async (event) => {
|
|
|
460
478
|
charset: {
|
|
461
479
|
type: 'meta',
|
|
462
480
|
attributes: {
|
|
463
|
-
charset: 'UTF-8'
|
|
464
|
-
}
|
|
481
|
+
charset: 'UTF-8',
|
|
482
|
+
},
|
|
465
483
|
},
|
|
466
484
|
viewport: {
|
|
467
485
|
type: 'meta',
|
|
468
486
|
attributes: {
|
|
469
487
|
name: 'viewport',
|
|
470
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
471
|
-
}
|
|
488
|
+
content: 'width=device-width, initial-scale=1.0',
|
|
489
|
+
},
|
|
472
490
|
},
|
|
473
491
|
title: {
|
|
474
492
|
type: 'title',
|
|
475
493
|
attributes: {},
|
|
476
|
-
content: 'SolidStep'
|
|
494
|
+
content: 'SolidStep',
|
|
477
495
|
},
|
|
478
496
|
build_time: {
|
|
479
497
|
type: 'meta',
|
|
480
498
|
attributes: {
|
|
481
499
|
name: 'x-build-time',
|
|
482
500
|
content: Date.now().toString(),
|
|
483
|
-
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.',
|
|
484
502
|
},
|
|
485
|
-
}
|
|
503
|
+
},
|
|
486
504
|
};
|
|
487
505
|
const assets = await clientManifest.inputs[clientManifest.handler].assets();
|
|
488
506
|
const manifestHtml = `<script ${cspNonce ? `nonce="${cspNonce}"` : ''}>window.manifest=${JSON.stringify(await clientManifest.json())}</script>`;
|
|
489
507
|
let clientHydrationScript = undefined;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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));
|
|
494
514
|
try {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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);
|
|
506
640
|
}
|
|
507
|
-
clientHydrationScript = `
|
|
508
|
-
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
509
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
510
|
-
main('/not-found/',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
511
|
-
</script>
|
|
512
|
-
`;
|
|
513
|
-
html = rendered;
|
|
514
|
-
meta = {
|
|
515
|
-
...meta,
|
|
516
|
-
...documentMeta
|
|
517
|
-
};
|
|
518
|
-
res.statusCode = 404;
|
|
519
|
-
}
|
|
520
|
-
catch (e) {
|
|
521
|
-
console.error('404 module not found:', e);
|
|
522
|
-
res.statusCode = 404;
|
|
523
|
-
return res.end('Not Found');
|
|
524
641
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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;
|
|
530
649
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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) => {
|
|
540
690
|
const attributeString = Object.entries(asset.attrs)
|
|
541
691
|
.map(([key, value]) => `${key}="${value}"`)
|
|
542
692
|
.join(' ');
|
|
543
|
-
if (asset.tag === 'script') {
|
|
544
|
-
return `<script ${attributeString}></script>`;
|
|
545
|
-
}
|
|
546
693
|
if (asset.tag === 'link') {
|
|
547
694
|
return `<link ${attributeString}>`;
|
|
548
695
|
}
|
|
549
696
|
if (asset.tag === 'style') {
|
|
550
697
|
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
551
698
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
${rendered}
|
|
568
|
-
</html>
|
|
569
|
-
`;
|
|
570
|
-
res.write(html);
|
|
571
|
-
res.write(`
|
|
572
|
-
<script type="module" data-hydration="loading" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
573
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
574
|
-
main('${matched.loadingPage?.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
575
|
-
</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>
|
|
576
714
|
`);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
583
|
-
toRender: 'main',
|
|
584
|
-
entry: matched,
|
|
585
|
-
routeParams: params,
|
|
586
|
-
searchParams,
|
|
587
|
-
req: toWebRequest(event),
|
|
588
|
-
cspNonce,
|
|
589
|
-
});
|
|
590
|
-
for (const asset of documentAssets) {
|
|
591
|
-
assets.push(asset);
|
|
592
|
-
}
|
|
593
|
-
clientHydrationScript = `
|
|
594
|
-
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
595
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
596
|
-
main('${matched.mainPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
597
|
-
</script>
|
|
598
|
-
`;
|
|
599
|
-
html = rendered;
|
|
600
|
-
meta = {
|
|
601
|
-
...meta,
|
|
602
|
-
...documentMeta
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
catch (e1) {
|
|
607
|
-
if (e1 instanceof RedirectError ||
|
|
608
|
-
e1.name === 'RedirectError') {
|
|
609
|
-
throw e1;
|
|
610
|
-
}
|
|
611
|
-
try {
|
|
612
|
-
const errorPage = matched.errorPage;
|
|
613
|
-
if (!errorPage) {
|
|
614
|
-
throw e1;
|
|
615
|
-
}
|
|
616
|
-
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
617
|
-
toRender: 'error',
|
|
618
|
-
entry: matched,
|
|
619
|
-
routeParams: params,
|
|
620
|
-
searchParams,
|
|
621
|
-
req: toWebRequest(event),
|
|
622
|
-
cspNonce,
|
|
623
|
-
});
|
|
624
|
-
for (const asset of documentAssets) {
|
|
625
|
-
assets.push(asset);
|
|
626
|
-
}
|
|
627
|
-
clientHydrationScript = `
|
|
628
|
-
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
629
|
-
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
630
|
-
main('${errorPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
631
|
-
</script>
|
|
632
|
-
`;
|
|
633
|
-
html = rendered;
|
|
634
|
-
meta = {
|
|
635
|
-
...meta,
|
|
636
|
-
...documentMeta
|
|
637
|
-
};
|
|
638
|
-
res.statusCode = 500;
|
|
639
|
-
}
|
|
640
|
-
catch (e2) {
|
|
641
|
-
throw e1;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
if (loading) {
|
|
645
|
-
const assetsHtml = assets.map((asset) => {
|
|
646
|
-
const attributeString = Object.entries(asset.attrs)
|
|
647
|
-
.map(([key, value]) => `${key}="${value}"`)
|
|
648
|
-
.join(' ');
|
|
649
|
-
if (asset.tag === 'link') {
|
|
650
|
-
return `<link ${attributeString}>`;
|
|
651
|
-
}
|
|
652
|
-
if (asset.tag === 'style') {
|
|
653
|
-
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
715
|
+
push(manifestHtml);
|
|
716
|
+
push(clientHydrationScript);
|
|
717
|
+
controller.close();
|
|
718
|
+
return;
|
|
654
719
|
}
|
|
655
|
-
|
|
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
|
-
if (asset.tag === 'link') {
|
|
681
|
-
return `<link ${attributeString}>`;
|
|
682
|
-
}
|
|
683
|
-
if (asset.tag === 'style') {
|
|
684
|
-
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
685
|
-
}
|
|
686
|
-
}).join('\n');
|
|
687
|
-
const transformHtml = template
|
|
688
|
-
.replace('<!--app-head-->', `${generateHtmlHead(meta)}\n${assetsHtml}\n${hydrationScript({ nonce: cspNonce })}`)
|
|
689
|
-
.replace('<!--app-body-->', (html ?? '') + manifestHtml + clientHydrationScript);
|
|
690
|
-
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;
|
|
691
745
|
}
|
|
692
746
|
catch (e) {
|
|
693
|
-
if (e instanceof RedirectError ||
|
|
694
|
-
e.name === 'RedirectError') {
|
|
695
|
-
res.statusCode = 302;
|
|
696
|
-
res.setHeader('Location', e.message);
|
|
697
|
-
return res.end('Redirecting...');
|
|
698
|
-
}
|
|
699
747
|
console.error(e);
|
|
700
|
-
|
|
701
|
-
return res.end('Internal Server Error');
|
|
748
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
702
749
|
}
|
|
703
750
|
});
|
|
704
751
|
export default handler;
|
package/utils/cache.js
CHANGED
|
@@ -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({
|