@webtypen/webframez-react 0.0.25 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/http.cjs +143 -11
- package/dist/http.js +143 -11
- package/dist/index.cjs +143 -11
- package/dist/index.js +143 -11
- package/dist/router.cjs +74 -7
- package/dist/router.js +74 -7
- package/dist/types.d.ts +16 -6
- package/dist/webframez-core.cjs +143 -11
- package/dist/webframez-core.js +143 -11
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ React Server Components (RSC) extension for `@webtypen/webframez-core`.
|
|
|
14
14
|
|
|
15
15
|
- Node.js `>= 20`
|
|
16
16
|
- `@webtypen/webframez-core`
|
|
17
|
-
- React
|
|
17
|
+
- React 19.x with matching `react-dom` and `react-server-dom-webpack`
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -130,6 +130,25 @@ Route.renderReact("/app", {
|
|
|
130
130
|
- Additional route options forwarded to `webframez-core`.
|
|
131
131
|
- Typical use case: middleware.
|
|
132
132
|
|
|
133
|
+
## `Head()` Assets
|
|
134
|
+
|
|
135
|
+
Inside a page, layout, or error `Head()` method you can set `basename`.
|
|
136
|
+
That basename is applied to `favicon` and `links[].href`, and also updates the
|
|
137
|
+
router basename used by `Link`, `Redirect`, and client-side navigation.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
export function Head() {
|
|
141
|
+
return {
|
|
142
|
+
title: "Dashboard",
|
|
143
|
+
basename: "/app",
|
|
144
|
+
favicon: "/favicon.ico",
|
|
145
|
+
links: [{ rel: "stylesheet", href: "/styles/dashboard.css" }],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Absolute URLs like `https://...` or `//...` stay unchanged.
|
|
151
|
+
|
|
133
152
|
## Recommended Project Structure
|
|
134
153
|
|
|
135
154
|
```txt
|
package/dist/client.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var N=Object.create;var m=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var W=(e,t)=>{for(var n in t)m(e,n,{get:t[n],enumerable:!0})},v=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of z(t))!M.call(e,r)&&r!==n&&m(e,r,{get:()=>t[r],enumerable:!(o=U(t,r))||o.enumerable});return e};var D=(e,t,n)=>(n=e!=null?N(B(e)):{},v(t||!e||!e.__esModule?m(n,"default",{value:e,enumerable:!0}):n,e)),F=e=>v(m({},"__esModule",{value:!0}),e);var oe={};W(oe,{mountWebframezClient:()=>ne,useCookie:()=>X,useRouter:()=>K});module.exports=F(oe);var i=D(require("react"),1),L=require("react-dom/client"),l=require("react-server-dom-webpack/client");var G=/^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;function u(e){let t=e?.trim();if(t)return t==="/"?"/":t.replace(/\/+$/,"")}function x(e,t){let n=e.trim(),o=u(t);return!n||!o||G.test(n)||o!=="/"&&(n===o||n.startsWith(`${o}/`))?n:o==="/"?n.startsWith("/")?n:`/${n}`:n.startsWith("/")?`${o}${n}`:`${o}/${n}`}function E(e,t){if(!e)return;let n=u(e.basename)??u(t),o={...e,...n?{basename:n}:{}};return o.favicon&&(o.favicon=x(o.favicon,n)),o.links&&(o.links=o.links.map(r=>({...r,href:x(r.href,n)}))),o}var s=require("react/jsx-runtime"),Y={push:()=>{},replace:()=>{},refresh:()=>{}},p=typeof i.default.createContext=="function"?i.default.createContext(null):null,O="__WEBFRAMEZ_ROUTER__",Z="[data-webframez-head='true']";function q(){return typeof window>"u"?null:window[O]??null}function j(e){typeof window>"u"||(window[O]=e)}function S(){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("="),d=r>=0?o.slice(0,r).trim():o,c=r>=0?o.slice(r+1):"";d&&(e[d]=decodeURIComponent(c))}return e}function b(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 X(){return i.default.useMemo(()=>({all:()=>S(),get:e=>S()[e],set:(e,t,n)=>{typeof document>"u"||(document.cookie=b(e,t,n))},remove:(e,t)=>{typeof document>"u"||(document.cookie=b(e,"",{...t??{},maxAge:0}))}}),[])}function K(){let e=p?i.default.useContext(p):null;if(!e){let t=q();if(t)return t;if(typeof window>"u")return Y;throw new Error("useRouter must be used inside mountWebframezClient()")}return e}function V({active:e}){return(0,s.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 k(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 I(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 P(e){if(typeof document>"u")return;let t=E(e)??e,n=u(t.basename);window.__RSC_BASENAME=n??"",document.title=t.title||"Webframez React";for(let o of document.head.querySelectorAll(Z))o.remove();t.description&&k({name:"description",content:t.description}),t.favicon&&I({rel:"icon",href:t.favicon});for(let o of t.meta??[])k(o);for(let o of t.links??[])I(o)}function J(e){let t=new TextEncoder;return new ReadableStream({start(n){n.enqueue(t.encode(e)),n.close()}})}function Q(){if(typeof window>"u")return null;let e=window.__RSC_INITIAL_PAYLOAD;return typeof e!="string"||e===""?null:(delete window.__RSC_INITIAL_PAYLOAD,(0,l.createFromReadableStream)(J(e)))}function ee(e){return Q()??(0,l.createFromFetch)(fetch(`${e}?path=${encodeURIComponent(window.location.pathname)}&search=${encodeURIComponent(window.location.search)}`,{headers:{Accept:"text/x-component"}}))}function te(e,t){return function(){let o=i.default.use(e),[r,d]=(0,i.useState)(o.model),[c,w]=(0,i.useState)(o.head),[T,h]=(0,i.useState)(!1),[$,H]=(0,i.useState)(!1);async function f(a,_="push"){h(!0);try{let g=await(0,l.createFromFetch)(fetch(`${t}?path=${encodeURIComponent(a.pathname)}&search=${encodeURIComponent(a.search)}`,{headers:{Accept:"text/x-component"}}));P(g.head),w(g.head),d(g.model);let y=`${a.pathname}${a.search}`;_==="replace"?history.replaceState(null,"",y):_==="push"&&history.pushState(null,"",y)}catch(A){console.error("[webframez-react] Failed to render route",A),d((0,s.jsx)("p",{children:"Failed to load route."}))}finally{h(!1)}}(0,i.useEffect)(()=>{P(c)},[c]),(0,i.useEffect)(()=>{H(!0);let a=()=>{f(new URL(window.location.href),"none")};return window.addEventListener("popstate",a),()=>{window.removeEventListener("popstate",a)}},[]);let R=i.default.useMemo(()=>({push:a=>{f(new URL(a,window.location.origin),"push")},replace:a=>{f(new URL(a,window.location.origin),"replace")},refresh:()=>{f(new URL(window.location.href),"none")}}),[]);j(R);let C=(0,s.jsxs)(s.Fragment,{children:[$?(0,s.jsx)(V,{active:T}):null,r??(0,s.jsx)("p",{style:{padding:24},children:"Loading..."})]});return p?(0,s.jsx)(p.Provider,{value:R,children:C}):C}}function ne(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",d=ee(r),c=te(d,r);return(0,L.hydrateRoot)(n,(0,s.jsx)(c,{}))}
|
package/dist/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import c,{useEffect as x,useState as f}from"react";import{hydrateRoot as N}from"react-dom/client";import{createFromFetch as P,createFromReadableStream as U}from"react-server-dom-webpack/client";var H=/^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;function u(t){let o=t?.trim();if(o)return o==="/"?"/":o.replace(/\/+$/,"")}function y(t,o){let n=t.trim(),e=u(o);return!n||!e||H.test(n)||e!=="/"&&(n===e||n.startsWith(`${e}/`))?n:e==="/"?n.startsWith("/")?n:`/${n}`:n.startsWith("/")?`${e}${n}`:`${e}/${n}`}function v(t,o){if(!t)return;let n=u(t.basename)??u(o),e={...t,...n?{basename:n}:{}};return e.favicon&&(e.favicon=y(e.favicon,n)),e.links&&(e.links=e.links.map(r=>({...r,href:y(r.href,n)}))),e}import{Fragment as q,jsx as d,jsxs as j}from"react/jsx-runtime";var z={push:()=>{},replace:()=>{},refresh:()=>{}},m=typeof c.createContext=="function"?c.createContext(null):null,L="__WEBFRAMEZ_ROUTER__",B="[data-webframez-head='true']";function M(){return typeof window>"u"?null:window[L]??null}function W(t){typeof window>"u"||(window[L]=t)}function E(){let t={},o=typeof document>"u"?"":document.cookie;if(!o||o.trim()==="")return t;for(let n of o.split(";")){let e=n.trim();if(!e)continue;let r=e.indexOf("="),a=r>=0?e.slice(0,r).trim():e,s=r>=0?e.slice(r+1):"";a&&(t[a]=decodeURIComponent(s))}return t}function S(t,o,n={}){let e=[`${t}=${encodeURIComponent(o)}`];return n.path&&e.push(`Path=${n.path}`),n.domain&&e.push(`Domain=${n.domain}`),typeof n.maxAge=="number"&&e.push(`Max-Age=${Math.floor(n.maxAge)}`),n.expires&&e.push(`Expires=${n.expires.toUTCString()}`),n.sameSite&&e.push(`SameSite=${n.sameSite}`),n.secure&&e.push("Secure"),e.join("; ")}function ee(){return c.useMemo(()=>({all:()=>E(),get:t=>E()[t],set:(t,o,n)=>{typeof document>"u"||(document.cookie=S(t,o,n))},remove:(t,o)=>{typeof document>"u"||(document.cookie=S(t,"",{...o??{},maxAge:0}))}}),[])}function te(){let t=m?c.useContext(m):null;if(!t){let o=M();if(o)return o;if(typeof window>"u")return z;throw new Error("useRouter must be used inside mountWebframezClient()")}return t}function D({active:t}){return d("div",{style:{position:"fixed",top:0,left:0,width:"100%",height:3,zIndex:9999,transformOrigin:"left",transform:t?"scaleX(1)":"scaleX(0)",transition:"transform 180ms ease",background:"linear-gradient(90deg, #0ea5e9, #22c55e)"}})}function b(t){let o=document.createElement("meta");o.setAttribute("data-webframez-head","true");let n=Object.entries(t).filter(([,e])=>!!e);for(let[e,r]of n)o.setAttribute(e,String(r));document.head.appendChild(o)}function k(t){let o=document.createElement("link");o.setAttribute("data-webframez-head","true");let n=Object.entries(t).filter(([,e])=>!!e);for(let[e,r]of n)o.setAttribute(e,String(r));document.head.appendChild(o)}function I(t){if(typeof document>"u")return;let o=v(t)??t,n=u(o.basename);window.__RSC_BASENAME=n??"",document.title=o.title||"Webframez React";for(let e of document.head.querySelectorAll(B))e.remove();o.description&&b({name:"description",content:o.description}),o.favicon&&k({rel:"icon",href:o.favicon});for(let e of o.meta??[])b(e);for(let e of o.links??[])k(e)}function F(t){let o=new TextEncoder;return new ReadableStream({start(n){n.enqueue(o.encode(t)),n.close()}})}function G(){if(typeof window>"u")return null;let t=window.__RSC_INITIAL_PAYLOAD;return typeof t!="string"||t===""?null:(delete window.__RSC_INITIAL_PAYLOAD,U(F(t)))}function Y(t){return G()??P(fetch(`${t}?path=${encodeURIComponent(window.location.pathname)}&search=${encodeURIComponent(window.location.search)}`,{headers:{Accept:"text/x-component"}}))}function Z(t,o){return function(){let e=c.use(t),[r,a]=f(e.model),[s,g]=f(e.head),[O,w]=f(!1),[T,$]=f(!1);async function l(i,C="push"){w(!0);try{let p=await P(fetch(`${o}?path=${encodeURIComponent(i.pathname)}&search=${encodeURIComponent(i.search)}`,{headers:{Accept:"text/x-component"}}));I(p.head),g(p.head),a(p.model);let A=`${i.pathname}${i.search}`;C==="replace"?history.replaceState(null,"",A):C==="push"&&history.pushState(null,"",A)}catch(_){console.error("[webframez-react] Failed to render route",_),a(d("p",{children:"Failed to load route."}))}finally{w(!1)}}x(()=>{I(s)},[s]),x(()=>{$(!0);let i=()=>{l(new URL(window.location.href),"none")};return window.addEventListener("popstate",i),()=>{window.removeEventListener("popstate",i)}},[]);let h=c.useMemo(()=>({push:i=>{l(new URL(i,window.location.origin),"push")},replace:i=>{l(new URL(i,window.location.origin),"replace")},refresh:()=>{l(new URL(window.location.href),"none")}}),[]);W(h);let R=j(q,{children:[T?d(D,{active:O}):null,r??d("p",{style:{padding:24},children:"Loading..."})]});return m?d(m.Provider,{value:h,children:R}):R}}function ne(t={}){let o=t.rootId??"root",n=document.getElementById(o);if(!n)throw new Error(`Missing #${o} element`);let e=typeof window<"u"&&window.__RSC_ENDPOINT,r=t.rscEndpoint??e??"/rsc",a=Y(r),s=Z(a,r);return N(n,d(s,{}))}export{ne as mountWebframezClient,ee as useCookie,te as useRouter};
|
package/dist/http.cjs
CHANGED
|
@@ -150,6 +150,62 @@ var import_node_fs = __toESM(require("node:fs"), 1);
|
|
|
150
150
|
var import_node_module2 = __toESM(require("node:module"), 1);
|
|
151
151
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
152
152
|
|
|
153
|
+
// src/head.ts
|
|
154
|
+
var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
|
|
155
|
+
function normalizeHeadBasename(value) {
|
|
156
|
+
const trimmed = value?.trim();
|
|
157
|
+
if (!trimmed) {
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
if (trimmed === "/") {
|
|
161
|
+
return "/";
|
|
162
|
+
}
|
|
163
|
+
return trimmed.replace(/\/+$/, "");
|
|
164
|
+
}
|
|
165
|
+
function resolveHeadAssetUrl(assetUrl, basename) {
|
|
166
|
+
const normalizedAssetUrl = assetUrl.trim();
|
|
167
|
+
const normalizedBasename = normalizeHeadBasename(basename);
|
|
168
|
+
if (!normalizedAssetUrl || !normalizedBasename) {
|
|
169
|
+
return normalizedAssetUrl;
|
|
170
|
+
}
|
|
171
|
+
if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
|
|
172
|
+
return normalizedAssetUrl;
|
|
173
|
+
}
|
|
174
|
+
if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
|
|
175
|
+
return normalizedAssetUrl;
|
|
176
|
+
}
|
|
177
|
+
if (normalizedBasename === "/") {
|
|
178
|
+
return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
|
|
179
|
+
}
|
|
180
|
+
if (normalizedAssetUrl.startsWith("/")) {
|
|
181
|
+
return `${normalizedBasename}${normalizedAssetUrl}`;
|
|
182
|
+
}
|
|
183
|
+
return `${normalizedBasename}/${normalizedAssetUrl}`;
|
|
184
|
+
}
|
|
185
|
+
function normalizeHeadConfig(head, inheritedBasename) {
|
|
186
|
+
if (!head) {
|
|
187
|
+
return void 0;
|
|
188
|
+
}
|
|
189
|
+
const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
|
|
190
|
+
const normalizedHead = {
|
|
191
|
+
...head,
|
|
192
|
+
...effectiveBasename ? { basename: effectiveBasename } : {}
|
|
193
|
+
};
|
|
194
|
+
if (normalizedHead.favicon) {
|
|
195
|
+
normalizedHead.favicon = resolveHeadAssetUrl(
|
|
196
|
+
normalizedHead.favicon,
|
|
197
|
+
effectiveBasename
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (normalizedHead.links) {
|
|
201
|
+
normalizedHead.links = normalizedHead.links.map((link) => ({
|
|
202
|
+
...link,
|
|
203
|
+
href: resolveHeadAssetUrl(link.href, effectiveBasename)
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
return normalizedHead;
|
|
207
|
+
}
|
|
208
|
+
|
|
153
209
|
// src/router-runtime.tsx
|
|
154
210
|
var import_react = __toESM(require("react"), 1);
|
|
155
211
|
var ROUTE_CHILDREN_TAG = "webframez-route-children";
|
|
@@ -365,7 +421,8 @@ function mergeHead(...configs) {
|
|
|
365
421
|
meta: [],
|
|
366
422
|
links: []
|
|
367
423
|
};
|
|
368
|
-
for (const
|
|
424
|
+
for (const candidate of configs) {
|
|
425
|
+
const config = normalizeHeadConfig(candidate, merged.basename);
|
|
369
426
|
if (!config) {
|
|
370
427
|
continue;
|
|
371
428
|
}
|
|
@@ -375,6 +432,9 @@ function mergeHead(...configs) {
|
|
|
375
432
|
if (config.description) {
|
|
376
433
|
merged.description = config.description;
|
|
377
434
|
}
|
|
435
|
+
if (config.basename) {
|
|
436
|
+
merged.basename = config.basename;
|
|
437
|
+
}
|
|
378
438
|
if (config.favicon) {
|
|
379
439
|
merged.favicon = config.favicon;
|
|
380
440
|
}
|
|
@@ -388,22 +448,23 @@ function mergeHead(...configs) {
|
|
|
388
448
|
return merged;
|
|
389
449
|
}
|
|
390
450
|
function renderHeadToString(head) {
|
|
451
|
+
const normalizedHead = normalizeHeadConfig(head) ?? head;
|
|
391
452
|
const tags = [];
|
|
392
|
-
if (
|
|
453
|
+
if (normalizedHead.description) {
|
|
393
454
|
tags.push(
|
|
394
|
-
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(
|
|
455
|
+
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
|
|
395
456
|
);
|
|
396
457
|
}
|
|
397
|
-
if (
|
|
458
|
+
if (normalizedHead.favicon) {
|
|
398
459
|
tags.push(
|
|
399
|
-
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(
|
|
460
|
+
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
|
|
400
461
|
);
|
|
401
462
|
}
|
|
402
|
-
for (const meta of
|
|
463
|
+
for (const meta of normalizedHead.meta ?? []) {
|
|
403
464
|
const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
404
465
|
tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
|
|
405
466
|
}
|
|
406
|
-
for (const link of
|
|
467
|
+
for (const link of normalizedHead.links ?? []) {
|
|
407
468
|
const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
408
469
|
tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
|
|
409
470
|
}
|
|
@@ -619,6 +680,12 @@ function createFileRouter(options) {
|
|
|
619
680
|
params: {},
|
|
620
681
|
searchParams: input.searchParams,
|
|
621
682
|
cookies: input.cookies ?? {},
|
|
683
|
+
request: input.request ?? {
|
|
684
|
+
host: null,
|
|
685
|
+
pathname,
|
|
686
|
+
originalPathname: pathname,
|
|
687
|
+
headers: {}
|
|
688
|
+
},
|
|
622
689
|
abort: (options2) => {
|
|
623
690
|
throw createAbort(options2);
|
|
624
691
|
}
|
|
@@ -846,6 +913,60 @@ function normalizeBasePath(basePath) {
|
|
|
846
913
|
const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
|
|
847
914
|
return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
848
915
|
}
|
|
916
|
+
function normalizeRequestHostValue(value) {
|
|
917
|
+
const trimmed = value.trim();
|
|
918
|
+
if (!trimmed) {
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
const firstValue = trimmed.split(",")[0]?.trim() || "";
|
|
922
|
+
if (!firstValue) {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
if (firstValue.startsWith("[")) {
|
|
926
|
+
const closingIndex = firstValue.indexOf("]");
|
|
927
|
+
if (closingIndex > 0) {
|
|
928
|
+
return firstValue.slice(1, closingIndex).toLowerCase();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return firstValue.replace(/:\d+$/, "").toLowerCase();
|
|
932
|
+
}
|
|
933
|
+
function getRequestHost(req) {
|
|
934
|
+
const candidates = [
|
|
935
|
+
req.headers["x-forwarded-host"],
|
|
936
|
+
req.headers["x-original-host"],
|
|
937
|
+
req.headers.host
|
|
938
|
+
];
|
|
939
|
+
for (const candidate of candidates) {
|
|
940
|
+
if (Array.isArray(candidate)) {
|
|
941
|
+
for (const singleValue of candidate) {
|
|
942
|
+
if (typeof singleValue !== "string") {
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
const normalized2 = normalizeRequestHostValue(singleValue);
|
|
946
|
+
if (normalized2) {
|
|
947
|
+
return normalized2;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (typeof candidate !== "string") {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
const normalized = normalizeRequestHostValue(candidate);
|
|
956
|
+
if (normalized) {
|
|
957
|
+
return normalized;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
function createRouteRequestContext(req, pathname, originalPathname) {
|
|
963
|
+
return {
|
|
964
|
+
host: getRequestHost(req),
|
|
965
|
+
pathname,
|
|
966
|
+
originalPathname,
|
|
967
|
+
headers: req.headers
|
|
968
|
+
};
|
|
969
|
+
}
|
|
849
970
|
function stripBasePath(pathname, basePath) {
|
|
850
971
|
if (!basePath) {
|
|
851
972
|
return pathname;
|
|
@@ -1191,13 +1312,19 @@ function createNodeRequestHandler(options) {
|
|
|
1191
1312
|
}
|
|
1192
1313
|
if (url.pathname === rscPath) {
|
|
1193
1314
|
const pathname = stripBasePath(url.searchParams.get("path") || "/", basePath);
|
|
1315
|
+
const requestContext = createRouteRequestContext(
|
|
1316
|
+
req,
|
|
1317
|
+
pathname,
|
|
1318
|
+
url.searchParams.get("path") || "/"
|
|
1319
|
+
);
|
|
1194
1320
|
const search = new URLSearchParams(url.searchParams.get("search") || "");
|
|
1195
1321
|
const resolved2 = await withRequestBasename(
|
|
1196
1322
|
basePath,
|
|
1197
1323
|
() => router.resolve({
|
|
1198
1324
|
pathname,
|
|
1199
1325
|
searchParams: parseSearchParams(search),
|
|
1200
|
-
cookies: requestCookies
|
|
1326
|
+
cookies: requestCookies,
|
|
1327
|
+
request: requestContext
|
|
1201
1328
|
})
|
|
1202
1329
|
);
|
|
1203
1330
|
const payload = {
|
|
@@ -1237,7 +1364,12 @@ function createNodeRequestHandler(options) {
|
|
|
1237
1364
|
() => router.resolve({
|
|
1238
1365
|
pathname: stripBasePath(url.pathname, basePath),
|
|
1239
1366
|
searchParams: parseSearchParams(url.searchParams),
|
|
1240
|
-
cookies: requestCookies
|
|
1367
|
+
cookies: requestCookies,
|
|
1368
|
+
request: createRouteRequestContext(
|
|
1369
|
+
req,
|
|
1370
|
+
stripBasePath(url.pathname, basePath),
|
|
1371
|
+
url.pathname
|
|
1372
|
+
)
|
|
1241
1373
|
})
|
|
1242
1374
|
);
|
|
1243
1375
|
const initialPayload = {
|
|
@@ -1281,7 +1413,7 @@ function createNodeRequestHandler(options) {
|
|
|
1281
1413
|
rscEndpoint: rscPath,
|
|
1282
1414
|
rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
|
|
1283
1415
|
initialFlightData,
|
|
1284
|
-
basename: basePath,
|
|
1416
|
+
basename: resolved?.head?.basename ?? basePath,
|
|
1285
1417
|
liveReloadPath: liveReloadPath || void 0,
|
|
1286
1418
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1287
1419
|
})
|
|
@@ -1300,7 +1432,7 @@ function createNodeRequestHandler(options) {
|
|
|
1300
1432
|
rscEndpoint: rscPath,
|
|
1301
1433
|
rootHtml,
|
|
1302
1434
|
initialFlightData,
|
|
1303
|
-
basename: basePath,
|
|
1435
|
+
basename: resolved.head.basename ?? basePath,
|
|
1304
1436
|
liveReloadPath: liveReloadPath || void 0,
|
|
1305
1437
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1306
1438
|
})
|
package/dist/http.js
CHANGED
|
@@ -125,6 +125,62 @@ import fs from "node:fs";
|
|
|
125
125
|
import Module from "node:module";
|
|
126
126
|
import path2 from "node:path";
|
|
127
127
|
|
|
128
|
+
// src/head.ts
|
|
129
|
+
var ABSOLUTE_ASSET_URL_PATTERN = /^(?:[a-zA-Z][a-zA-Z\d+\-.]*:|\/\/|#)/;
|
|
130
|
+
function normalizeHeadBasename(value) {
|
|
131
|
+
const trimmed = value?.trim();
|
|
132
|
+
if (!trimmed) {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
if (trimmed === "/") {
|
|
136
|
+
return "/";
|
|
137
|
+
}
|
|
138
|
+
return trimmed.replace(/\/+$/, "");
|
|
139
|
+
}
|
|
140
|
+
function resolveHeadAssetUrl(assetUrl, basename) {
|
|
141
|
+
const normalizedAssetUrl = assetUrl.trim();
|
|
142
|
+
const normalizedBasename = normalizeHeadBasename(basename);
|
|
143
|
+
if (!normalizedAssetUrl || !normalizedBasename) {
|
|
144
|
+
return normalizedAssetUrl;
|
|
145
|
+
}
|
|
146
|
+
if (ABSOLUTE_ASSET_URL_PATTERN.test(normalizedAssetUrl)) {
|
|
147
|
+
return normalizedAssetUrl;
|
|
148
|
+
}
|
|
149
|
+
if (normalizedBasename !== "/" && (normalizedAssetUrl === normalizedBasename || normalizedAssetUrl.startsWith(`${normalizedBasename}/`))) {
|
|
150
|
+
return normalizedAssetUrl;
|
|
151
|
+
}
|
|
152
|
+
if (normalizedBasename === "/") {
|
|
153
|
+
return normalizedAssetUrl.startsWith("/") ? normalizedAssetUrl : `/${normalizedAssetUrl}`;
|
|
154
|
+
}
|
|
155
|
+
if (normalizedAssetUrl.startsWith("/")) {
|
|
156
|
+
return `${normalizedBasename}${normalizedAssetUrl}`;
|
|
157
|
+
}
|
|
158
|
+
return `${normalizedBasename}/${normalizedAssetUrl}`;
|
|
159
|
+
}
|
|
160
|
+
function normalizeHeadConfig(head, inheritedBasename) {
|
|
161
|
+
if (!head) {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
const effectiveBasename = normalizeHeadBasename(head.basename) ?? normalizeHeadBasename(inheritedBasename);
|
|
165
|
+
const normalizedHead = {
|
|
166
|
+
...head,
|
|
167
|
+
...effectiveBasename ? { basename: effectiveBasename } : {}
|
|
168
|
+
};
|
|
169
|
+
if (normalizedHead.favicon) {
|
|
170
|
+
normalizedHead.favicon = resolveHeadAssetUrl(
|
|
171
|
+
normalizedHead.favicon,
|
|
172
|
+
effectiveBasename
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (normalizedHead.links) {
|
|
176
|
+
normalizedHead.links = normalizedHead.links.map((link) => ({
|
|
177
|
+
...link,
|
|
178
|
+
href: resolveHeadAssetUrl(link.href, effectiveBasename)
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
return normalizedHead;
|
|
182
|
+
}
|
|
183
|
+
|
|
128
184
|
// src/router-runtime.tsx
|
|
129
185
|
import React from "react";
|
|
130
186
|
var ROUTE_CHILDREN_TAG = "webframez-route-children";
|
|
@@ -340,7 +396,8 @@ function mergeHead(...configs) {
|
|
|
340
396
|
meta: [],
|
|
341
397
|
links: []
|
|
342
398
|
};
|
|
343
|
-
for (const
|
|
399
|
+
for (const candidate of configs) {
|
|
400
|
+
const config = normalizeHeadConfig(candidate, merged.basename);
|
|
344
401
|
if (!config) {
|
|
345
402
|
continue;
|
|
346
403
|
}
|
|
@@ -350,6 +407,9 @@ function mergeHead(...configs) {
|
|
|
350
407
|
if (config.description) {
|
|
351
408
|
merged.description = config.description;
|
|
352
409
|
}
|
|
410
|
+
if (config.basename) {
|
|
411
|
+
merged.basename = config.basename;
|
|
412
|
+
}
|
|
353
413
|
if (config.favicon) {
|
|
354
414
|
merged.favicon = config.favicon;
|
|
355
415
|
}
|
|
@@ -363,22 +423,23 @@ function mergeHead(...configs) {
|
|
|
363
423
|
return merged;
|
|
364
424
|
}
|
|
365
425
|
function renderHeadToString(head) {
|
|
426
|
+
const normalizedHead = normalizeHeadConfig(head) ?? head;
|
|
366
427
|
const tags = [];
|
|
367
|
-
if (
|
|
428
|
+
if (normalizedHead.description) {
|
|
368
429
|
tags.push(
|
|
369
|
-
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(
|
|
430
|
+
`<meta ${createManagedAttributes()} name="description" content="${escapeHtml(normalizedHead.description)}" />`
|
|
370
431
|
);
|
|
371
432
|
}
|
|
372
|
-
if (
|
|
433
|
+
if (normalizedHead.favicon) {
|
|
373
434
|
tags.push(
|
|
374
|
-
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(
|
|
435
|
+
`<link ${createManagedAttributes()} rel="icon" href="${escapeHtml(normalizedHead.favicon)}" />`
|
|
375
436
|
);
|
|
376
437
|
}
|
|
377
|
-
for (const meta of
|
|
438
|
+
for (const meta of normalizedHead.meta ?? []) {
|
|
378
439
|
const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
379
440
|
tags.push(`<meta ${createManagedAttributes()} ${attrs} />`);
|
|
380
441
|
}
|
|
381
|
-
for (const link of
|
|
442
|
+
for (const link of normalizedHead.links ?? []) {
|
|
382
443
|
const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
|
|
383
444
|
tags.push(`<link ${createManagedAttributes()} ${attrs} />`);
|
|
384
445
|
}
|
|
@@ -594,6 +655,12 @@ function createFileRouter(options) {
|
|
|
594
655
|
params: {},
|
|
595
656
|
searchParams: input.searchParams,
|
|
596
657
|
cookies: input.cookies ?? {},
|
|
658
|
+
request: input.request ?? {
|
|
659
|
+
host: null,
|
|
660
|
+
pathname,
|
|
661
|
+
originalPathname: pathname,
|
|
662
|
+
headers: {}
|
|
663
|
+
},
|
|
597
664
|
abort: (options2) => {
|
|
598
665
|
throw createAbort(options2);
|
|
599
666
|
}
|
|
@@ -821,6 +888,60 @@ function normalizeBasePath(basePath) {
|
|
|
821
888
|
const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
|
|
822
889
|
return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
823
890
|
}
|
|
891
|
+
function normalizeRequestHostValue(value) {
|
|
892
|
+
const trimmed = value.trim();
|
|
893
|
+
if (!trimmed) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
const firstValue = trimmed.split(",")[0]?.trim() || "";
|
|
897
|
+
if (!firstValue) {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
if (firstValue.startsWith("[")) {
|
|
901
|
+
const closingIndex = firstValue.indexOf("]");
|
|
902
|
+
if (closingIndex > 0) {
|
|
903
|
+
return firstValue.slice(1, closingIndex).toLowerCase();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return firstValue.replace(/:\d+$/, "").toLowerCase();
|
|
907
|
+
}
|
|
908
|
+
function getRequestHost(req) {
|
|
909
|
+
const candidates = [
|
|
910
|
+
req.headers["x-forwarded-host"],
|
|
911
|
+
req.headers["x-original-host"],
|
|
912
|
+
req.headers.host
|
|
913
|
+
];
|
|
914
|
+
for (const candidate of candidates) {
|
|
915
|
+
if (Array.isArray(candidate)) {
|
|
916
|
+
for (const singleValue of candidate) {
|
|
917
|
+
if (typeof singleValue !== "string") {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const normalized2 = normalizeRequestHostValue(singleValue);
|
|
921
|
+
if (normalized2) {
|
|
922
|
+
return normalized2;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (typeof candidate !== "string") {
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const normalized = normalizeRequestHostValue(candidate);
|
|
931
|
+
if (normalized) {
|
|
932
|
+
return normalized;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
function createRouteRequestContext(req, pathname, originalPathname) {
|
|
938
|
+
return {
|
|
939
|
+
host: getRequestHost(req),
|
|
940
|
+
pathname,
|
|
941
|
+
originalPathname,
|
|
942
|
+
headers: req.headers
|
|
943
|
+
};
|
|
944
|
+
}
|
|
824
945
|
function stripBasePath(pathname, basePath) {
|
|
825
946
|
if (!basePath) {
|
|
826
947
|
return pathname;
|
|
@@ -1166,13 +1287,19 @@ function createNodeRequestHandler(options) {
|
|
|
1166
1287
|
}
|
|
1167
1288
|
if (url.pathname === rscPath) {
|
|
1168
1289
|
const pathname = stripBasePath(url.searchParams.get("path") || "/", basePath);
|
|
1290
|
+
const requestContext = createRouteRequestContext(
|
|
1291
|
+
req,
|
|
1292
|
+
pathname,
|
|
1293
|
+
url.searchParams.get("path") || "/"
|
|
1294
|
+
);
|
|
1169
1295
|
const search = new URLSearchParams(url.searchParams.get("search") || "");
|
|
1170
1296
|
const resolved2 = await withRequestBasename(
|
|
1171
1297
|
basePath,
|
|
1172
1298
|
() => router.resolve({
|
|
1173
1299
|
pathname,
|
|
1174
1300
|
searchParams: parseSearchParams(search),
|
|
1175
|
-
cookies: requestCookies
|
|
1301
|
+
cookies: requestCookies,
|
|
1302
|
+
request: requestContext
|
|
1176
1303
|
})
|
|
1177
1304
|
);
|
|
1178
1305
|
const payload = {
|
|
@@ -1212,7 +1339,12 @@ function createNodeRequestHandler(options) {
|
|
|
1212
1339
|
() => router.resolve({
|
|
1213
1340
|
pathname: stripBasePath(url.pathname, basePath),
|
|
1214
1341
|
searchParams: parseSearchParams(url.searchParams),
|
|
1215
|
-
cookies: requestCookies
|
|
1342
|
+
cookies: requestCookies,
|
|
1343
|
+
request: createRouteRequestContext(
|
|
1344
|
+
req,
|
|
1345
|
+
stripBasePath(url.pathname, basePath),
|
|
1346
|
+
url.pathname
|
|
1347
|
+
)
|
|
1216
1348
|
})
|
|
1217
1349
|
);
|
|
1218
1350
|
const initialPayload = {
|
|
@@ -1256,7 +1388,7 @@ function createNodeRequestHandler(options) {
|
|
|
1256
1388
|
rscEndpoint: rscPath,
|
|
1257
1389
|
rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
|
|
1258
1390
|
initialFlightData,
|
|
1259
|
-
basename: basePath,
|
|
1391
|
+
basename: resolved?.head?.basename ?? basePath,
|
|
1260
1392
|
liveReloadPath: liveReloadPath || void 0,
|
|
1261
1393
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1262
1394
|
})
|
|
@@ -1275,7 +1407,7 @@ function createNodeRequestHandler(options) {
|
|
|
1275
1407
|
rscEndpoint: rscPath,
|
|
1276
1408
|
rootHtml,
|
|
1277
1409
|
initialFlightData,
|
|
1278
|
-
basename: basePath,
|
|
1410
|
+
basename: resolved.head.basename ?? basePath,
|
|
1279
1411
|
liveReloadPath: liveReloadPath || void 0,
|
|
1280
1412
|
liveReloadServerId: liveReloadPath ? devServerId : void 0
|
|
1281
1413
|
})
|