@webtypen/webframez-react 0.0.47 → 0.0.49

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/dist/index.js CHANGED
@@ -23,6 +23,8 @@ function createHTMLShell(options = {}) {
23
23
  buildId = "",
24
24
  headTags = "",
25
25
  bodyClassName = "",
26
+ bodyStartHtml = "",
27
+ bodyEndHtml = "",
26
28
  rootHtml = "",
27
29
  initialFlightData = "",
28
30
  basename = "",
@@ -72,7 +74,9 @@ function createHTMLShell(options = {}) {
72
74
  ${headTags}
73
75
  </head>
74
76
  <body${bodyClassName ? ` class="${bodyClassName.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}"` : ""}>
77
+ ${bodyStartHtml}
75
78
  <div id="root">${rootHtml}</div>
79
+ ${bodyEndHtml}
76
80
  <script>window.__RSC_ENDPOINT = "${rscEndpoint}";</script>
77
81
  <script>window.__RSC_BASENAME = "${basename}";</script>
78
82
  <script>window.__RSC_ROUTE_BASE_PATH = "${routeBasePath}";</script>
@@ -215,6 +219,12 @@ function normalizeHeadConfig(head, inheritedBasename) {
215
219
  href: resolveHeadAssetUrl(link.href, effectiveBasename)
216
220
  }));
217
221
  }
222
+ if (normalizedHead.scripts) {
223
+ normalizedHead.scripts = normalizedHead.scripts.map((script) => ({
224
+ ...script,
225
+ ...script.src ? { src: resolveHeadAssetUrl(script.src, effectiveBasename) } : {}
226
+ }));
227
+ }
218
228
  return normalizedHead;
219
229
  }
220
230
 
