@webtypen/webframez-react 0.0.2 → 0.0.4

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,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from "node:fs";
4
+ import fsp from "node:fs/promises";
4
5
  import path from "node:path";
5
6
  import { spawn } from "node:child_process";
6
- import { fileURLToPath } from "node:url";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
8
 
8
9
  const binFilePath = fileURLToPath(import.meta.url);
9
10
  const packageRoot = path.resolve(path.dirname(binFilePath), "..");
@@ -12,6 +13,7 @@ const projectRoot = process.cwd();
12
13
  const command = process.argv[2];
13
14
  const passthroughStart = process.argv[3] === "--" ? 4 : 3;
14
15
  const passthroughArgs = process.argv.slice(passthroughStart);
16
+ const customArgPrefixes = ["--client-entry", "--server-entry"];
15
17
 
16
18
  function printHelp() {
17
19
  console.log(
@@ -25,6 +27,7 @@ function printHelp() {
25
27
  " webframez-react watch:client",
26
28
  " webframez-react build:server:webpack",
27
29
  " webframez-react watch:server:webpack",
30
+ " webframez-react exec -- <command> [args...]",
28
31
  "",
29
32
  "Config fallback order:",
30
33
  " 1) project root override file",
@@ -34,6 +37,13 @@ function printHelp() {
34
37
  " - tsconfig.server.json",
35
38
  " - webpack.client.cjs",
36
39
  " - webpack.server.cjs",
40
+ "",
41
+ "Optional project config file:",
42
+ " - webframez-react.config.mjs|cjs|js|json",
43
+ "",
44
+ "Optional CLI overrides:",
45
+ " --client-entry=src/client.tsx",
46
+ " --server-entry=src/server.ts",
37
47
  ].join("\n"),
38
48
  );
39
49
  }
@@ -42,6 +52,43 @@ function hasFlag(flag) {
42
52
  return passthroughArgs.includes(flag);
43
53
  }
44
54
 
55
+ function normalizeRelativePath(value) {
56
+ return value.replace(/\\/g, "/");
57
+ }
58
+
59
+ function readCustomArg(name) {
60
+ const withEquals = `${name}=`;
61
+ for (let index = 0; index < passthroughArgs.length; index += 1) {
62
+ const value = passthroughArgs[index];
63
+ if (value === name) {
64
+ return passthroughArgs[index + 1] || null;
65
+ }
66
+ if (value.startsWith(withEquals)) {
67
+ return value.slice(withEquals.length);
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+
73
+ function stripCustomArgs(args) {
74
+ const filtered = [];
75
+ for (let index = 0; index < args.length; index += 1) {
76
+ const value = args[index];
77
+ const matchedPrefix = customArgPrefixes.find(
78
+ (prefix) => value === prefix || value.startsWith(`${prefix}=`),
79
+ );
80
+ if (!matchedPrefix) {
81
+ filtered.push(value);
82
+ continue;
83
+ }
84
+
85
+ if (value === matchedPrefix) {
86
+ index += 1;
87
+ }
88
+ }
89
+ return filtered;
90
+ }
91
+
45
92
  function resolveConfig(localFileName, fallbackFileName) {
46
93
  const localPath = path.resolve(projectRoot, localFileName);
47
94
  if (fs.existsSync(localPath)) {
@@ -69,7 +116,83 @@ function resolveBinary(name) {
69
116
  return name;
70
117
  }
71
118
 
72
- function run(binaryName, args) {
119
+ function buildReactServerNodeOptions() {
120
+ const existing = process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ` : "";
121
+ return `${existing}--conditions react-server -r @webtypen/webframez-react/register`.trim();
122
+ }
123
+
124
+ async function loadProjectConfig() {
125
+ const configFiles = [
126
+ "webframez-react.config.mjs",
127
+ "webframez-react.config.cjs",
128
+ "webframez-react.config.js",
129
+ "webframez-react.config.json",
130
+ ];
131
+
132
+ for (const fileName of configFiles) {
133
+ const filePath = path.resolve(projectRoot, fileName);
134
+ if (!fs.existsSync(filePath)) {
135
+ continue;
136
+ }
137
+
138
+ if (fileName.endsWith(".json")) {
139
+ return JSON.parse(await fsp.readFile(filePath, "utf8"));
140
+ }
141
+
142
+ const imported = await import(pathToFileURL(filePath).href);
143
+ return imported.default ?? imported;
144
+ }
145
+
146
+ return {};
147
+ }
148
+
149
+ function resolveEntryPath(projectConfig, customArgValue, configKey, defaults) {
150
+ const configuredValue = customArgValue || projectConfig?.[configKey];
151
+ if (configuredValue && typeof configuredValue === "string") {
152
+ return path.resolve(projectRoot, configuredValue);
153
+ }
154
+
155
+ for (const defaultPath of defaults) {
156
+ const resolved = path.resolve(projectRoot, defaultPath);
157
+ if (fs.existsSync(resolved)) {
158
+ return resolved;
159
+ }
160
+ }
161
+
162
+ return path.resolve(projectRoot, defaults[0]);
163
+ }
164
+
165
+ async function createServerTsConfig(baseConfig, serverEntryPath, clientEntryPath) {
166
+ const generatedPath = path.resolve(projectRoot, ".webframez-react.tsconfig.server.json");
167
+ const extendsPath =
168
+ baseConfig.source === "project"
169
+ ? `./${normalizeRelativePath(path.basename(baseConfig.path))}`
170
+ : normalizeRelativePath(path.relative(projectRoot, baseConfig.path));
171
+
172
+ const include = [
173
+ normalizeRelativePath(path.relative(projectRoot, serverEntryPath)),
174
+ "src/components/**/*.tsx",
175
+ "pages/**/*.tsx",
176
+ "src/types.d.ts",
177
+ ];
178
+
179
+ const exclude = [
180
+ normalizeRelativePath(path.relative(projectRoot, clientEntryPath)),
181
+ "dist",
182
+ "node_modules",
183
+ ];
184
+
185
+ const generatedConfig = {
186
+ extends: extendsPath,
187
+ include: Array.from(new Set(include)),
188
+ exclude: Array.from(new Set(exclude)),
189
+ };
190
+
191
+ await fsp.writeFile(generatedPath, JSON.stringify(generatedConfig, null, 2));
192
+ return generatedPath;
193
+ }
194
+
195
+ function run(binaryName, args, envAdditions = {}) {
73
196
  const binary = resolveBinary(binaryName);
74
197
 
75
198
  return new Promise((resolve, reject) => {
@@ -77,6 +200,10 @@ function run(binaryName, args) {
77
200
  cwd: projectRoot,
78
201
  stdio: "inherit",
79
202
  shell: false,
203
+ env: {
204
+ ...process.env,
205
+ ...envAdditions,
206
+ },
80
207
  });
81
208
 
82
209
  child.on("error", (error) => {
@@ -95,11 +222,35 @@ async function main() {
95
222
  return;
96
223
  }
97
224
 
225
+ const projectConfig = await loadProjectConfig();
226
+ const customClientEntry = readCustomArg("--client-entry");
227
+ const customServerEntry = readCustomArg("--server-entry");
228
+ const passthroughArgsClean = stripCustomArgs(passthroughArgs);
229
+
230
+ const clientEntryPath = resolveEntryPath(
231
+ projectConfig,
232
+ customClientEntry,
233
+ "clientEntryPath",
234
+ ["src/client.tsx"],
235
+ );
236
+ const serverEntryPath = resolveEntryPath(
237
+ projectConfig,
238
+ customServerEntry,
239
+ "serverEntryPath",
240
+ ["src/server.ts", "src/server.tsx"],
241
+ );
242
+
98
243
  if (command === "build:server" || command === "watch:server") {
99
244
  const config = resolveConfig("tsconfig.server.json", "tsconfig.server.json");
245
+ const generatedConfigPath = await createServerTsConfig(
246
+ config,
247
+ serverEntryPath,
248
+ clientEntryPath,
249
+ );
100
250
  console.log(`[webframez-react] tsc config (${config.source}): ${config.path}`);
251
+ console.log(`[webframez-react] server entry: ${serverEntryPath}`);
101
252
 
102
- const args = ["-p", config.path, ...passthroughArgs];
253
+ const args = ["-p", generatedConfigPath, ...passthroughArgsClean];
103
254
  if (command === "watch:server") {
104
255
  if (!hasFlag("--watch")) {
105
256
  args.push("--watch");
@@ -114,16 +265,34 @@ async function main() {
114
265
  return;
115
266
  }
116
267
 
268
+ if (command === "exec") {
269
+ if (passthroughArgsClean.length === 0) {
270
+ console.error("[webframez-react] Missing command for exec.");
271
+ printHelp();
272
+ process.exit(1);
273
+ }
274
+
275
+ const [binaryName, ...binaryArgs] = passthroughArgsClean;
276
+ const code = await run(binaryName, binaryArgs, {
277
+ NODE_OPTIONS: buildReactServerNodeOptions(),
278
+ });
279
+ process.exit(code);
280
+ return;
281
+ }
282
+
117
283
  if (command === "build:client" || command === "watch:client") {
118
284
  const config = resolveConfig("webpack.client.cjs", "webpack.client.cjs");
119
285
  console.log(`[webframez-react] webpack config (${config.source}): ${config.path}`);
286
+ console.log(`[webframez-react] client entry: ${clientEntryPath}`);
120
287
 
121
- const args = ["--config", config.path, ...passthroughArgs];
288
+ const args = ["--config", config.path, ...passthroughArgsClean];
122
289
  if (command === "watch:client" && !hasFlag("--watch")) {
123
290
  args.push("--watch");
124
291
  }
125
292
 
126
- const code = await run("webpack", args);
293
+ const code = await run("webpack", args, {
294
+ WEBFRAMEZ_REACT_CLIENT_ENTRY: clientEntryPath,
295
+ });
127
296
  process.exit(code);
128
297
  return;
129
298
  }
@@ -131,13 +300,16 @@ async function main() {
131
300
  if (command === "build:server:webpack" || command === "watch:server:webpack") {
132
301
  const config = resolveConfig("webpack.server.cjs", "webpack.server.cjs");
133
302
  console.log(`[webframez-react] webpack server config (${config.source}): ${config.path}`);
303
+ console.log(`[webframez-react] server entry: ${serverEntryPath}`);
134
304
 
135
- const args = ["--config", config.path, ...passthroughArgs];
305
+ const args = ["--config", config.path, ...passthroughArgsClean];
136
306
  if (command === "watch:server:webpack" && !hasFlag("--watch")) {
137
307
  args.push("--watch");
138
308
  }
139
309
 
140
- const code = await run("webpack", args);
310
+ const code = await run("webpack", args, {
311
+ WEBFRAMEZ_REACT_SERVER_ENTRY: serverEntryPath,
312
+ });
141
313
  process.exit(code);
142
314
  return;
143
315
  }
@@ -0,0 +1,46 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "moduleResolution": "Node",
6
+ "baseUrl": ".",
7
+ "paths": {
8
+ "@webtypen/webframez-react": [
9
+ "./node_modules/@webtypen/webframez-react/dist/index.d.ts"
10
+ ],
11
+ "@webtypen/webframez-react/types": [
12
+ "./node_modules/@webtypen/webframez-react/dist/types.d.ts"
13
+ ],
14
+ "@webtypen/webframez-react/router": [
15
+ "./node_modules/@webtypen/webframez-react/dist/router.d.ts"
16
+ ],
17
+ "@webtypen/webframez-react/client": [
18
+ "./node_modules/@webtypen/webframez-react/dist/client.d.ts"
19
+ ],
20
+ "@webtypen/webframez-react/navigation": [
21
+ "./node_modules/@webtypen/webframez-react/dist/navigation.d.ts"
22
+ ],
23
+ "@webtypen/webframez-react/webframez-core": [
24
+ "./node_modules/@webtypen/webframez-react/dist/webframez-core.d.ts"
25
+ ]
26
+ },
27
+ "jsx": "react-jsx",
28
+ "strict": true,
29
+ "esModuleInterop": true,
30
+ "skipLibCheck": true,
31
+ "outDir": "dist",
32
+ "rootDir": "."
33
+ },
34
+ "include": [
35
+ "src/server.ts",
36
+ "src/server.tsx",
37
+ "src/components/**/*.tsx",
38
+ "pages/**/*.tsx",
39
+ "src/types.d.ts"
40
+ ],
41
+ "exclude": [
42
+ "src/client.tsx",
43
+ "dist",
44
+ "node_modules"
45
+ ]
46
+ }
@@ -5,6 +5,9 @@
5
5
  "moduleResolution": "Node",
6
6
  "baseUrl": ".",
7
7
  "paths": {
8
+ "@webtypen/webframez-react": [
9
+ "./node_modules/@webtypen/webframez-react/dist/index.d.ts"
10
+ ],
8
11
  "@webtypen/webframez-react/types": [
9
12
  "./node_modules/@webtypen/webframez-react/dist/types.d.ts"
10
13
  ],
@@ -17,6 +20,12 @@
17
20
  "@webtypen/webframez-react/navigation": [
18
21
  "./node_modules/@webtypen/webframez-react/dist/navigation.d.ts"
19
22
  ],
23
+ "@webtypen/webframez-react/webframez-core": [
24
+ "./node_modules/@webtypen/webframez-react/dist/webframez-core.d.ts"
25
+ ],
26
+ "webframez-react": [
27
+ "./node_modules/@webtypen/webframez-react/dist/index.d.ts"
28
+ ],
20
29
  "webframez-react/types": [
21
30
  "./node_modules/@webtypen/webframez-react/dist/types.d.ts"
22
31
  ],
@@ -28,6 +37,9 @@
28
37
  ],
29
38
  "webframez-react/navigation": [
30
39
  "./node_modules/@webtypen/webframez-react/dist/navigation.d.ts"
40
+ ],
41
+ "webframez-react/webframez-core": [
42
+ "./node_modules/@webtypen/webframez-react/dist/webframez-core.d.ts"
31
43
  ]
32
44
  },
33
45
  "jsx": "react-jsx",
@@ -39,6 +51,7 @@
39
51
  },
40
52
  "include": [
41
53
  "src/server.ts",
54
+ "src/server.tsx",
42
55
  "src/components/**/*.tsx",
43
56
  "pages/**/*.tsx",
44
57
  "src/types.d.ts"
@@ -3,11 +3,14 @@ const ReactFlightWebpackPlugin = require("react-server-dom-webpack/plugin");
3
3
 
4
4
  const projectRoot = process.cwd();
5
5
  const frameworkDistDir = path.resolve(__dirname, "..", "dist");
6
+ const clientEntry = process.env.WEBFRAMEZ_REACT_CLIENT_ENTRY
7
+ ? path.resolve(projectRoot, process.env.WEBFRAMEZ_REACT_CLIENT_ENTRY)
8
+ : path.resolve(projectRoot, "src/client.tsx");
6
9
 
7
10
  module.exports = {
8
11
  mode: process.env.NODE_ENV === "production" ? "production" : "development",
9
12
  entry: {
10
- client: path.resolve(projectRoot, "src/client.tsx"),
13
+ client: clientEntry,
11
14
  },
12
15
  output: {
13
16
  path: path.resolve(projectRoot, "dist"),
@@ -1,11 +1,18 @@
1
1
  const path = require("path");
2
+ const fs = require("fs");
2
3
 
3
4
  const projectRoot = process.cwd();
5
+ const defaultServerEntry = fs.existsSync(path.resolve(projectRoot, "src/server.ts"))
6
+ ? path.resolve(projectRoot, "src/server.ts")
7
+ : path.resolve(projectRoot, "src/server.tsx");
8
+ const serverEntry = process.env.WEBFRAMEZ_REACT_SERVER_ENTRY
9
+ ? path.resolve(projectRoot, process.env.WEBFRAMEZ_REACT_SERVER_ENTRY)
10
+ : defaultServerEntry;
4
11
 
5
12
  module.exports = {
6
13
  mode: process.env.NODE_ENV === "production" ? "production" : "development",
7
14
  target: "node",
8
- entry: path.resolve(projectRoot, "src/server.ts"),
15
+ entry: serverEntry,
9
16
  output: {
10
17
  path: path.resolve(projectRoot, "dist"),
11
18
  filename: "server.cjs",
package/dist/client.cjs CHANGED
@@ -1 +1 @@
1
- var _=Object.create;var d=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,U=Object.prototype.hasOwnProperty;var b=(e,n)=>{for(var t in n)d(e,t,{get:n[t],enumerable:!0})},g=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of O(n))!U.call(e,r)&&r!==t&&d(e,r,{get:()=>n[r],enumerable:!(o=k(n,r))||o.enumerable});return e};var A=(e,n,t)=>(t=e!=null?_(S(e)):{},g(n||!e||!e.__esModule?d(t,"default",{value:e,enumerable:!0}):t,e)),L=e=>g(d({},"__esModule",{value:!0}),e);var B={};b(B,{mountWebframezClient:()=>W,useCookie:()=>T,useRouter:()=>F});module.exports=L(B);var a=A(require("react"),1),x=require("react-dom/client"),v=require("react-server-dom-webpack/client"),i=require("react/jsx-runtime"),$={push:()=>{},replace:()=>{},refresh:()=>{}},l=typeof a.default.createContext=="function"?a.default.createContext(null):null,E="__WEBFRAMEZ_ROUTER__";function I(){return typeof window>"u"?null:window[E]??null}function P(e){typeof window>"u"||(window[E]=e)}function w(){let e={},n=typeof document>"u"?"":document.cookie;if(!n||n.trim()==="")return e;for(let t of n.split(";")){let o=t.trim();if(!o)continue;let r=o.indexOf("="),c=r>=0?o.slice(0,r).trim():o,u=r>=0?o.slice(r+1):"";c&&(e[c]=decodeURIComponent(u))}return e}function h(e,n,t={}){let o=[`${e}=${encodeURIComponent(n)}`];return t.path&&o.push(`Path=${t.path}`),t.domain&&o.push(`Domain=${t.domain}`),typeof t.maxAge=="number"&&o.push(`Max-Age=${Math.floor(t.maxAge)}`),t.expires&&o.push(`Expires=${t.expires.toUTCString()}`),t.sameSite&&o.push(`SameSite=${t.sameSite}`),t.secure&&o.push("Secure"),o.join("; ")}function T(){return a.default.useMemo(()=>({all:()=>w(),get:e=>w()[e],set:(e,n,t)=>{typeof document>"u"||(document.cookie=h(e,n,t))},remove:(e,n)=>{typeof document>"u"||(document.cookie=h(e,"",{...n??{},maxAge:0}))}}),[])}function F(){let e=l?a.default.useContext(l):null;if(!e){let n=I();if(n)return n;if(typeof window>"u")return $;throw new Error("useRouter must be used inside mountWebframezClient()")}return e}function C({active:e}){return(0,i.jsx)("div",{style:{position:"fixed",top:0,left:0,width:"100%",height:3,zIndex:9999,transformOrigin:"left",transform:e?"scaleX(1)":"scaleX(0)",transition:"transform 180ms ease",background:"linear-gradient(90deg, #0ea5e9, #22c55e)"}})}function M(e){return function(){let[t,o]=(0,a.useState)(null),[r,c]=(0,a.useState)(!1);async function u(s,f="push"){c(!0);try{let y=await(0,v.createFromFetch)(fetch(`${e}?path=${encodeURIComponent(s.pathname)}&search=${encodeURIComponent(s.search)}`,{headers:{Accept:"text/x-component"}}));o(y);let R=`${s.pathname}${s.search}`;f==="replace"?history.replaceState(null,"",R):f==="push"&&history.pushState(null,"",R)}catch(m){console.error("[webframez-react] Failed to render route",m),o((0,i.jsx)("p",{children:"Failed to load route."}))}finally{c(!1)}}(0,a.useEffect)(()=>{let s=()=>{u(new URL(window.location.href),"none")};return window.addEventListener("popstate",s),u(new URL(window.location.href),"none"),()=>{window.removeEventListener("popstate",s)}},[]);let p=a.default.useMemo(()=>({push:s=>{u(new URL(s,window.location.origin),"push")},replace:s=>{u(new URL(s,window.location.origin),"replace")},refresh:()=>{u(new URL(window.location.href),"none")}}),[]);return P(p),l?(0,i.jsxs)(l.Provider,{value:p,children:[(0,i.jsx)(C,{active:r}),t??(0,i.jsx)("p",{style:{padding:24},children:"Loading..."})]}):(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(C,{active:r}),t??(0,i.jsx)("p",{style:{padding:24},children:"Loading..."})]})}}function W(e={}){let n=e.rootId??"root",t=document.getElementById(n);if(!t)throw new Error(`Missing #${n} element`);let o=typeof window<"u"&&window.__RSC_ENDPOINT,r=e.rscEndpoint??o??"/rsc",c=M(r),u=(0,x.createRoot)(t);return u.render((0,i.jsx)(c,{})),u}
1
+ var k=Object.create;var d=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var T=(e,t)=>{for(var n in t)d(e,n,{get:t[n],enumerable:!0})},R=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of O(t))!L.call(e,r)&&r!==n&&d(e,r,{get:()=>t[r],enumerable:!(o=A(t,r))||o.enumerable});return e};var U=(e,t,n)=>(n=e!=null?k(S(e)):{},R(t||!e||!e.__esModule?d(n,"default",{value:e,enumerable:!0}):n,e)),M=e=>R(d({},"__esModule",{value:!0}),e);var D={};T(D,{mountWebframezClient:()=>F,useCookie:()=>B,useRouter:()=>H});module.exports=M(D);var s=U(require("react"),1),E=require("react-dom/client"),_=require("react-server-dom-webpack/client"),i=require("react/jsx-runtime"),$={push:()=>{},replace:()=>{},refresh:()=>{}},l=typeof s.default.createContext=="function"?s.default.createContext(null):null,b="__WEBFRAMEZ_ROUTER__",I="[data-webframez-head='true']";function P(){return typeof window>"u"?null:window[b]??null}function N(e){typeof window>"u"||(window[b]=e)}function h(){let e={},t=typeof document>"u"?"":document.cookie;if(!t||t.trim()==="")return e;for(let n of t.split(";")){let o=n.trim();if(!o)continue;let r=o.indexOf("="),u=r>=0?o.slice(0,r).trim():o,c=r>=0?o.slice(r+1):"";u&&(e[u]=decodeURIComponent(c))}return e}function C(e,t,n={}){let o=[`${e}=${encodeURIComponent(t)}`];return n.path&&o.push(`Path=${n.path}`),n.domain&&o.push(`Domain=${n.domain}`),typeof n.maxAge=="number"&&o.push(`Max-Age=${Math.floor(n.maxAge)}`),n.expires&&o.push(`Expires=${n.expires.toUTCString()}`),n.sameSite&&o.push(`SameSite=${n.sameSite}`),n.secure&&o.push("Secure"),o.join("; ")}function B(){return s.default.useMemo(()=>({all:()=>h(),get:e=>h()[e],set:(e,t,n)=>{typeof document>"u"||(document.cookie=C(e,t,n))},remove:(e,t)=>{typeof document>"u"||(document.cookie=C(e,"",{...t??{},maxAge:0}))}}),[])}function H(){let e=l?s.default.useContext(l):null;if(!e){let t=P();if(t)return t;if(typeof window>"u")return $;throw new Error("useRouter must be used inside mountWebframezClient()")}return e}function v({active:e}){return(0,i.jsx)("div",{style:{position:"fixed",top:0,left:0,width:"100%",height:3,zIndex:9999,transformOrigin:"left",transform:e?"scaleX(1)":"scaleX(0)",transition:"transform 180ms ease",background:"linear-gradient(90deg, #0ea5e9, #22c55e)"}})}function y(e){let t=document.createElement("meta");t.setAttribute("data-webframez-head","true");let n=Object.entries(e).filter(([,o])=>!!o);for(let[o,r]of n)t.setAttribute(o,String(r));document.head.appendChild(t)}function x(e){let t=document.createElement("link");t.setAttribute("data-webframez-head","true");let n=Object.entries(e).filter(([,o])=>!!o);for(let[o,r]of n)t.setAttribute(o,String(r));document.head.appendChild(t)}function W(e){if(!(typeof document>"u")){document.title=e.title||"Webframez React";for(let t of document.head.querySelectorAll(I))t.remove();e.description&&y({name:"description",content:e.description}),e.favicon&&x({rel:"icon",href:e.favicon});for(let t of e.meta??[])y(t);for(let t of e.links??[])x(t)}}function z(e){return function(){let[n,o]=(0,s.useState)(null),[r,u]=(0,s.useState)(!1);async function c(a,p="push"){u(!0);try{let g=await(0,_.createFromFetch)(fetch(`${e}?path=${encodeURIComponent(a.pathname)}&search=${encodeURIComponent(a.search)}`,{headers:{Accept:"text/x-component"}}));W(g.head),o(g.model);let w=`${a.pathname}${a.search}`;p==="replace"?history.replaceState(null,"",w):p==="push"&&history.pushState(null,"",w)}catch(m){console.error("[webframez-react] Failed to render route",m),o((0,i.jsx)("p",{children:"Failed to load route."}))}finally{u(!1)}}(0,s.useEffect)(()=>{let a=()=>{c(new URL(window.location.href),"none")};return window.addEventListener("popstate",a),c(new URL(window.location.href),"none"),()=>{window.removeEventListener("popstate",a)}},[]);let f=s.default.useMemo(()=>({push:a=>{c(new URL(a,window.location.origin),"push")},replace:a=>{c(new URL(a,window.location.origin),"replace")},refresh:()=>{c(new URL(window.location.href),"none")}}),[]);return N(f),l?(0,i.jsxs)(l.Provider,{value:f,children:[(0,i.jsx)(v,{active:r}),n??(0,i.jsx)("p",{style:{padding:24},children:"Loading..."})]}):(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(v,{active:r}),n??(0,i.jsx)("p",{style:{padding:24},children:"Loading..."})]})}}function F(e={}){let t=e.rootId??"root",n=document.getElementById(t);if(!n)throw new Error(`Missing #${t} element`);let o=typeof window<"u"&&window.__RSC_ENDPOINT,r=e.rscEndpoint??o??"/rsc",u=z(r),c=(0,E.createRoot)(n);return c.render((0,i.jsx)(u,{})),c}
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- import c,{useEffect as E,useState as R}from"react";import{createRoot as y}from"react-dom/client";import{createFromFetch as _}from"react-server-dom-webpack/client";import{Fragment as b,jsx as u,jsxs as C}from"react/jsx-runtime";var k={push:()=>{},replace:()=>{},refresh:()=>{}},d=typeof c.createContext=="function"?c.createContext(null):null,x="__WEBFRAMEZ_ROUTER__";function O(){return typeof window>"u"?null:window[x]??null}function S(e){typeof window>"u"||(window[x]=e)}function g(){let e={},o=typeof document>"u"?"":document.cookie;if(!o||o.trim()==="")return e;for(let t of o.split(";")){let n=t.trim();if(!n)continue;let s=n.indexOf("="),a=s>=0?n.slice(0,s).trim():n,i=s>=0?n.slice(s+1):"";a&&(e[a]=decodeURIComponent(i))}return e}function w(e,o,t={}){let n=[`${e}=${encodeURIComponent(o)}`];return t.path&&n.push(`Path=${t.path}`),t.domain&&n.push(`Domain=${t.domain}`),typeof t.maxAge=="number"&&n.push(`Max-Age=${Math.floor(t.maxAge)}`),t.expires&&n.push(`Expires=${t.expires.toUTCString()}`),t.sameSite&&n.push(`SameSite=${t.sameSite}`),t.secure&&n.push("Secure"),n.join("; ")}function I(){return c.useMemo(()=>({all:()=>g(),get:e=>g()[e],set:(e,o,t)=>{typeof document>"u"||(document.cookie=w(e,o,t))},remove:(e,o)=>{typeof document>"u"||(document.cookie=w(e,"",{...o??{},maxAge:0}))}}),[])}function P(){let e=d?c.useContext(d):null;if(!e){let o=O();if(o)return o;if(typeof window>"u")return k;throw new Error("useRouter must be used inside mountWebframezClient()")}return e}function h({active:e}){return u("div",{style:{position:"fixed",top:0,left:0,width:"100%",height:3,zIndex:9999,transformOrigin:"left",transform:e?"scaleX(1)":"scaleX(0)",transition:"transform 180ms ease",background:"linear-gradient(90deg, #0ea5e9, #22c55e)"}})}function U(e){return function(){let[t,n]=R(null),[s,a]=R(!1);async function i(r,p="push"){a(!0);try{let v=await _(fetch(`${e}?path=${encodeURIComponent(r.pathname)}&search=${encodeURIComponent(r.search)}`,{headers:{Accept:"text/x-component"}}));n(v);let m=`${r.pathname}${r.search}`;p==="replace"?history.replaceState(null,"",m):p==="push"&&history.pushState(null,"",m)}catch(f){console.error("[webframez-react] Failed to render route",f),n(u("p",{children:"Failed to load route."}))}finally{a(!1)}}E(()=>{let r=()=>{i(new URL(window.location.href),"none")};return window.addEventListener("popstate",r),i(new URL(window.location.href),"none"),()=>{window.removeEventListener("popstate",r)}},[]);let l=c.useMemo(()=>({push:r=>{i(new URL(r,window.location.origin),"push")},replace:r=>{i(new URL(r,window.location.origin),"replace")},refresh:()=>{i(new URL(window.location.href),"none")}}),[]);return S(l),d?C(d.Provider,{value:l,children:[u(h,{active:s}),t??u("p",{style:{padding:24},children:"Loading..."})]}):C(b,{children:[u(h,{active:s}),t??u("p",{style:{padding:24},children:"Loading..."})]})}}function T(e={}){let o=e.rootId??"root",t=document.getElementById(o);if(!t)throw new Error(`Missing #${o} element`);let n=typeof window<"u"&&window.__RSC_ENDPOINT,s=e.rscEndpoint??n??"/rsc",a=U(s),i=y(t);return i.render(u(a,{})),i}export{T as mountWebframezClient,I as useCookie,P as useRouter};
1
+ import u,{useEffect as _,useState as w}from"react";import{createRoot as b}from"react-dom/client";import{createFromFetch as k}from"react-server-dom-webpack/client";import{Fragment as M,jsx as c,jsxs as x}from"react/jsx-runtime";var A={push:()=>{},replace:()=>{},refresh:()=>{}},d=typeof u.createContext=="function"?u.createContext(null):null,E="__WEBFRAMEZ_ROUTER__",O="[data-webframez-head='true']";function S(){return typeof window>"u"?null:window[E]??null}function L(e){typeof window>"u"||(window[E]=e)}function R(){let e={},t=typeof document>"u"?"":document.cookie;if(!t||t.trim()==="")return e;for(let o of t.split(";")){let n=o.trim();if(!n)continue;let r=n.indexOf("="),s=r>=0?n.slice(0,r).trim():n,a=r>=0?n.slice(r+1):"";s&&(e[s]=decodeURIComponent(a))}return e}function h(e,t,o={}){let n=[`${e}=${encodeURIComponent(t)}`];return o.path&&n.push(`Path=${o.path}`),o.domain&&n.push(`Domain=${o.domain}`),typeof o.maxAge=="number"&&n.push(`Max-Age=${Math.floor(o.maxAge)}`),o.expires&&n.push(`Expires=${o.expires.toUTCString()}`),o.sameSite&&n.push(`SameSite=${o.sameSite}`),o.secure&&n.push("Secure"),n.join("; ")}function N(){return u.useMemo(()=>({all:()=>R(),get:e=>R()[e],set:(e,t,o)=>{typeof document>"u"||(document.cookie=h(e,t,o))},remove:(e,t)=>{typeof document>"u"||(document.cookie=h(e,"",{...t??{},maxAge:0}))}}),[])}function B(){let e=d?u.useContext(d):null;if(!e){let t=S();if(t)return t;if(typeof window>"u")return A;throw new Error("useRouter must be used inside mountWebframezClient()")}return e}function C({active:e}){return c("div",{style:{position:"fixed",top:0,left:0,width:"100%",height:3,zIndex:9999,transformOrigin:"left",transform:e?"scaleX(1)":"scaleX(0)",transition:"transform 180ms ease",background:"linear-gradient(90deg, #0ea5e9, #22c55e)"}})}function v(e){let t=document.createElement("meta");t.setAttribute("data-webframez-head","true");let o=Object.entries(e).filter(([,n])=>!!n);for(let[n,r]of o)t.setAttribute(n,String(r));document.head.appendChild(t)}function y(e){let t=document.createElement("link");t.setAttribute("data-webframez-head","true");let o=Object.entries(e).filter(([,n])=>!!n);for(let[n,r]of o)t.setAttribute(n,String(r));document.head.appendChild(t)}function T(e){if(!(typeof document>"u")){document.title=e.title||"Webframez React";for(let t of document.head.querySelectorAll(O))t.remove();e.description&&v({name:"description",content:e.description}),e.favicon&&y({rel:"icon",href:e.favicon});for(let t of e.meta??[])v(t);for(let t of e.links??[])y(t)}}function U(e){return function(){let[o,n]=w(null),[r,s]=w(!1);async function a(i,f="push"){s(!0);try{let m=await k(fetch(`${e}?path=${encodeURIComponent(i.pathname)}&search=${encodeURIComponent(i.search)}`,{headers:{Accept:"text/x-component"}}));T(m.head),n(m.model);let g=`${i.pathname}${i.search}`;f==="replace"?history.replaceState(null,"",g):f==="push"&&history.pushState(null,"",g)}catch(p){console.error("[webframez-react] Failed to render route",p),n(c("p",{children:"Failed to load route."}))}finally{s(!1)}}_(()=>{let i=()=>{a(new URL(window.location.href),"none")};return window.addEventListener("popstate",i),a(new URL(window.location.href),"none"),()=>{window.removeEventListener("popstate",i)}},[]);let l=u.useMemo(()=>({push:i=>{a(new URL(i,window.location.origin),"push")},replace:i=>{a(new URL(i,window.location.origin),"replace")},refresh:()=>{a(new URL(window.location.href),"none")}}),[]);return L(l),d?x(d.Provider,{value:l,children:[c(C,{active:r}),o??c("p",{style:{padding:24},children:"Loading..."})]}):x(M,{children:[c(C,{active:r}),o??c("p",{style:{padding:24},children:"Loading..."})]})}}function H(e={}){let t=e.rootId??"root",o=document.getElementById(t);if(!o)throw new Error(`Missing #${t} element`);let n=typeof window<"u"&&window.__RSC_ENDPOINT,r=e.rscEndpoint??n??"/rsc",s=U(r),a=b(o);return a.render(c(s,{})),a}export{H as mountWebframezClient,N as useCookie,B as useRouter};
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,
@@ -479,14 +499,26 @@ function stripBasePath(pathname, basePath) {
479
499
  }
480
500
  function runNodeCommand(args) {
481
501
  return new Promise((resolve, reject) => {
482
- (0, import_node_child_process.execFile)(process.execPath, args, { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 }, (error, stdout, stderr) => {
483
- if (error) {
484
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
485
- reject(new Error(out || error.message));
486
- return;
502
+ const execArgs = [
503
+ "--conditions",
504
+ "react-server",
505
+ "-r",
506
+ "@webtypen/webframez-react/register",
507
+ ...args
508
+ ];
509
+ (0, import_node_child_process.execFile)(
510
+ process.execPath,
511
+ execArgs,
512
+ { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 },
513
+ (error, stdout, stderr) => {
514
+ if (error) {
515
+ const out = stderr && stderr.trim() !== "" ? stderr : stdout;
516
+ reject(new Error(out || error.message));
517
+ return;
518
+ }
519
+ resolve({ stdout, stderr });
487
520
  }
488
- resolve({ stdout, stderr });
489
- });
521
+ );
490
522
  });
491
523
  }
492
524
  async function renderInitialHtmlInWorker(options) {
@@ -510,7 +542,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
510
542
  }
511
543
  return originalResolveFilename.call(this, request, parent, isMain, options);
512
544
  };
513
- const { createFileRouter } = require("webframez-react/router");
545
+ const { createFileRouter } = require("@webtypen/webframez-react/router");
514
546
  const reactDomPkg = require.resolve("react-dom/package.json", {
515
547
  paths: [process.cwd(), input.pagesDir]
516
548
  });
@@ -649,7 +681,11 @@ function createNodeRequestHandler(options) {
649
681
  cookies: requestCookies
650
682
  })
651
683
  );
652
- sendRSC(res, resolved2.model, {
684
+ const payload = {
685
+ model: resolved2.model,
686
+ head: resolved2.head
687
+ };
688
+ sendRSC(res, payload, {
653
689
  moduleMap,
654
690
  statusCode: resolved2.statusCode
655
691
  });
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