solidstep 0.3.5 → 0.4.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 +235 -5
- package/index.d.ts.map +1 -1
- package/index.js +20 -3
- package/package.json +143 -72
- package/server.d.ts.map +1 -1
- package/server.js +305 -219
- package/utils/cache.d.ts.map +1 -1
- package/utils/cache.js +8 -19
- package/utils/components/form.d.ts +47 -0
- package/utils/components/form.d.ts.map +1 -0
- package/utils/components/form.js +95 -0
- package/utils/csp.d.ts.map +1 -1
- package/utils/csp.js +0 -2
- package/utils/hooks/action-state.d.ts +19 -1
- package/utils/hooks/action-state.d.ts.map +1 -1
- package/utils/hooks/action-state.js +24 -4
- package/utils/hooks/form-status.d.ts +12 -0
- package/utils/hooks/form-status.d.ts.map +1 -0
- package/utils/hooks/form-status.js +16 -0
- package/utils/instrumentation-noop.d.ts +3 -0
- package/utils/instrumentation-noop.d.ts.map +1 -0
- package/utils/instrumentation-noop.js +3 -0
- package/utils/instrumentation.d.ts +94 -0
- package/utils/instrumentation.d.ts.map +1 -0
- package/utils/instrumentation.js +132 -0
- package/utils/loader.d.ts.map +1 -1
- package/utils/loader.js +5 -11
- package/utils/middleware.d.ts +43 -0
- package/utils/middleware.d.ts.map +1 -0
- package/utils/middleware.js +48 -0
- package/utils/path-router.js +10 -10
- package/utils/router.d.ts.map +1 -1
- package/utils/router.js +3 -1
- package/utils/server-action.server.d.ts.map +1 -1
- package/utils/server-action.server.js +56 -22
package/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eventHandler, toWebRequest, setHeader, setResponseStatus, } from 'vinxi/http';
|
|
1
|
+
import { eventHandler, getResponseStatus, 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';
|
|
@@ -9,9 +9,14 @@ import { readFile } from 'node:fs/promises';
|
|
|
9
9
|
import { dirname } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { createNode, insertRoute, matchRoute, } from './utils/path-router';
|
|
12
|
-
|
|
12
|
+
import { loadInstrumentation, getInstrumentation, safeExecuteHook, createRequestContext, createResponseContext, } from './utils/instrumentation';
|
|
13
|
+
let instrumentationReady = null;
|
|
14
|
+
// Module cache for dynamically imported modules — skipped in dev so HMR invalidations are respected
|
|
13
15
|
const moduleCache = new Map();
|
|
14
16
|
const getCachedModule = async (importFn) => {
|
|
17
|
+
if (import.meta.env.DEV) {
|
|
18
|
+
return importFn.import();
|
|
19
|
+
}
|
|
15
20
|
const key = importFn.src;
|
|
16
21
|
if (moduleCache.has(key)) {
|
|
17
22
|
return moduleCache.get(key);
|
|
@@ -59,9 +64,15 @@ const createRouteManifest = async () => {
|
|
|
59
64
|
errorPagesMap.set(path, fileRoute);
|
|
60
65
|
}
|
|
61
66
|
if (fileRoute.type === 'group') {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
// `fileRoute.parent` is already a clean route path (e.g. '/dashboard'
|
|
68
|
+
// or '' for the root), so it must NOT go through getNormalizedPath,
|
|
69
|
+
// which strips a leading prefix. Normalize it the same way page
|
|
70
|
+
// routePaths are (drop '(group)' segments, ensure a leading slash) so
|
|
71
|
+
// nested parallel routes attach to their parent route.
|
|
72
|
+
const parentPath = `/${(fileRoute.parent || '')
|
|
73
|
+
.split('/')
|
|
74
|
+
.filter((s) => s && !s.startsWith('('))
|
|
75
|
+
.join('/')}`;
|
|
65
76
|
const existing = groupsMap.get(parentPath) || [];
|
|
66
77
|
existing.push(fileRoute);
|
|
67
78
|
groupsMap.set(parentPath, existing);
|
|
@@ -207,14 +218,52 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
207
218
|
const loaderData = {};
|
|
208
219
|
const clientManifest = getManifest('client');
|
|
209
220
|
const assets = [];
|
|
221
|
+
// Select the page variant being rendered up front so its loader can be
|
|
222
|
+
// pre-resolved alongside the layout loaders.
|
|
223
|
+
const pageToRender = toRender === 'loading'
|
|
224
|
+
? entry.loadingPage
|
|
225
|
+
: toRender === 'error'
|
|
226
|
+
? entry.errorPage
|
|
227
|
+
: toRender === 'not-found'
|
|
228
|
+
? entry.notFoundPage
|
|
229
|
+
: entry.mainPage;
|
|
230
|
+
// Run every layout loader and the page loader concurrently instead of
|
|
231
|
+
// sequentially down the layout chain. Results are keyed by manifestPath and
|
|
232
|
+
// applied in tree order below, so loaderData ordering and per-node data are
|
|
233
|
+
// unchanged — only the awaits now overlap.
|
|
234
|
+
const loaderTargets = [];
|
|
235
|
+
for (const layout of entry.layouts) {
|
|
236
|
+
if (layout.loader) {
|
|
237
|
+
loaderTargets.push({
|
|
238
|
+
manifestPath: layout.manifestPath,
|
|
239
|
+
loader: layout.loader,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (pageToRender?.loader) {
|
|
244
|
+
loaderTargets.push({
|
|
245
|
+
manifestPath: pageToRender.manifestPath,
|
|
246
|
+
loader: pageToRender.loader,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// A `$loader` import is created for every layout/page node, even when the
|
|
250
|
+
// file exports no loader — in that case the picked module is empty and has
|
|
251
|
+
// no `loader` export, so we skip it. Only nodes whose loader actually ran
|
|
252
|
+
// get an entry here, which is how the closures below decide whether to
|
|
253
|
+
// populate loaderData (matching the previous in-closure behavior).
|
|
254
|
+
const resolvedLoaderData = new Map();
|
|
255
|
+
await Promise.all(loaderTargets.map(async ({ manifestPath, loader }) => {
|
|
256
|
+
const { loader: loaderFn } = await getCachedModule(loader);
|
|
257
|
+
if (!loaderFn)
|
|
258
|
+
return;
|
|
259
|
+
const result = await loaderFn.loader(req);
|
|
260
|
+
resolvedLoaderData.set(manifestPath, result.data || {});
|
|
261
|
+
}));
|
|
210
262
|
const compose = entry.layouts.reduceRight((children, layout, index) => async () => {
|
|
211
263
|
const moduleSrc = `${layout.layout.src}&pick=$css`;
|
|
212
264
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
213
265
|
assets.push(...moduleAssets);
|
|
214
266
|
const { default: layoutModule } = await getCachedModule(layout.layout);
|
|
215
|
-
const { loader: layoutLoader } = layout.loader
|
|
216
|
-
? await getCachedModule(layout.loader)
|
|
217
|
-
: { loader: null };
|
|
218
267
|
const { generateMeta: generateMetaPage } = layout.generateMeta
|
|
219
268
|
? await getCachedModule(layout.generateMeta)
|
|
220
269
|
: { generateMeta: null };
|
|
@@ -231,9 +280,8 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
231
280
|
};
|
|
232
281
|
}
|
|
233
282
|
}
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
data = result.data || {};
|
|
283
|
+
if (resolvedLoaderData.has(layout.manifestPath)) {
|
|
284
|
+
data = resolvedLoaderData.get(layout.manifestPath);
|
|
237
285
|
loaderData[layout.manifestPath] = data;
|
|
238
286
|
}
|
|
239
287
|
const slots = {};
|
|
@@ -276,27 +324,16 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, pageOpt
|
|
|
276
324
|
},
|
|
277
325
|
});
|
|
278
326
|
}, async () => {
|
|
279
|
-
const pageToRender = toRender === 'loading'
|
|
280
|
-
? entry.loadingPage
|
|
281
|
-
: toRender === 'error'
|
|
282
|
-
? entry.errorPage
|
|
283
|
-
: toRender === 'not-found'
|
|
284
|
-
? entry.notFoundPage
|
|
285
|
-
: entry.mainPage;
|
|
286
327
|
const moduleSrc = `${pageToRender.page.src}&pick=$css`;
|
|
287
328
|
const moduleAssets = await clientManifest.inputs[moduleSrc].assets();
|
|
288
329
|
assets.push(...moduleAssets);
|
|
289
330
|
const { default: page } = await getCachedModule(pageToRender.page);
|
|
290
|
-
const { loader: pageLoader } = pageToRender.loader
|
|
291
|
-
? await getCachedModule(pageToRender.loader)
|
|
292
|
-
: { loader: null };
|
|
293
331
|
const { generateMeta } = pageToRender.generateMeta
|
|
294
332
|
? await getCachedModule(pageToRender.generateMeta)
|
|
295
333
|
: { generateMeta: null };
|
|
296
334
|
let data = {};
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
data = result.data || {};
|
|
335
|
+
if (resolvedLoaderData.has(pageToRender.manifestPath)) {
|
|
336
|
+
data = resolvedLoaderData.get(pageToRender.manifestPath);
|
|
300
337
|
loaderData[pageToRender.manifestPath] = data;
|
|
301
338
|
}
|
|
302
339
|
if (generateMeta) {
|
|
@@ -364,9 +401,16 @@ const onStart = async () => {
|
|
|
364
401
|
catch (e) {
|
|
365
402
|
console.error('Error creating route manifest:', e);
|
|
366
403
|
}
|
|
404
|
+
// Load instrumentation
|
|
405
|
+
const instrumentation = await loadInstrumentation();
|
|
406
|
+
if (instrumentation?.register) {
|
|
407
|
+
await safeExecuteHook('register', instrumentation.register);
|
|
408
|
+
}
|
|
367
409
|
};
|
|
368
|
-
onStart();
|
|
410
|
+
instrumentationReady = onStart();
|
|
369
411
|
const handler = eventHandler(async (event) => {
|
|
412
|
+
if (instrumentationReady)
|
|
413
|
+
await instrumentationReady;
|
|
370
414
|
const req = toWebRequest(event);
|
|
371
415
|
try {
|
|
372
416
|
if (req.url.includes('/.well-known/appspecific/com.chrome.devtools.json')) {
|
|
@@ -390,21 +434,45 @@ const handler = eventHandler(async (event) => {
|
|
|
390
434
|
const matched = match?.handler;
|
|
391
435
|
const params = match?.params || {};
|
|
392
436
|
if (matched && matched.type === 'route') {
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
437
|
+
const inst = getInstrumentation();
|
|
438
|
+
const reqCtx = createRequestContext(req, {
|
|
439
|
+
routePath: matched.routePath || 'unknown',
|
|
440
|
+
routeType: 'api',
|
|
441
|
+
params,
|
|
442
|
+
searchParams,
|
|
443
|
+
});
|
|
444
|
+
await safeExecuteHook('onRequest', inst?.onRequest, req, reqCtx);
|
|
445
|
+
try {
|
|
446
|
+
const routeModule = await getCachedModule(matched.handler);
|
|
447
|
+
const reqMethod = req.method?.toUpperCase();
|
|
448
|
+
if (reqMethod) {
|
|
449
|
+
const handler = routeModule[reqMethod];
|
|
450
|
+
if (typeof handler === 'function') {
|
|
451
|
+
const result = await handler(req, {
|
|
452
|
+
params: params,
|
|
453
|
+
searchParams: searchParams,
|
|
454
|
+
});
|
|
455
|
+
const respCtx = createResponseContext(reqCtx, getResponseStatus(event) || 200);
|
|
456
|
+
await safeExecuteHook('onResponseEnd', inst?.onResponseEnd, req, respCtx);
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
throw new Error(`Method ${reqMethod} not implemented in ${matched.handler.src}`);
|
|
403
460
|
}
|
|
404
|
-
throw new Error(`
|
|
461
|
+
throw new Error(`Unsupported request method: ${reqMethod}`);
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
await safeExecuteHook('onRequestError', inst?.onRequestError, error, req, reqCtx);
|
|
465
|
+
throw error;
|
|
405
466
|
}
|
|
406
|
-
throw new Error(`Unsupported request method: ${reqMethod}`);
|
|
407
467
|
}
|
|
468
|
+
const inst = getInstrumentation();
|
|
469
|
+
const reqCtx = createRequestContext(req, {
|
|
470
|
+
routePath: matched ? pathnamePart : '/not-found',
|
|
471
|
+
routeType: matched ? 'page' : 'not-found',
|
|
472
|
+
params,
|
|
473
|
+
searchParams,
|
|
474
|
+
});
|
|
475
|
+
await safeExecuteHook('onRequest', inst?.onRequest, req, reqCtx);
|
|
408
476
|
let loading = false;
|
|
409
477
|
let html = undefined;
|
|
410
478
|
let meta = {
|
|
@@ -444,95 +512,97 @@ const handler = eventHandler(async (event) => {
|
|
|
444
512
|
async start(controller) {
|
|
445
513
|
const encoder = new TextEncoder();
|
|
446
514
|
const push = (text) => controller.enqueue(encoder.encode(text));
|
|
515
|
+
let streamError = null;
|
|
447
516
|
try {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
517
|
+
try {
|
|
518
|
+
if (!matched) {
|
|
519
|
+
try {
|
|
520
|
+
const match = matchRoute(routeManifest, '/');
|
|
521
|
+
const notFoundEntry = match.handler;
|
|
522
|
+
if (!notFoundEntry) {
|
|
523
|
+
throw new Error('No not-found page configured');
|
|
524
|
+
}
|
|
525
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
526
|
+
toRender: 'not-found',
|
|
527
|
+
entry: notFoundEntry,
|
|
528
|
+
routeParams: {},
|
|
529
|
+
searchParams: {},
|
|
530
|
+
req: req,
|
|
531
|
+
pageOptions: {},
|
|
532
|
+
cspNonce,
|
|
533
|
+
});
|
|
534
|
+
assets.push(...documentAssets);
|
|
535
|
+
clientHydrationScript = `
|
|
466
536
|
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
467
537
|
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
468
538
|
main('/not-found/',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
469
539
|
</script>
|
|
470
540
|
`;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
catch (e) {
|
|
479
|
-
console.error('404 module not found:', e);
|
|
480
|
-
setResponseStatus(404);
|
|
481
|
-
push('Not Found');
|
|
482
|
-
controller.close();
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
else {
|
|
487
|
-
const { options } = matched
|
|
488
|
-
.mainPage.options
|
|
489
|
-
? await getCachedModule(matched.mainPage
|
|
490
|
-
.options)
|
|
491
|
-
: { options: {} };
|
|
492
|
-
if (options?.responseHeaders) {
|
|
493
|
-
const headers = options.responseHeaders;
|
|
494
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
495
|
-
setHeader(key, value);
|
|
541
|
+
html = rendered;
|
|
542
|
+
meta = {
|
|
543
|
+
...meta,
|
|
544
|
+
...documentMeta,
|
|
545
|
+
};
|
|
546
|
+
setResponseStatus(404);
|
|
496
547
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
548
|
+
catch (e) {
|
|
549
|
+
console.error('404 module not found:', e);
|
|
550
|
+
setResponseStatus(404);
|
|
551
|
+
push('Not Found');
|
|
552
|
+
controller.close();
|
|
553
|
+
return;
|
|
501
554
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
.map((asset) => {
|
|
514
|
-
const attributeString = Object.entries(asset.attrs)
|
|
515
|
-
.map(([key, value]) => `${key}="${value}"`)
|
|
516
|
-
.join(' ');
|
|
517
|
-
if (asset.tag === 'script') {
|
|
518
|
-
return `<script ${attributeString}></script>`;
|
|
519
|
-
}
|
|
520
|
-
if (asset.tag === 'link') {
|
|
521
|
-
return `<link ${attributeString}>`;
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
const { options } = matched
|
|
558
|
+
.mainPage.options
|
|
559
|
+
? await getCachedModule(matched.mainPage
|
|
560
|
+
.options)
|
|
561
|
+
: { options: {} };
|
|
562
|
+
if (options?.responseHeaders) {
|
|
563
|
+
const headers = options.responseHeaders;
|
|
564
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
565
|
+
setHeader(key, value);
|
|
522
566
|
}
|
|
523
|
-
|
|
524
|
-
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
if (!matched.loadingPage) {
|
|
570
|
+
throw new Error('No loading page');
|
|
525
571
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
572
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
573
|
+
toRender: 'loading',
|
|
574
|
+
entry: matched,
|
|
575
|
+
routeParams: params,
|
|
576
|
+
searchParams,
|
|
577
|
+
req: req,
|
|
578
|
+
pageOptions: options,
|
|
579
|
+
cspNonce,
|
|
580
|
+
});
|
|
581
|
+
const assetsHtml = assets
|
|
582
|
+
.concat(documentAssets)
|
|
583
|
+
.map((asset) => {
|
|
584
|
+
const attributeString = Object.entries(asset.attrs)
|
|
585
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
586
|
+
.join(' ');
|
|
587
|
+
if (asset.tag === 'script') {
|
|
588
|
+
return `<script ${attributeString}></script>`;
|
|
589
|
+
}
|
|
590
|
+
if (asset.tag === 'link') {
|
|
591
|
+
return `<link ${attributeString}>`;
|
|
592
|
+
}
|
|
593
|
+
if (asset.tag === 'style') {
|
|
594
|
+
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
595
|
+
}
|
|
596
|
+
})
|
|
597
|
+
.join('\n');
|
|
598
|
+
const html = `
|
|
529
599
|
<!doctype html>
|
|
530
600
|
<html lang="en">
|
|
531
601
|
<head>
|
|
532
602
|
${generateHtmlHead({
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
603
|
+
...meta,
|
|
604
|
+
...documentMeta,
|
|
605
|
+
})}
|
|
536
606
|
${assetsHtml}
|
|
537
607
|
${hydrationScript({ nonce: cspNonce })}
|
|
538
608
|
</head>
|
|
@@ -542,154 +612,170 @@ const handler = eventHandler(async (event) => {
|
|
|
542
612
|
${rendered}
|
|
543
613
|
</html>
|
|
544
614
|
`;
|
|
545
|
-
|
|
546
|
-
|
|
615
|
+
push(html);
|
|
616
|
+
push(`
|
|
547
617
|
<script type="module" data-hydration="loading" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
548
618
|
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
549
619
|
main('${matched.loadingPage?.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
550
620
|
</script>
|
|
551
621
|
`);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
622
|
+
loading = true;
|
|
623
|
+
}
|
|
624
|
+
catch (e) {
|
|
625
|
+
// skip
|
|
626
|
+
}
|
|
627
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
628
|
+
toRender: 'main',
|
|
629
|
+
entry: matched,
|
|
630
|
+
routeParams: params,
|
|
631
|
+
searchParams,
|
|
632
|
+
req: req,
|
|
633
|
+
pageOptions: options,
|
|
634
|
+
cspNonce,
|
|
635
|
+
});
|
|
636
|
+
assets.push(...documentAssets);
|
|
637
|
+
clientHydrationScript = `
|
|
568
638
|
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
569
639
|
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
570
640
|
main('${matched.mainPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
571
641
|
</script>
|
|
572
642
|
`;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
catch (e1) {
|
|
582
|
-
if (e1 instanceof RedirectError ||
|
|
583
|
-
e1.name === 'RedirectError') {
|
|
584
|
-
setHeader('Location', e1.message);
|
|
585
|
-
setResponseStatus(302);
|
|
586
|
-
controller.close();
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
if (import.meta.env.DEV) {
|
|
590
|
-
console.error(e1);
|
|
643
|
+
html = rendered;
|
|
644
|
+
meta = {
|
|
645
|
+
...meta,
|
|
646
|
+
...documentMeta,
|
|
647
|
+
};
|
|
648
|
+
setResponseStatus(200);
|
|
649
|
+
}
|
|
591
650
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
651
|
+
catch (e1) {
|
|
652
|
+
streamError = e1;
|
|
653
|
+
if (e1 instanceof RedirectError ||
|
|
654
|
+
e1.name === 'RedirectError') {
|
|
655
|
+
setHeader('Location', e1.message);
|
|
656
|
+
setResponseStatus(302);
|
|
657
|
+
controller.close();
|
|
658
|
+
return;
|
|
597
659
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
660
|
+
if (import.meta.env.DEV) {
|
|
661
|
+
console.error(e1);
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
const errorPage = matched
|
|
665
|
+
.errorPage;
|
|
666
|
+
if (!errorPage) {
|
|
667
|
+
throw e1;
|
|
668
|
+
}
|
|
669
|
+
const { rendered, documentMeta, documentAssets, loaderData, } = await render({
|
|
670
|
+
toRender: 'error',
|
|
671
|
+
entry: matched,
|
|
672
|
+
routeParams: params,
|
|
673
|
+
searchParams,
|
|
674
|
+
req: req,
|
|
675
|
+
pageOptions: {},
|
|
676
|
+
cspNonce,
|
|
677
|
+
error: e1,
|
|
678
|
+
});
|
|
679
|
+
assets.push(...documentAssets);
|
|
680
|
+
clientHydrationScript = `
|
|
610
681
|
<script type="module" ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
611
682
|
import main from '${clientManifest.inputs[clientManifest.handler].output.path}';
|
|
612
683
|
main('${errorPage.manifestPath}',${JSON.stringify(params)},${JSON.stringify(searchParams)}, ${JSON.stringify(loaderData)});
|
|
613
684
|
</script>
|
|
614
685
|
`;
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
686
|
+
html = rendered;
|
|
687
|
+
meta = {
|
|
688
|
+
...meta,
|
|
689
|
+
...documentMeta,
|
|
690
|
+
};
|
|
691
|
+
// statusCode = 500;
|
|
692
|
+
setResponseStatus(500);
|
|
693
|
+
}
|
|
694
|
+
catch (e2) {
|
|
695
|
+
throw e1;
|
|
696
|
+
}
|
|
622
697
|
}
|
|
623
|
-
|
|
624
|
-
|
|
698
|
+
if (loading) {
|
|
699
|
+
const assetsHtml = assets
|
|
700
|
+
.map((asset) => {
|
|
701
|
+
const attributeString = Object.entries(asset.attrs)
|
|
702
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
703
|
+
.join(' ');
|
|
704
|
+
if (asset.tag === 'link') {
|
|
705
|
+
return `<link ${attributeString}>`;
|
|
706
|
+
}
|
|
707
|
+
if (asset.tag === 'style') {
|
|
708
|
+
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
709
|
+
}
|
|
710
|
+
return '';
|
|
711
|
+
})
|
|
712
|
+
.join('\n');
|
|
713
|
+
push(`<template id="__page_html__">${html}</template>`);
|
|
714
|
+
push(`
|
|
715
|
+
<script ${cspNonce ? `nonce="${cspNonce}"` : ''}>
|
|
716
|
+
const head = document.querySelector('head');
|
|
717
|
+
const scripts = Array.from(head.querySelectorAll('script'));
|
|
718
|
+
head.innerHTML = ${JSON.stringify(generateHtmlHead(meta) + assetsHtml)};
|
|
719
|
+
scripts.forEach(script => {
|
|
720
|
+
head.appendChild(script);
|
|
721
|
+
});
|
|
722
|
+
document.querySelector('script[data-hydration="loading"]')?.remove();
|
|
723
|
+
const loading = document.querySelector('body');
|
|
724
|
+
const template = document.getElementById('__page_html__');
|
|
725
|
+
loading.innerHTML = template.innerHTML;
|
|
726
|
+
template.remove();
|
|
727
|
+
</script>
|
|
728
|
+
`);
|
|
729
|
+
push(manifestHtml);
|
|
730
|
+
push(clientHydrationScript);
|
|
731
|
+
controller.close();
|
|
732
|
+
return;
|
|
625
733
|
}
|
|
626
|
-
}
|
|
627
|
-
if (loading) {
|
|
628
734
|
const assetsHtml = assets
|
|
629
735
|
.map((asset) => {
|
|
630
736
|
const attributeString = Object.entries(asset.attrs)
|
|
631
737
|
.map(([key, value]) => `${key}="${value}"`)
|
|
632
738
|
.join(' ');
|
|
739
|
+
if (asset.tag === 'script') {
|
|
740
|
+
return `<script ${attributeString} ${cspNonce ? `nonce="${cspNonce}"` : ''}></script>`;
|
|
741
|
+
}
|
|
633
742
|
if (asset.tag === 'link') {
|
|
634
743
|
return `<link ${attributeString}>`;
|
|
635
744
|
}
|
|
636
745
|
if (asset.tag === 'style') {
|
|
637
746
|
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
638
747
|
}
|
|
639
|
-
return '';
|
|
640
748
|
})
|
|
641
749
|
.join('\n');
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const scripts = Array.from(head.querySelectorAll('script'));
|
|
647
|
-
head.innerHTML = \`${generateHtmlHead(meta) + assetsHtml}\`;
|
|
648
|
-
scripts.forEach(script => {
|
|
649
|
-
head.appendChild(script);
|
|
650
|
-
});
|
|
651
|
-
document.querySelector('script[data-hydration="loading"]')?.remove();
|
|
652
|
-
const loading = document.querySelector('body');
|
|
653
|
-
const template = document.getElementById('__page_html__');
|
|
654
|
-
loading.innerHTML = template.innerHTML;
|
|
655
|
-
template.remove();
|
|
656
|
-
</script>
|
|
657
|
-
`);
|
|
658
|
-
push(manifestHtml);
|
|
659
|
-
push(clientHydrationScript);
|
|
750
|
+
const transformHtml = template
|
|
751
|
+
.replace('<!--app-head-->', `${generateHtmlHead(meta)}\n${assetsHtml}\n${hydrationScript({ nonce: cspNonce })}`)
|
|
752
|
+
.replace('<!--app-body-->', (html ?? '') + manifestHtml + clientHydrationScript);
|
|
753
|
+
push(transformHtml);
|
|
660
754
|
controller.close();
|
|
661
755
|
return;
|
|
662
756
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
if (asset.tag === 'style') {
|
|
675
|
-
return `<style ${attributeString}>${asset.children || ''}</style>`;
|
|
757
|
+
catch (error) {
|
|
758
|
+
streamError = streamError ?? error;
|
|
759
|
+
throw error;
|
|
760
|
+
}
|
|
761
|
+
finally {
|
|
762
|
+
const statusCode = getResponseStatus(event) || 200;
|
|
763
|
+
const respCtx = createResponseContext(reqCtx, statusCode);
|
|
764
|
+
await safeExecuteHook('onResponseEnd', inst?.onResponseEnd, req, respCtx);
|
|
765
|
+
if (streamError) {
|
|
766
|
+
await safeExecuteHook('onRequestError', inst?.onRequestError, streamError, req, reqCtx);
|
|
676
767
|
}
|
|
677
|
-
}
|
|
678
|
-
.join('\n');
|
|
679
|
-
const transformHtml = template
|
|
680
|
-
.replace('<!--app-head-->', `${generateHtmlHead(meta)}\n${assetsHtml}\n${hydrationScript({ nonce: cspNonce })}`)
|
|
681
|
-
.replace('<!--app-body-->', (html ?? '') + manifestHtml + clientHydrationScript);
|
|
682
|
-
push(transformHtml);
|
|
683
|
-
controller.close();
|
|
684
|
-
return;
|
|
768
|
+
}
|
|
685
769
|
},
|
|
686
770
|
});
|
|
687
771
|
return stream;
|
|
688
772
|
}
|
|
689
773
|
catch (e) {
|
|
690
|
-
if (e instanceof RedirectError ||
|
|
691
|
-
|
|
692
|
-
|
|
774
|
+
if (e instanceof RedirectError || e.name === 'RedirectError') {
|
|
775
|
+
return new Response('', {
|
|
776
|
+
status: 302,
|
|
777
|
+
headers: { Location: e.message },
|
|
778
|
+
});
|
|
693
779
|
}
|
|
694
780
|
console.error(e);
|
|
695
781
|
return new Response('Internal Server Error', { status: 500 });
|