@varlabs/create-solidstep 0.1.6 → 0.1.7

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