@webtypen/webframez-react 0.0.1 → 0.0.3

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/http.cjs CHANGED
@@ -223,6 +223,10 @@ function readSearchParams(urlSearchParams) {
223
223
  function escapeHtml(value) {
224
224
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
225
225
  }
226
+ var MANAGED_HEAD_ATTR = "data-webframez-head";
227
+ function createManagedAttributes() {
228
+ return `${MANAGED_HEAD_ATTR}="true"`;
229
+ }
226
230
  function toRouteEntry(pagesDir, filePath) {
227
231
  const normalized = filePath.replace(/\\/g, "/");
228
232
  if (!normalized.endsWith("/index.js")) {
@@ -291,19 +295,21 @@ function renderHeadToString(head) {
291
295
  const tags = [];
292
296
  if (head.description) {
293
297
  tags.push(
294
- `<meta name="description" content="${escapeHtml(head.description)}" />`
298
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
295
299
  );
296
300
  }
297
301
  if (head.favicon) {
298
- tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
302
+ tags.push(
303
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
304
+ );
299
305
  }
300
306
  for (const meta of head.meta ?? []) {
301
307
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
302
- tags.push(`<meta ${attrs} />`);
308
+ tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
303
309
  }
304
310
  for (const link of head.links ?? []) {
305
311
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
306
- tags.push(`<link ${attrs} />`);
312
+ tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
307
313
  }
308
314
  return tags.join("\n");
309
315
  }
@@ -317,6 +323,12 @@ async function resolveHead(candidate, context) {
317
323
  }
318
324
  return candidate.Head(context);
319
325
  }
326
+ async function resolvePageData(candidate, context) {
327
+ if (!candidate.Data) {
328
+ return void 0;
329
+ }
330
+ return candidate.Data(context);
331
+ }
320
332
  function findBestMatch(entries, pathname) {
321
333
  const matches = [];
322
334
  for (const entry of entries) {
@@ -396,10 +408,11 @@ function createFileRouter(options) {
396
408
  };
397
409
  }
398
410
  const errorModule = resolveModule(errorPath);
399
- const errorNode = errorModule.default(errorProps);
411
+ const errorNode = await errorModule.default(errorProps);
400
412
  const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
401
413
  const errorHead = await resolveHead(errorModule, errorProps);
402
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
414
+ const layoutNode = layoutModule ? await layoutModule.default(context) : null;
415
+ const model = layoutNode ? injectRouteChildren(layoutNode, errorNode) : errorNode;
403
416
  return {
404
417
  statusCode,
405
418
  model,
@@ -431,10 +444,17 @@ function createFileRouter(options) {
431
444
  activeContext = context;
432
445
  const pageModule = resolveModule(match.entry.filePath);
433
446
  const layoutModule = import_node_fs.default.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
434
- const pageNode = pageModule.default(context);
435
- const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
436
- const pageHead = await resolveHead(pageModule, context);
437
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
447
+ const pageData = await resolvePageData(pageModule, context);
448
+ const pageContext = {
449
+ ...context,
450
+ data: pageData
451
+ };
452
+ activeContext = pageContext;
453
+ const pageNode = await pageModule.default(pageContext);
454
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
455
+ const pageHead = await resolveHead(pageModule, pageContext);
456
+ const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
457
+ const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
438
458
  return {
439
459
  statusCode: 200,
440
460
  model,
@@ -493,8 +513,23 @@ async function renderInitialHtmlInWorker(options) {
493
513
  const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
494
514
  const script = `
495
515
  const path = require("node:path");
516
+ const Module = require("node:module");
496
517
  const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
497
518
  globalThis.__RSC_BASENAME = input.basename || "";
519
+ const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
520
+ const originalResolveFilename = Module._resolveFilename;
521
+ const forcedResolutions = new Map([
522
+ ["react", appRequire.resolve("react")],
523
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
524
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
525
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
526
+ ]);
527
+ Module._resolveFilename = function(request, parent, isMain, options) {
528
+ if (forcedResolutions.has(request)) {
529
+ return forcedResolutions.get(request);
530
+ }
531
+ return originalResolveFilename.call(this, request, parent, isMain, options);
532
+ };
498
533
  const { createFileRouter } = require("webframez-react/router");
499
534
  const reactDomPkg = require.resolve("react-dom/package.json", {
500
535
  paths: [process.cwd(), input.pagesDir]
@@ -634,7 +669,11 @@ function createNodeRequestHandler(options) {
634
669
  cookies: requestCookies
635
670
  })
636
671
  );
637
- sendRSC(res, resolved2.model, {
672
+ const payload = {
673
+ model: resolved2.model,
674
+ head: resolved2.head
675
+ };
676
+ sendRSC(res, payload, {
638
677
  moduleMap,
639
678
  statusCode: resolved2.statusCode
640
679
  });
package/dist/http.d.ts CHANGED
@@ -1,15 +1,26 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
 
3
- export type CreateNodeHandlerOptions = {
3
+ export type WebframezReactRoutePath = `/${string}` | "/";
4
+ export type WebframezReactAssetsPrefix = `${WebframezReactRoutePath}/` | "/";
5
+
6
+ export interface CreateNodeHandlerPathsOptions {
4
7
  distRootDir: string;
5
8
  pagesDir?: string;
6
9
  manifestPath?: string;
7
- assetsPrefix?: string;
8
- rscPath?: string;
9
- clientScriptUrl?: string;
10
- basePath?: string;
11
- liveReloadPath?: string | false;
12
- };
10
+ }
11
+
12
+ export interface CreateNodeHandlerRoutingOptions {
13
+ assetsPrefix?: WebframezReactAssetsPrefix;
14
+ rscPath?: WebframezReactRoutePath;
15
+ clientScriptUrl?: WebframezReactRoutePath;
16
+ basePath?: WebframezReactRoutePath;
17
+ liveReloadPath?: WebframezReactRoutePath | false;
18
+ }
19
+
20
+ export interface CreateNodeHandlerOptions
21
+ extends CreateNodeHandlerPathsOptions,
22
+ CreateNodeHandlerRoutingOptions {
23
+ }
13
24
 
14
25
  export function createNodeRequestHandler(
15
26
  options: CreateNodeHandlerOptions
package/dist/http.js CHANGED
@@ -198,6 +198,10 @@ function readSearchParams(urlSearchParams) {
198
198
  function escapeHtml(value) {
199
199
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
200
200
  }
201
+ var MANAGED_HEAD_ATTR = "data-webframez-head";
202
+ function createManagedAttributes() {
203
+ return `${MANAGED_HEAD_ATTR}="true"`;
204
+ }
201
205
  function toRouteEntry(pagesDir, filePath) {
202
206
  const normalized = filePath.replace(/\\/g, "/");
203
207
  if (!normalized.endsWith("/index.js")) {
@@ -266,19 +270,21 @@ function renderHeadToString(head) {
266
270
  const tags = [];
267
271
  if (head.description) {
268
272
  tags.push(
269
- `<meta name="description" content="${escapeHtml(head.description)}" />`
273
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
270
274
  );
271
275
  }
272
276
  if (head.favicon) {
273
- tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
277
+ tags.push(
278
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
279
+ );
274
280
  }
275
281
  for (const meta of head.meta ?? []) {
276
282
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
277
- tags.push(`<meta ${attrs} />`);
283
+ tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
278
284
  }
279
285
  for (const link of head.links ?? []) {
280
286
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
281
- tags.push(`<link ${attrs} />`);
287
+ tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
282
288
  }
283
289
  return tags.join("\n");
284
290
  }
@@ -292,6 +298,12 @@ async function resolveHead(candidate, context) {
292
298
  }
293
299
  return candidate.Head(context);
294
300
  }
301
+ async function resolvePageData(candidate, context) {
302
+ if (!candidate.Data) {
303
+ return void 0;
304
+ }
305
+ return candidate.Data(context);
306
+ }
295
307
  function findBestMatch(entries, pathname) {
296
308
  const matches = [];
297
309
  for (const entry of entries) {
@@ -371,10 +383,11 @@ function createFileRouter(options) {
371
383
  };
372
384
  }
373
385
  const errorModule = resolveModule(errorPath);
374
- const errorNode = errorModule.default(errorProps);
386
+ const errorNode = await errorModule.default(errorProps);
375
387
  const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
376
388
  const errorHead = await resolveHead(errorModule, errorProps);
377
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
389
+ const layoutNode = layoutModule ? await layoutModule.default(context) : null;
390
+ const model = layoutNode ? injectRouteChildren(layoutNode, errorNode) : errorNode;
378
391
  return {
379
392
  statusCode,
380
393
  model,
@@ -406,10 +419,17 @@ function createFileRouter(options) {
406
419
  activeContext = context;
407
420
  const pageModule = resolveModule(match.entry.filePath);
408
421
  const layoutModule = fs.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
409
- const pageNode = pageModule.default(context);
410
- const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
411
- const pageHead = await resolveHead(pageModule, context);
412
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
422
+ const pageData = await resolvePageData(pageModule, context);
423
+ const pageContext = {
424
+ ...context,
425
+ data: pageData
426
+ };
427
+ activeContext = pageContext;
428
+ const pageNode = await pageModule.default(pageContext);
429
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
430
+ const pageHead = await resolveHead(pageModule, pageContext);
431
+ const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
432
+ const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
413
433
  return {
414
434
  statusCode: 200,
415
435
  model,
@@ -468,8 +488,23 @@ async function renderInitialHtmlInWorker(options) {
468
488
  const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
469
489
  const script = `
470
490
  const path = require("node:path");
491
+ const Module = require("node:module");
471
492
  const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
472
493
  globalThis.__RSC_BASENAME = input.basename || "";
494
+ const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
495
+ const originalResolveFilename = Module._resolveFilename;
496
+ const forcedResolutions = new Map([
497
+ ["react", appRequire.resolve("react")],
498
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
499
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
500
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
501
+ ]);
502
+ Module._resolveFilename = function(request, parent, isMain, options) {
503
+ if (forcedResolutions.has(request)) {
504
+ return forcedResolutions.get(request);
505
+ }
506
+ return originalResolveFilename.call(this, request, parent, isMain, options);
507
+ };
473
508
  const { createFileRouter } = require("webframez-react/router");
474
509
  const reactDomPkg = require.resolve("react-dom/package.json", {
475
510
  paths: [process.cwd(), input.pagesDir]
@@ -609,7 +644,11 @@ function createNodeRequestHandler(options) {
609
644
  cookies: requestCookies
610
645
  })
611
646
  );
612
- sendRSC(res, resolved2.model, {
647
+ const payload = {
648
+ model: resolved2.model,
649
+ head: resolved2.head
650
+ };
651
+ sendRSC(res, payload, {
613
652
  moduleMap,
614
653
  statusCode: resolved2.statusCode
615
654
  });
package/dist/index.cjs CHANGED
@@ -257,6 +257,10 @@ function readSearchParams(urlSearchParams) {
257
257
  function escapeHtml(value) {
258
258
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
259
259
  }
260
+ var MANAGED_HEAD_ATTR = "data-webframez-head";
261
+ function createManagedAttributes() {
262
+ return `${MANAGED_HEAD_ATTR}="true"`;
263
+ }
260
264
  function toRouteEntry(pagesDir, filePath) {
261
265
  const normalized = filePath.replace(/\\/g, "/");
262
266
  if (!normalized.endsWith("/index.js")) {
@@ -325,19 +329,21 @@ function renderHeadToString(head) {
325
329
  const tags = [];
326
330
  if (head.description) {
327
331
  tags.push(
328
- `<meta name="description" content="${escapeHtml(head.description)}" />`
332
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
329
333
  );
330
334
  }
331
335
  if (head.favicon) {
332
- tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
336
+ tags.push(
337
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
338
+ );
333
339
  }
334
340
  for (const meta of head.meta ?? []) {
335
341
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
336
- tags.push(`<meta ${attrs} />`);
342
+ tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
337
343
  }
338
344
  for (const link of head.links ?? []) {
339
345
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
340
- tags.push(`<link ${attrs} />`);
346
+ tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
341
347
  }
342
348
  return tags.join("\n");
343
349
  }
@@ -351,6 +357,12 @@ async function resolveHead(candidate, context) {
351
357
  }
352
358
  return candidate.Head(context);
353
359
  }
360
+ async function resolvePageData(candidate, context) {
361
+ if (!candidate.Data) {
362
+ return void 0;
363
+ }
364
+ return candidate.Data(context);
365
+ }
354
366
  function findBestMatch(entries, pathname) {
355
367
  const matches = [];
356
368
  for (const entry of entries) {
@@ -430,10 +442,11 @@ function createFileRouter(options) {
430
442
  };
431
443
  }
432
444
  const errorModule = resolveModule(errorPath);
433
- const errorNode = errorModule.default(errorProps);
445
+ const errorNode = await errorModule.default(errorProps);
434
446
  const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
435
447
  const errorHead = await resolveHead(errorModule, errorProps);
436
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
448
+ const layoutNode = layoutModule ? await layoutModule.default(context) : null;
449
+ const model = layoutNode ? injectRouteChildren(layoutNode, errorNode) : errorNode;
437
450
  return {
438
451
  statusCode,
439
452
  model,
@@ -465,10 +478,17 @@ function createFileRouter(options) {
465
478
  activeContext = context;
466
479
  const pageModule = resolveModule(match.entry.filePath);
467
480
  const layoutModule = import_node_fs.default.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
468
- const pageNode = pageModule.default(context);
469
- const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
470
- const pageHead = await resolveHead(pageModule, context);
471
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
481
+ const pageData = await resolvePageData(pageModule, context);
482
+ const pageContext = {
483
+ ...context,
484
+ data: pageData
485
+ };
486
+ activeContext = pageContext;
487
+ const pageNode = await pageModule.default(pageContext);
488
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
489
+ const pageHead = await resolveHead(pageModule, pageContext);
490
+ const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
491
+ const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
472
492
  return {
473
493
  statusCode: 200,
474
494
  model,
@@ -530,8 +550,23 @@ async function renderInitialHtmlInWorker(options) {
530
550
  const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
531
551
  const script = `
532
552
  const path = require("node:path");
553
+ const Module = require("node:module");
533
554
  const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
534
555
  globalThis.__RSC_BASENAME = input.basename || "";
556
+ const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
557
+ const originalResolveFilename = Module._resolveFilename;
558
+ const forcedResolutions = new Map([
559
+ ["react", appRequire.resolve("react")],
560
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
561
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
562
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
563
+ ]);
564
+ Module._resolveFilename = function(request, parent, isMain, options) {
565
+ if (forcedResolutions.has(request)) {
566
+ return forcedResolutions.get(request);
567
+ }
568
+ return originalResolveFilename.call(this, request, parent, isMain, options);
569
+ };
535
570
  const { createFileRouter } = require("webframez-react/router");
536
571
  const reactDomPkg = require.resolve("react-dom/package.json", {
537
572
  paths: [process.cwd(), input.pagesDir]
@@ -671,7 +706,11 @@ function createNodeRequestHandler(options) {
671
706
  cookies: requestCookies
672
707
  })
673
708
  );
674
- sendRSC(res, resolved2.model, {
709
+ const payload = {
710
+ model: resolved2.model,
711
+ head: resolved2.head
712
+ };
713
+ sendRSC(res, payload, {
675
714
  moduleMap,
676
715
  statusCode: resolved2.statusCode
677
716
  });
package/dist/index.d.ts CHANGED
@@ -1,15 +1,12 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import type { CreateHtmlShellOptions, SendRSCOptions } from "./types";
3
3
  import type { CreateNodeHandlerOptions } from "./http";
4
+ import "./webframez-core";
4
5
 
5
6
  export * from "./types";
6
7
  export { createFileRouter, parseSearchParams, renderHeadToString, RouteChildren } from "./router";
7
8
  export type { ResolvedRoute } from "./router";
8
- export { initWebframezReact, setupWebframezCoreReactRoute } from "./webframez-core";
9
- export type {
10
- WebframezCoreRouteMethod,
11
- WebframezCoreReactRenderRouteOptions,
12
- } from "./webframez-core";
9
+ export * from "./webframez-core";
13
10
 
14
11
  export function createHTMLShell(options?: CreateHtmlShellOptions): string;
15
12
  export function sendRSC(
package/dist/index.js CHANGED
@@ -221,6 +221,10 @@ function readSearchParams(urlSearchParams) {
221
221
  function escapeHtml(value) {
222
222
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
223
223
  }
224
+ var MANAGED_HEAD_ATTR = "data-webframez-head";
225
+ function createManagedAttributes() {
226
+ return `${MANAGED_HEAD_ATTR}="true"`;
227
+ }
224
228
  function toRouteEntry(pagesDir, filePath) {
225
229
  const normalized = filePath.replace(/\\/g, "/");
226
230
  if (!normalized.endsWith("/index.js")) {
@@ -289,19 +293,21 @@ function renderHeadToString(head) {
289
293
  const tags = [];
290
294
  if (head.description) {
291
295
  tags.push(
292
- `<meta name="description" content="${escapeHtml(head.description)}" />`
296
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
293
297
  );
294
298
  }
295
299
  if (head.favicon) {
296
- tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
300
+ tags.push(
301
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
302
+ );
297
303
  }
298
304
  for (const meta of head.meta ?? []) {
299
305
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
300
- tags.push(`<meta ${attrs} />`);
306
+ tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
301
307
  }
302
308
  for (const link of head.links ?? []) {
303
309
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
304
- tags.push(`<link ${attrs} />`);
310
+ tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
305
311
  }
306
312
  return tags.join("\n");
307
313
  }
@@ -315,6 +321,12 @@ async function resolveHead(candidate, context) {
315
321
  }
316
322
  return candidate.Head(context);
317
323
  }
324
+ async function resolvePageData(candidate, context) {
325
+ if (!candidate.Data) {
326
+ return void 0;
327
+ }
328
+ return candidate.Data(context);
329
+ }
318
330
  function findBestMatch(entries, pathname) {
319
331
  const matches = [];
320
332
  for (const entry of entries) {
@@ -394,10 +406,11 @@ function createFileRouter(options) {
394
406
  };
395
407
  }
396
408
  const errorModule = resolveModule(errorPath);
397
- const errorNode = errorModule.default(errorProps);
409
+ const errorNode = await errorModule.default(errorProps);
398
410
  const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
399
411
  const errorHead = await resolveHead(errorModule, errorProps);
400
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
412
+ const layoutNode = layoutModule ? await layoutModule.default(context) : null;
413
+ const model = layoutNode ? injectRouteChildren(layoutNode, errorNode) : errorNode;
401
414
  return {
402
415
  statusCode,
403
416
  model,
@@ -429,10 +442,17 @@ function createFileRouter(options) {
429
442
  activeContext = context;
430
443
  const pageModule = resolveModule(match.entry.filePath);
431
444
  const layoutModule = fs.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
432
- const pageNode = pageModule.default(context);
433
- const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
434
- const pageHead = await resolveHead(pageModule, context);
435
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
445
+ const pageData = await resolvePageData(pageModule, context);
446
+ const pageContext = {
447
+ ...context,
448
+ data: pageData
449
+ };
450
+ activeContext = pageContext;
451
+ const pageNode = await pageModule.default(pageContext);
452
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
453
+ const pageHead = await resolveHead(pageModule, pageContext);
454
+ const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
455
+ const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
436
456
  return {
437
457
  statusCode: 200,
438
458
  model,
@@ -494,8 +514,23 @@ async function renderInitialHtmlInWorker(options) {
494
514
  const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
495
515
  const script = `
496
516
  const path = require("node:path");
517
+ const Module = require("node:module");
497
518
  const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
498
519
  globalThis.__RSC_BASENAME = input.basename || "";
520
+ const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
521
+ const originalResolveFilename = Module._resolveFilename;
522
+ const forcedResolutions = new Map([
523
+ ["react", appRequire.resolve("react")],
524
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
525
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
526
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
527
+ ]);
528
+ Module._resolveFilename = function(request, parent, isMain, options) {
529
+ if (forcedResolutions.has(request)) {
530
+ return forcedResolutions.get(request);
531
+ }
532
+ return originalResolveFilename.call(this, request, parent, isMain, options);
533
+ };
499
534
  const { createFileRouter } = require("webframez-react/router");
500
535
  const reactDomPkg = require.resolve("react-dom/package.json", {
501
536
  paths: [process.cwd(), input.pagesDir]
@@ -635,7 +670,11 @@ function createNodeRequestHandler(options) {
635
670
  cookies: requestCookies
636
671
  })
637
672
  );
638
- sendRSC(res, resolved2.model, {
673
+ const payload = {
674
+ model: resolved2.model,
675
+ head: resolved2.head
676
+ };
677
+ sendRSC(res, payload, {
639
678
  moduleMap,
640
679
  statusCode: resolved2.statusCode
641
680
  });
package/dist/router.cjs CHANGED
@@ -144,6 +144,10 @@ function readSearchParams(urlSearchParams) {
144
144
  function escapeHtml(value) {
145
145
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
146
146
  }
147
+ var MANAGED_HEAD_ATTR = "data-webframez-head";
148
+ function createManagedAttributes() {
149
+ return `${MANAGED_HEAD_ATTR}="true"`;
150
+ }
147
151
  function toRouteEntry(pagesDir, filePath) {
148
152
  const normalized = filePath.replace(/\\/g, "/");
149
153
  if (!normalized.endsWith("/index.js")) {
@@ -212,19 +216,21 @@ function renderHeadToString(head) {
212
216
  const tags = [];
213
217
  if (head.description) {
214
218
  tags.push(
215
- `<meta name="description" content="${escapeHtml(head.description)}" />`
219
+ `<meta ${createManagedAttributes()} name="description" content="${escapeHtml(head.description)}" />`
216
220
  );
217
221
  }
218
222
  if (head.favicon) {
219
- tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
223
+ tags.push(
224
+ `<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(head.favicon)}" />`
225
+ );
220
226
  }
221
227
  for (const meta of head.meta ?? []) {
222
228
  const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
223
- tags.push(`<meta ${attrs} />`);
229
+ tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
224
230
  }
225
231
  for (const link of head.links ?? []) {
226
232
  const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
227
- tags.push(`<link ${attrs} />`);
233
+ tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
228
234
  }
229
235
  return tags.join("\n");
230
236
  }
@@ -238,6 +244,12 @@ async function resolveHead(candidate, context) {
238
244
  }
239
245
  return candidate.Head(context);
240
246
  }
247
+ async function resolvePageData(candidate, context) {
248
+ if (!candidate.Data) {
249
+ return void 0;
250
+ }
251
+ return candidate.Data(context);
252
+ }
241
253
  function findBestMatch(entries, pathname) {
242
254
  const matches = [];
243
255
  for (const entry of entries) {
@@ -317,10 +329,11 @@ function createFileRouter(options) {
317
329
  };
318
330
  }
319
331
  const errorModule = resolveModule(errorPath);
320
- const errorNode = errorModule.default(errorProps);
332
+ const errorNode = await errorModule.default(errorProps);
321
333
  const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
322
334
  const errorHead = await resolveHead(errorModule, errorProps);
323
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
335
+ const layoutNode = layoutModule ? await layoutModule.default(context) : null;
336
+ const model = layoutNode ? injectRouteChildren(layoutNode, errorNode) : errorNode;
324
337
  return {
325
338
  statusCode,
326
339
  model,
@@ -352,10 +365,17 @@ function createFileRouter(options) {
352
365
  activeContext = context;
353
366
  const pageModule = resolveModule(match.entry.filePath);
354
367
  const layoutModule = import_node_fs.default.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
355
- const pageNode = pageModule.default(context);
356
- const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
357
- const pageHead = await resolveHead(pageModule, context);
358
- const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
368
+ const pageData = await resolvePageData(pageModule, context);
369
+ const pageContext = {
370
+ ...context,
371
+ data: pageData
372
+ };
373
+ activeContext = pageContext;
374
+ const pageNode = await pageModule.default(pageContext);
375
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, pageContext) : void 0;
376
+ const pageHead = await resolveHead(pageModule, pageContext);
377
+ const layoutNode = layoutModule ? await layoutModule.default(pageContext) : null;
378
+ const model = layoutNode ? injectRouteChildren(layoutNode, pageNode) : pageNode;
359
379
  return {
360
380
  statusCode: 200,
361
381
  model,