@webtypen/webframez-react 0.0.1
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 +227 -0
- package/dist/client.cjs +1 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.js +1 -0
- package/dist/http.cjs +704 -0
- package/dist/http.d.ts +16 -0
- package/dist/http.js +678 -0
- package/dist/index.cjs +894 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +857 -0
- package/dist/navigation.cjs +149 -0
- package/dist/navigation.d.ts +16 -0
- package/dist/navigation.js +125 -0
- package/dist/router.cjs +386 -0
- package/dist/router.d.ts +21 -0
- package/dist/router.js +355 -0
- package/dist/types.cjs +17 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.js +0 -0
- package/dist/webframez-core.cjs +852 -0
- package/dist/webframez-core.d.ts +19 -0
- package/dist/webframez-core.js +823 -0
- package/package.json +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# webframez-react
|
|
2
|
+
|
|
3
|
+
React Server Components (RSC) extension for `@webtypen/webframez-core`.
|
|
4
|
+
|
|
5
|
+
`webframez-react` provides:
|
|
6
|
+
- seamless `webframez-core` integration via `initWebframezReact(Route)`
|
|
7
|
+
- file-based routing for `pages/**/index.tsx`
|
|
8
|
+
- layout/error handling with `RouteChildren`
|
|
9
|
+
- client-side navigation (`Link`, `Redirect`, `useRouter`) and cookies (`useCookie`)
|
|
10
|
+
- server-rendered initial HTML plus RSC streaming
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js `>= 20`
|
|
15
|
+
- `@webtypen/webframez-core`
|
|
16
|
+
- React Experimental (matching the package peer dependencies)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm i @webtypen/webframez-react @webtypen/webframez-core react react-dom react-server-dom-webpack
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start with webframez-core
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// server.ts
|
|
28
|
+
import { WebApplication, Route } from "@webtypen/webframez-core";
|
|
29
|
+
import { initWebframezReact } from "@webtypen/webframez-react/webframez-core";
|
|
30
|
+
|
|
31
|
+
initWebframezReact(Route);
|
|
32
|
+
|
|
33
|
+
Route.renderReact("/react", {
|
|
34
|
+
distRootDir: `${process.cwd()}/dist`,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
WebApplication.boot();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Notes:
|
|
41
|
+
- `"/react"` is automatically registered as a catch-all route (`/react/*`).
|
|
42
|
+
- `basePath`, `assetsPrefix`, `rscPath`, and `clientScriptUrl` are derived automatically from the mount path.
|
|
43
|
+
|
|
44
|
+
## Page Structure (File-Based Routing)
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
|
|
48
|
+
```txt
|
|
49
|
+
pages/
|
|
50
|
+
layout.tsx
|
|
51
|
+
errors.tsx
|
|
52
|
+
index.tsx
|
|
53
|
+
about/index.tsx
|
|
54
|
+
accounts/[username]/index.tsx
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`pages/layout.tsx`:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
"use server";
|
|
61
|
+
|
|
62
|
+
import React from "react";
|
|
63
|
+
import { RouteChildren } from "webframez-react/router";
|
|
64
|
+
|
|
65
|
+
export default function Layout() {
|
|
66
|
+
return (
|
|
67
|
+
<main>
|
|
68
|
+
<nav>...</nav>
|
|
69
|
+
<RouteChildren />
|
|
70
|
+
</main>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`RouteChildren` marks where the currently resolved page should be rendered.
|
|
76
|
+
|
|
77
|
+
## Error Handling with `abort()`
|
|
78
|
+
|
|
79
|
+
Every server page gets `abort()` via `RouteContext`.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
"use server";
|
|
83
|
+
|
|
84
|
+
import type { PageProps } from "webframez-react/types";
|
|
85
|
+
|
|
86
|
+
export default function AccountPage({ params, abort }: PageProps) {
|
|
87
|
+
if (params.username !== "jane") {
|
|
88
|
+
abort({
|
|
89
|
+
status: 404,
|
|
90
|
+
message: `Account \"${params.username}\" not found`,
|
|
91
|
+
payload: { attemptedUsername: params.username },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return <section>...</section>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Behavior:
|
|
100
|
+
- default without options: `404` + `"Page not found"`
|
|
101
|
+
- rendered through `pages/errors.tsx` (same behavior as unmatched routes)
|
|
102
|
+
- `pathname` is provided automatically by context
|
|
103
|
+
- optional `payload` is forwarded to `errors.tsx`
|
|
104
|
+
|
|
105
|
+
## Client Entry
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// src/client.tsx
|
|
109
|
+
import { mountWebframezClient } from "webframez-react/client";
|
|
110
|
+
|
|
111
|
+
mountWebframezClient();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Optional:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
mountWebframezClient({
|
|
118
|
+
rootId: "root",
|
|
119
|
+
rscEndpoint: "/react/rsc",
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Navigation
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
"use client";
|
|
127
|
+
|
|
128
|
+
import React from "react";
|
|
129
|
+
import { Link, Redirect } from "webframez-react/navigation";
|
|
130
|
+
|
|
131
|
+
export function Nav() {
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
<Link to="/">Home</Link>
|
|
135
|
+
<Link to="/about">About</Link>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function Guard({ loggedIn }: { loggedIn: boolean }) {
|
|
141
|
+
if (!loggedIn) {
|
|
142
|
+
return <Redirect to="/login" replace />;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Note:
|
|
150
|
+
- `Link` and `Redirect` automatically use the basename from `Route.renderReact()`.
|
|
151
|
+
- You can override it per usage via `basename`.
|
|
152
|
+
|
|
153
|
+
## Router and Cookies in Client Components
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
"use client";
|
|
157
|
+
|
|
158
|
+
import React from "react";
|
|
159
|
+
import { useCookie, useRouter } from "webframez-react/client";
|
|
160
|
+
|
|
161
|
+
export default function LoginAction() {
|
|
162
|
+
const cookie = useCookie();
|
|
163
|
+
const router = useRouter();
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<button
|
|
167
|
+
onClick={() => {
|
|
168
|
+
cookie.set("logged_in", "1", { path: "/react", sameSite: "Lax" });
|
|
169
|
+
router.refresh();
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
Login
|
|
173
|
+
</button>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Public Entrypoints
|
|
179
|
+
|
|
180
|
+
- `webframez-react`
|
|
181
|
+
- `createNodeRequestHandler`, `createFileRouter`, `createHTMLShell`, `sendRSC`, `createRSCHandler`
|
|
182
|
+
- `webframez-react/webframez-core`
|
|
183
|
+
- `initWebframezReact`
|
|
184
|
+
- `webframez-react/router`
|
|
185
|
+
- `RouteChildren`
|
|
186
|
+
- `webframez-react/client`
|
|
187
|
+
- `mountWebframezClient`, `useRouter`, `useCookie`
|
|
188
|
+
- `webframez-react/navigation`
|
|
189
|
+
- `Link`, `Redirect`
|
|
190
|
+
- `webframez-react/types`
|
|
191
|
+
- all public types (`RouteContext`, `PageProps`, `ErrorPageProps`, ...)
|
|
192
|
+
|
|
193
|
+
## package.json Scripts
|
|
194
|
+
|
|
195
|
+
Example scripts for a `webframez-react` app:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"scripts": {
|
|
200
|
+
"build:server": "tsc -p tsconfig.server.json",
|
|
201
|
+
"build:client": "webpack --config webpack.client.cjs",
|
|
202
|
+
"build": "npm run build:server && npm run build:client",
|
|
203
|
+
"start": "node --conditions react-server start-server.cjs",
|
|
204
|
+
"watch:server": "tsc -p tsconfig.server.json --watch --preserveWatchOutput",
|
|
205
|
+
"watch:client": "webpack --config webpack.client.cjs --watch",
|
|
206
|
+
"serve:watch": "node --watch --conditions react-server start-server.cjs",
|
|
207
|
+
"dev": "sh -c 'npm run watch:server & npm run watch:client & npm run serve:watch & wait'"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Notes:
|
|
213
|
+
- `build` compiles server output (`pages`, `server.ts`) and client output (`client.tsx` bundle + RSC manifests).
|
|
214
|
+
- `start` runs the built app in React Server mode.
|
|
215
|
+
- `dev` enables watch mode for TypeScript and webpack and restarts Node automatically on server output changes.
|
|
216
|
+
|
|
217
|
+
## Build
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npm run build
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Watch mode:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npm run build:watch
|
|
227
|
+
```
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +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}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Root } from "react-dom/client";
|
|
2
|
+
|
|
3
|
+
export type ClientOptions = {
|
|
4
|
+
rootId?: string;
|
|
5
|
+
rscEndpoint?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type RouterClient = {
|
|
9
|
+
push: (href: string) => void;
|
|
10
|
+
replace: (href: string) => void;
|
|
11
|
+
refresh: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CookieOptions = {
|
|
15
|
+
path?: string;
|
|
16
|
+
domain?: string;
|
|
17
|
+
secure?: boolean;
|
|
18
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
19
|
+
maxAge?: number;
|
|
20
|
+
expires?: Date;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type CookieClient = {
|
|
24
|
+
all: () => Record<string, string>;
|
|
25
|
+
get: (name: string) => string | undefined;
|
|
26
|
+
set: (name: string, value: string, options?: CookieOptions) => void;
|
|
27
|
+
remove: (name: string, options?: Omit<CookieOptions, "maxAge" | "expires">) => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function mountWebframezClient(options?: ClientOptions): Root;
|
|
31
|
+
export function useRouter(): RouterClient;
|
|
32
|
+
export function useCookie(): CookieClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +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};
|