@@ -467,7 +477,9 @@ function matchRoute(entry, pathname) {
467
477
  function mergeHead(...configs) {
468
478
  const merged = {
469
479
  meta: [],
470
- links: []
480
+ links: [],
481
+ scripts: [],
482
+ html: []
471
483
  };
472
484
  for (const candidate of configs) {
473
485
  const config = normalizeHeadConfig(candidate, merged.basename);
@@ -502,9 +514,28 @@ function mergeHead(...configs) {
502
514
  if (config.links) {
503
515
  merged.links?.push(...config.links);
504
516
  }
517
+ if (config.scripts) {
518
+ merged.scripts?.push(...config.scripts);
519
+ }
520
+ if (config.html) {
521
+ merged.html?.push(...Array.isArray(config.html) ? config.html : [config.html]);
522
+ }
523
+ if (config.bodyStartHtml) {
524
+ merged.bodyStartHtml = [merged.bodyStartHtml, config.bodyStartHtml].filter(Boolean).join("\n");
525
+ }
526
+ if (config.bodyEndHtml) {
527
+ merged.bodyEndHtml = [merged.bodyEndHtml, config.bodyEndHtml].filter(Boolean).join("\n");
528
+ }
505
529
  }
506
530
  return merged;
507
531
  }
532
+ function renderGenericAttributes(entry, excluded = []) {
533
+ return Object.entries(entry).filter(
534
+ ([key, value]) => !excluded.includes(key) && value !== void 0 && value !== null && value !== false
535
+ ).map(
536
+ ([key, value]) => value === true ? key : `${key}="${escapeHtml(String(value))}"`
537
+ ).join(" ");
538
+ }
508
539
  function renderHeadToString(head) {
509
540
  const normalizedHead = normalizeHeadConfig(head) ?? head;
510
541
  const tags = [];
@@ -526,6 +557,23 @@ function renderHeadToString(head) {
526
557
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
527
558
  tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
528
559
  }
560
+ for (const script of normalizedHead.scripts ?? []) {
561
+ if (script.html) {
562
+ tags.push(script.html);
563
+ continue;
564
+ }
565
+ const attrs = renderGenericAttributes(script, [
566
+ "content",
567
+ "html"
568
+ ]);
569
+ const content = script.content ? String(script.content) : "";
570
+ tags.push(`<script ${createManagedAttributes()} ${attrs}>${content}</script>`);
571
+ }
572
+ for (const html of normalizedHead.html ?? []) {
573
+ if (typeof html === "string" && html.trim() !== "") {
574
+ tags.push(html);
575
+ }
576
+ }
529
577
  return tags.join("\n");
530
578
  }
531
579
  function clearModuleCache(modulePath, rootDir, visited = /* @__PURE__ */ new Set()) {
@@ -611,6 +659,7 @@ function isRouteAbort(value) {
611
659
  function createFileRouter(options) {
612
660
  installForcedPackageResolution();
613
661
  const pagesDir = options.pagesDir;
662
+ const onData = options.onData;
614
663
  const layoutPath = path2.join(pagesDir, "layout.js");
615
664
  const errorPath = path2.join(pagesDir, "errors.js");
616
665
  const middlewaresPath = path2.join(pagesDir, "middlewares.js");
@@ -775,10 +824,16 @@ function createFileRouter(options) {
775
824
  data: mergeRouteData(middlewareContext.data, pageData)
776
825
  };
777
826
  activeContext = pageContext;
778
- const pageNode = await pageModule.default(pageContext);
779
- const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
780
- const pageHead = await resolveHead(pageModule, pageContext);
781
- const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
827
+ const onDataResult = typeof onData === "function" ? await onData(pageContext) : void 0;
828
+ const eventContext = {
829
+ ...pageContext,
830
+ data: mergeRouteData(pageContext.data, onDataResult)
831
+ };
832
+ activeContext = eventContext;
833
+ const pageNode = await pageModule.default(eventContext);
834
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, eventContext) : void 0;
835
+ const pageHead = await resolveHead(pageModule, eventContext);
836
+ const layoutNode = layoutModule ? await layoutModule.default(eventContext) : null;
782
837
  const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
783
838
  const contextModel = layoutNode ? injectRouteChildren(layoutNode, /* @__PURE__ */ jsx(RouteChildren, {})) : void 0;
784
839
  return {
@@ -787,7 +842,7 @@ function createFileRouter(options) {
787
842
  contextModel,
788
843
  pageModel: pageNode,
789
844
  head: mergeHead(layoutHead, pageHead),
790
- context: pageContext
845
+ context: eventContext
791
846
  };
792
847
  } catch (error) {
793
848
  if (isRouteAbort(error)) {
@@ -1620,7 +1675,7 @@ function createNodeRequestHandler(options) {
1620
1675
  const liveReloadEnabled = options.liveReloadPath !== false && (nodeEnv === "development" || runningInWatchMode);
1621
1676
  const liveReloadPath = !liveReloadEnabled ? "" : options.liveReloadPath ?? `${basePath || ""}/__webframez_live_reload`;
1622
1677
  const liveReloadClients = /* @__PURE__ */ new Set();
1623
- const router = createFileRouter({ pagesDir });
1678
+ const router = createFileRouter({ pagesDir, onData: options.onData });
1624
1679
  const getManifestState = createManifestLoader({
1625
1680
  distRootDir,
1626
1681
  manifestPath,
@@ -1844,6 +1899,8 @@ function createNodeRequestHandler(options) {
1844
1899
  initialFlightData,
1845
1900
  basename: shellBasename,
1846
1901
  routeBasePath: shellRouteBasePath,
1902
+ bodyStartHtml: resolved.head.bodyStartHtml || "",
1903
+ bodyEndHtml: resolved.head.bodyEndHtml || "",
1847
1904
  liveReloadPath: liveReloadPath || void 0,
1848
1905
  liveReloadServerId: liveReloadPath ? devServerId : void 0
1849
1906
  }),
@@ -2088,7 +2145,8 @@ function resolveWebframezReactRouteOptions(routePathValue, options = {}) {
2088
2145
  clientEntryPath: options.clientEntryPath ? resolveFromProjectRoot(options.clientEntryPath) : void 0,
2089
2146
  styleSrcPath: options.styleSrcPath ? resolveFromProjectRoot(options.styleSrcPath) : defaults.styleSrcPath,
2090
2147
  basePath: options.basePath ?? defaults.basePath,
2091
- liveReloadPath: options.liveReloadPath
2148
+ liveReloadPath: options.liveReloadPath,
2149
+ onData: options.onData
2092
2150
  };
2093
2151
  }
2094
2152
  function getRegisteredReactBuildTargets() {
package/dist/router.cjs CHANGED
@@ -103,6 +103,12 @@ function normalizeHeadConfig(head, inheritedBasename) {
103
103
  href: resolveHeadAssetUrl(link.href, effectiveBasename)
104
104
  }));
105
105
  }
106
+ if (normalizedHead.scripts) {
107
+ normalizedHead.scripts = normalizedHead.scripts.map((script) => ({
108
+ ...script,
109
+ ...script.src ? { src: resolveHeadAssetUrl(script.src, effectiveBasename) } : {}
110
+ }));
111
+ }
106
112
  return normalizedHead;
107
113
  }
108
114
 
@@ -355,7 +361,9 @@ function matchRoute(entry, pathname) {
355
361
  function mergeHead(...configs) {
356
362
  const merged = {
357
363
  meta: [],
358
- links: []
364
+ links: [],
365
+ scripts: [],
366
+ html: []
359
367
  };
360
368
  for (const candidate of configs) {
361
369
  const config = normalizeHeadConfig(candidate, merged.basename);
@@ -390,9 +398,28 @@ function mergeHead(...configs) {
390
398
  if (config.links) {
391
399
  merged.links?.push(...config.links);
392
400
  }
401
+ if (config.scripts) {
402
+ merged.scripts?.push(...config.scripts);
403
+ }
404
+ if (config.html) {
405
+ merged.html?.push(...Array.isArray(config.html) ? config.html : [config.html]);
406
+ }
407
+ if (config.bodyStartHtml) {
408
+ merged.bodyStartHtml = [merged.bodyStartHtml, config.bodyStartHtml].filter(Boolean).join("\n");
409
+ }
410
+ if (config.bodyEndHtml) {
411
+ merged.bodyEndHtml = [merged.bodyEndHtml, config.bodyEndHtml].filter(Boolean).join("\n");
412
+ }
393
413
  }
394
414
  return merged;
395
415
  }
416
+ function renderGenericAttributes(entry, excluded = []) {
417
+ return Object.entries(entry).filter(
418
+ ([key, value]) => !excluded.includes(key) && value !== void 0 && value !== null && value !== false
419
+ ).map(
420
+ ([key, value]) => value === true ? key : `${key}="${escapeHtml(String(value))}"`
421
+ ).join(" ");
422
+ }
396
423
  function renderHeadToString(head) {
397
424
  const normalizedHead = normalizeHeadConfig(head) ?? head;
398
425
  const tags = [];
@@ -414,6 +441,23 @@ function renderHeadToString(head) {
414
441
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
415
442
  tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
416
443
  }
444
+ for (const script of normalizedHead.scripts ?? []) {
445
+ if (script.html) {
446
+ tags.push(script.html);
447
+ continue;
448
+ }
449
+ const attrs = renderGenericAttributes(script, [
450
+ "content",
451
+ "html"
452
+ ]);
453
+ const content = script.content ? String(script.content) : "";
454
+ tags.push(`<script ${createManagedAttributes()} ${attrs}>${content}</script>`);
455
+ }
456
+ for (const html of normalizedHead.html ?? []) {
457
+ if (typeof html === "string" && html.trim() !== "") {
458
+ tags.push(html);
459
+ }
460
+ }
417
461
  return tags.join("\n");
418
462
  }
419
463
  function clearModuleCache(modulePath, rootDir, visited = /* @__PURE__ */ new Set()) {
@@ -499,6 +543,7 @@ function isRouteAbort(value) {
499
543
  function createFileRouter(options) {
500
544
  installForcedPackageResolution();
501
545
  const pagesDir = options.pagesDir;
546
+ const onData = options.onData;
502
547
  const layoutPath = import_node_path.default.join(pagesDir, "layout.js");
503
548
  const errorPath = import_node_path.default.join(pagesDir, "errors.js");
504
549
  const middlewaresPath = import_node_path.default.join(pagesDir, "middlewares.js");
@@ -663,10 +708,16 @@ function createFileRouter(options) {
663
708
  data: mergeRouteData(middlewareContext.data, pageData)
664
709
  };
665
710
  activeContext = pageContext;
666
- const pageNode = await pageModule.default(pageContext);
667
- const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
668
- const pageHead = await resolveHead(pageModule, pageContext);
669
- const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
711
+ const onDataResult = typeof onData === "function" ? await onData(pageContext) : void 0;
712
+ const eventContext = {
713
+ ...pageContext,
714
+ data: mergeRouteData(pageContext.data, onDataResult)
715
+ };
716
+ activeContext = eventContext;
717
+ const pageNode = await pageModule.default(eventContext);
718
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, eventContext) : void 0;
719
+ const pageHead = await resolveHead(pageModule, eventContext);
720
+ const layoutNode = layoutModule ? await layoutModule.default(eventContext) : null;
670
721
  const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
671
722
  const contextModel = layoutNode ? injectRouteChildren(layoutNode, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RouteChildren, {})) : void 0;
672
723
  return {
@@ -675,7 +726,7 @@ function createFileRouter(options) {
675
726
  contextModel,
676
727
  pageModel: pageNode,
677
728
  head: mergeHead(layoutHead, pageHead),
678
- context: pageContext
729
+ context: eventContext
679
730
  };
680
731
  } catch (error) {
681
732
  if (isRouteAbort(error)) {
package/dist/router.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { ReactNode } from "react";
2
- import type { HeadConfig, RouteContext, RouteRequestContext, RouteSearchParams } from "./types";
2
+ import type {
3
+ HeadConfig,
4
+ RouteContext,
5
+ RouteDataHook,
6
+ RouteRequestContext,
7
+ RouteSearchParams,
8
+ } from "./types";
3
9
 
4
10
  export type ResolvedRoute = {
5
11
  statusCode: number;
@@ -10,7 +16,7 @@ export type ResolvedRoute = {
10
16
  pageModel?: ReactNode;
11
17
  };
12
18
 
13
- export function createFileRouter(options: { pagesDir: string }): {
19
+ export function createFileRouter(options: { pagesDir: string; onData?: RouteDataHook }): {
14
20
  resolve(input: {
15
21
  pathname: string;
16
22
  searchParams: RouteSearchParams;
package/dist/router.js CHANGED
@@ -73,6 +73,12 @@ function normalizeHeadConfig(head, inheritedBasename) {
73
73
  href: resolveHeadAssetUrl(link.href, effectiveBasename)
74
74
  }));
75
75
  }
76
+ if (normalizedHead.scripts) {
77
+ normalizedHead.scripts = normalizedHead.scripts.map((script) => ({
78
+ ...script,
79
+ ...script.src ? { src: resolveHeadAssetUrl(script.src, effectiveBasename) } : {}
80
+ }));
81
+ }
76
82
  return normalizedHead;
77
83
  }
78
84
 
@@ -325,7 +331,9 @@ function matchRoute(entry, pathname) {
325
331
  function mergeHead(...configs) {
326
332
  const merged = {
327
333
  meta: [],
328
- links: []
334
+ links: [],
335
+ scripts: [],
336
+ html: []
329
337
  };
330
338
  for (const candidate of configs) {
331
339
  const config = normalizeHeadConfig(candidate, merged.basename);
@@ -360,9 +368,28 @@ function mergeHead(...configs) {
360
368
  if (config.links) {
361
369
  merged.links?.push(...config.links);
362
370
  }
371
+ if (config.scripts) {
372
+ merged.scripts?.push(...config.scripts);
373
+ }
374
+ if (config.html) {
375
+ merged.html?.push(...Array.isArray(config.html) ? config.html : [config.html]);
376
+ }
377
+ if (config.bodyStartHtml) {
378
+ merged.bodyStartHtml = [merged.bodyStartHtml, config.bodyStartHtml].filter(Boolean).join("\n");
379
+ }
380
+ if (config.bodyEndHtml) {
381
+ merged.bodyEndHtml = [merged.bodyEndHtml, config.bodyEndHtml].filter(Boolean).join("\n");
382
+ }
363
383
  }
364
384
  return merged;
365
385
  }
386
+ function renderGenericAttributes(entry, excluded = []) {
387
+ return Object.entries(entry).filter(
388
+ ([key, value]) => !excluded.includes(key) && value !== void 0 && value !== null && value !== false
389
+ ).map(
390
+ ([key, value]) => value === true ? key : `${key}="${escapeHtml(String(value))}"`
391
+ ).join(" ");
392
+ }
366
393
  function renderHeadToString(head) {
367
394
  const normalizedHead = normalizeHeadConfig(head) ?? head;
368
395
  const tags = [];
@@ -384,6 +411,23 @@ function renderHeadToString(head) {
384
411
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
385
412
  tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
386
413
  }
414
+ for (const script of normalizedHead.scripts ?? []) {
415
+ if (script.html) {
416
+ tags.push(script.html);
417
+ continue;
418
+ }
419
+ const attrs = renderGenericAttributes(script, [
420
+ "content",
421
+ "html"
422
+ ]);
423
+ const content = script.content ? String(script.content) : "";
424
+ tags.push(`<script ${createManagedAttributes()} ${attrs}>${content}</script>`);
425
+ }
426
+ for (const html of normalizedHead.html ?? []) {
427
+ if (typeof html === "string" && html.trim() !== "") {
428
+ tags.push(html);
429
+ }
430
+ }
387
431
  return tags.join("\n");
388
432
  }
389
433
  function clearModuleCache(modulePath, rootDir, visited = /* @__PURE__ */ new Set()) {
@@ -469,6 +513,7 @@ function isRouteAbort(value) {
469
513
  function createFileRouter(options) {
470
514
  installForcedPackageResolution();
471
515
  const pagesDir = options.pagesDir;
516
+ const onData = options.onData;
472
517
  const layoutPath = path.join(pagesDir, "layout.js");
473
518
  const errorPath = path.join(pagesDir, "errors.js");
474
519
  const middlewaresPath = path.join(pagesDir, "middlewares.js");
@@ -633,10 +678,16 @@ function createFileRouter(options) {
633
678
  data: mergeRouteData(middlewareContext.data, pageData)
634
679
  };
635
680
  activeContext = pageContext;
636
- const pageNode = await pageModule.default(pageContext);
637
- const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
638
- const pageHead = await resolveHead(pageModule, pageContext);
639
- const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
681
+ const onDataResult = typeof onData === "function" ? await onData(pageContext) : void 0;
682
+ const eventContext = {
683
+ ...pageContext,
684
+ data: mergeRouteData(pageContext.data, onDataResult)
685
+ };
686
+ activeContext = eventContext;
687
+ const pageNode = await pageModule.default(eventContext);
688
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, eventContext) : void 0;
689
+ const pageHead = await resolveHead(pageModule, eventContext);
690
+ const layoutNode = layoutModule ? await layoutModule.default(eventContext) : null;
640
691
  const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
641
692
  const contextModel = layoutNode ? injectRouteChildren(layoutNode, /* @__PURE__ */ jsx(RouteChildren, {})) : void 0;
642
693
  return {
@@ -645,7 +696,7 @@ function createFileRouter(options) {
645
696
  contextModel,
646
697
  pageModel: pageNode,
647
698
  head: mergeHead(layoutHead, pageHead),
648
- context: pageContext
699
+ context: eventContext
649
700
  };
650
701
  } catch (error) {
651
702
  if (isRouteAbort(error)) {
package/dist/types.d.ts CHANGED
@@ -57,6 +57,19 @@ export type HeadLinkTag = {
57
57
  media?: string;
58
58
  };
59
59
 
60
+ export type HeadScriptTag = {
61
+ id?: string;
62
+ src?: string;
63
+ type?: string;
64
+ async?: boolean;
65
+ defer?: boolean;
66
+ content?: string;
67
+ html?: string;
68
+ integrity?: string;
69
+ crossOrigin?: string;
70
+ referrerPolicy?: string;
71
+ };
72
+
60
73
  export type HeadConfig = {
61
74
  title?: string;
62
75
  description?: string;
@@ -67,6 +80,10 @@ export type HeadConfig = {
67
80
  favicon?: string;
68
81
  meta?: HeadMetaTag[];
69
82
  links?: HeadLinkTag[];
83
+ scripts?: HeadScriptTag[];
84
+ html?: string[];
85
+ bodyStartHtml?: string;
86
+ bodyEndHtml?: string;
70
87
  };
71
88
 
72
89
  export type ClientNavigationPayload = {
@@ -84,6 +101,11 @@ export type PageDataResolver<TData = any> = (
84
101
  context: RouteContext
85
102
  ) => TData | Promise<TData>;
86
103
 
104
+ export type RouteDataHookResult = Record<string, unknown> | undefined | void;
105
+ export type RouteDataHook<TData = any> = (
106
+ context: RouteContext<TData>
107
+ ) => RouteDataHookResult | Promise<RouteDataHookResult>;
108
+
87
109
  export type RouteMiddlewareResult = Record<string, unknown> | undefined | void;
88
110
  export type RouteMiddlewareResolver<TData = any> = (
89
111
  context: RouteContext<TData>
@@ -115,6 +137,8 @@ export type CreateHtmlShellOptions = {
115
137
  buildId?: string;
116
138
  headTags?: string;
117
139
  bodyClassName?: string;
140
+ bodyStartHtml?: string;
141
+ bodyEndHtml?: string;
118
142
  rootHtml?: string;
119
143
  initialFlightData?: string;
120
144
  basename?: string;
@@ -62,6 +62,8 @@ function createHTMLShell(options = {}) {
62
62
  buildId = "",
63
63
  headTags = "",
64
64
  bodyClassName = "",
65
+ bodyStartHtml = "",
66
+ bodyEndHtml = "",
65
67
  rootHtml = "",
66
68
  initialFlightData = "",
67
69
  basename = "",
@@ -111,7 +113,9 @@ function createHTMLShell(options = {}) {
111
113
  ${headTags}
112
114
  </head>
113
115
  <body${bodyClassName ? ` class="${bodyClassName.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}"` : ""}>
116
+ ${bodyStartHtml}
114
117
  <div id="root">${rootHtml}</div>
118
+ ${bodyEndHtml}
115
119
  <script>window.__RSC_ENDPOINT = "${rscEndpoint}";</script>
116
120
  <script>window.__RSC_BASENAME = "${basename}";</script>
117
121
  <script>window.__RSC_ROUTE_BASE_PATH = "${routeBasePath}";</script>
@@ -226,6 +230,12 @@ function normalizeHeadConfig(head, inheritedBasename) {
226
230
  href: resolveHeadAssetUrl(link.href, effectiveBasename)
227
231
  }));
228
232
  }
233
+ if (normalizedHead.scripts) {
234
+ normalizedHead.scripts = normalizedHead.scripts.map((script) => ({
235
+ ...script,
236
+ ...script.src ? { src: resolveHeadAssetUrl(script.src, effectiveBasename) } : {}
237
+ }));
238
+ }
229
239
  return normalizedHead;
230
240
  }
231
241
 
@@ -478,7 +488,9 @@ function matchRoute(entry, pathname) {
478
488
  function mergeHead(...configs) {
479
489
  const merged = {
480
490
  meta: [],
481
- links: []
491
+ links: [],
492
+ scripts: [],
493
+ html: []
482
494
  };
483
495
  for (const candidate of configs) {
484
496
  const config = normalizeHeadConfig(candidate, merged.basename);
@@ -513,9 +525,28 @@ function mergeHead(...configs) {
513
525
  if (config.links) {
514
526
  merged.links?.push(...config.links);
515
527
  }
528
+ if (config.scripts) {
529
+ merged.scripts?.push(...config.scripts);
530
+ }
531
+ if (config.html) {
532
+ merged.html?.push(...Array.isArray(config.html) ? config.html : [config.html]);
533
+ }
534
+ if (config.bodyStartHtml) {
535
+ merged.bodyStartHtml = [merged.bodyStartHtml, config.bodyStartHtml].filter(Boolean).join("\n");
536
+ }
537
+ if (config.bodyEndHtml) {
538
+ merged.bodyEndHtml = [merged.bodyEndHtml, config.bodyEndHtml].filter(Boolean).join("\n");
539
+ }
516
540
  }
517
541
  return merged;
518
542
  }
543
+ function renderGenericAttributes(entry, excluded = []) {
544
+ return Object.entries(entry).filter(
545
+ ([key, value]) => !excluded.includes(key) && value !== void 0 && value !== null && value !== false
546
+ ).map(
547
+ ([key, value]) => value === true ? key : `${key}="${escapeHtml(String(value))}"`
548
+ ).join(" ");
549
+ }
519
550
  function renderHeadToString(head) {
520
551
  const normalizedHead = normalizeHeadConfig(head) ?? head;
521
552
  const tags = [];
@@ -537,6 +568,23 @@ function renderHeadToString(head) {
537
568
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
538
569
  tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
539
570
  }
571
+ for (const script of normalizedHead.scripts ?? []) {
572
+ if (script.html) {
573
+ tags.push(script.html);
574
+ continue;
575
+ }
576
+ const attrs = renderGenericAttributes(script, [
577
+ "content",
578
+ "html"
579
+ ]);
580
+ const content = script.content ? String(script.content) : "";
581
+ tags.push(`<script ${createManagedAttributes()} ${attrs}>${content}</script>`);
582
+ }
583
+ for (const html of normalizedHead.html ?? []) {
584
+ if (typeof html === "string" && html.trim() !== "") {
585
+ tags.push(html);
586
+ }
587
+ }
540
588
  return tags.join("\n");
541
589
  }
542
590
  function clearModuleCache(modulePath, rootDir, visited = /* @__PURE__ */ new Set()) {
@@ -622,6 +670,7 @@ function isRouteAbort(value) {
622
670
  function createFileRouter(options) {
623
671
  installForcedPackageResolution();
624
672
  const pagesDir = options.pagesDir;
673
+ const onData = options.onData;
625
674
  const layoutPath = import_node_path2.default.join(pagesDir, "layout.js");
626
675
  const errorPath = import_node_path2.default.join(pagesDir, "errors.js");
627
676
  const middlewaresPath = import_node_path2.default.join(pagesDir, "middlewares.js");
@@ -786,10 +835,16 @@ function createFileRouter(options) {
786
835
  data: mergeRouteData(middlewareContext.data, pageData)
787
836
  };
788
837
  activeContext = pageContext;
789
- const pageNode = await pageModule.default(pageContext);
790
- const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
791
- const pageHead = await resolveHead(pageModule, pageContext);
792
- const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
838
+ const onDataResult = typeof onData === "function" ? await onData(pageContext) : void 0;
839
+ const eventContext = {
840
+ ...pageContext,
841
+ data: mergeRouteData(pageContext.data, onDataResult)
842
+ };
843
+ activeContext = eventContext;
844
+ const pageNode = await pageModule.default(eventContext);
845
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, eventContext) : void 0;
846
+ const pageHead = await resolveHead(pageModule, eventContext);
847
+ const layoutNode = layoutModule ? await layoutModule.default(eventContext) : null;
793
848
  const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
794
849
  const contextModel = layoutNode ? injectRouteChildren(layoutNode, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RouteChildren, {})) : void 0;
795
850
  return {
@@ -798,7 +853,7 @@ function createFileRouter(options) {
798
853
  contextModel,
799
854
  pageModel: pageNode,
800
855
  head: mergeHead(layoutHead, pageHead),
801
- context: pageContext
856
+ context: eventContext
802
857
  };
803
858
  } catch (error) {
804
859
  if (isRouteAbort(error)) {
@@ -1621,7 +1676,7 @@ function createNodeRequestHandler(options) {
1621
1676
  const liveReloadEnabled = options.liveReloadPath !== false && (nodeEnv === "development" || runningInWatchMode);
1622
1677
  const liveReloadPath = !liveReloadEnabled ? "" : options.liveReloadPath ?? `${basePath || ""}/__webframez_live_reload`;
1623
1678
  const liveReloadClients = /* @__PURE__ */ new Set();
1624
- const router = createFileRouter({ pagesDir });
1679
+ const router = createFileRouter({ pagesDir, onData: options.onData });
1625
1680
  const getManifestState = createManifestLoader({
1626
1681
  distRootDir,
1627
1682
  manifestPath,
@@ -1845,6 +1900,8 @@ function createNodeRequestHandler(options) {
1845
1900
  initialFlightData,
1846
1901
  basename: shellBasename,
1847
1902
  routeBasePath: shellRouteBasePath,
1903
+ bodyStartHtml: resolved.head.bodyStartHtml || "",
1904
+ bodyEndHtml: resolved.head.bodyEndHtml || "",
1848
1905
  liveReloadPath: liveReloadPath || void 0,
1849
1906
  liveReloadServerId: liveReloadPath ? devServerId : void 0
1850
1907
  }),
@@ -2088,7 +2145,8 @@ function resolveWebframezReactRouteOptions(routePathValue, options = {}) {
2088
2145
  clientEntryPath: options.clientEntryPath ? resolveFromProjectRoot(options.clientEntryPath) : void 0,
2089
2146
  styleSrcPath: options.styleSrcPath ? resolveFromProjectRoot(options.styleSrcPath) : defaults.styleSrcPath,
2090
2147
  basePath: options.basePath ?? defaults.basePath,
2091
- liveReloadPath: options.liveReloadPath
2148
+ liveReloadPath: options.liveReloadPath,
2149
+ onData: options.onData
2092
2150
  };
2093
2151
  }
2094
2152
  function getRegisteredReactBuildTargets() {