weifuwu 0.31.1 → 0.31.2
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 +107 -78
- package/dist/core/router.d.ts +11 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +895 -311
- package/dist/middleware/esbuild-dev.d.ts +69 -0
- package/dist/middleware/esbuild-dev.js +335 -0
- package/dist/middleware/tailwind-dev.d.ts +43 -0
- package/dist/middleware/tailwind-dev.js +199 -0
- package/dist/react/client.d.ts +64 -27
- package/dist/react/client.js +130 -248
- package/dist/react/compile.d.ts +17 -0
- package/dist/react/error-boundary.d.ts +18 -19
- package/dist/react/index.d.ts +80 -16
- package/dist/react/index.js +835 -267
- package/dist/react/types.d.ts +134 -34
- package/package.json +16 -6
- package/dist/react/navigation.d.ts +0 -63
- package/dist/react/navigation.js +0 -154
- package/dist/react/render.d.ts +0 -8
- package/dist/react/route-utils.d.ts +0 -31
package/dist/react/client.d.ts
CHANGED
|
@@ -1,39 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client-side entry for React hydration
|
|
2
|
+
* Client-side entry for React hydration and SPA routing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Hydration:
|
|
5
5
|
* ```ts
|
|
6
|
-
* import { hydrate
|
|
7
|
-
*
|
|
6
|
+
* import { hydrate } from 'weifuwu/react/client'
|
|
7
|
+
* hydrate(HomePage, { layout: PageShell })
|
|
8
|
+
* ```
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Client-side router:
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createBrowserRouter } from 'weifuwu/react/client'
|
|
13
|
+
* createBrowserRouter({
|
|
14
|
+
* layout: PageShell,
|
|
15
|
+
* routes: {
|
|
16
|
+
* '/': () => import('./pages/HomePage.tsx'),
|
|
17
|
+
* '/users/:id': () => import('./pages/UserPage.tsx'),
|
|
18
|
+
* },
|
|
19
|
+
* })
|
|
14
20
|
* ```
|
|
15
21
|
*/
|
|
16
|
-
import { type ComponentType, type
|
|
17
|
-
import { type LinkProps } from './navigation.ts';
|
|
22
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
18
23
|
export interface HydrateOptions {
|
|
24
|
+
/** Hydration target element. Default: document.getElementById('root'). */
|
|
19
25
|
container?: HTMLElement;
|
|
26
|
+
/**
|
|
27
|
+
* Layout component matching the server-side `react({ layout })` config.
|
|
28
|
+
* Must accept `{ children: ReactNode }`.
|
|
29
|
+
* When provided, wraps the page component before hydrating —
|
|
30
|
+
* producing the same tree the server rendered.
|
|
31
|
+
*/
|
|
32
|
+
layout?: ComponentType<{
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}>;
|
|
20
35
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Hydrate a page component. When `layout` is provided, the hydration tree
|
|
38
|
+
* matches the server-rendered structure: `<div id="root"><Layout><App /></Layout></div>`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function hydrate(App: ComponentType, opts?: HydrateOptions): void;
|
|
41
|
+
export interface ClientRouterOptions {
|
|
42
|
+
/** Layout component shared across all routes. */
|
|
43
|
+
layout?: ComponentType<{
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Route map: URL pattern → lazy component loader.
|
|
48
|
+
* Patterns support `:param` segments (e.g. `/users/:id`).
|
|
49
|
+
*/
|
|
50
|
+
routes: Record<string, () => Promise<Record<string, unknown>>>;
|
|
51
|
+
/**
|
|
52
|
+
* Fallback component for unmatched routes.
|
|
53
|
+
* When omitted, unmatched routes do a full page navigation.
|
|
54
|
+
*/
|
|
55
|
+
fallback?: () => Promise<Record<string, unknown>>;
|
|
56
|
+
/** Target element. Default: document.getElementById('root'). */
|
|
57
|
+
container?: HTMLElement;
|
|
25
58
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
useParams: () => Record<string, string>;
|
|
30
|
-
navigate: (url: string) => Promise<void>;
|
|
59
|
+
interface RouterHandle {
|
|
60
|
+
navigate(url: string): Promise<void>;
|
|
61
|
+
renderPath(pathname: string, isInitial: boolean, data?: Record<string, unknown>): Promise<void>;
|
|
31
62
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Initialise the client-side router.
|
|
65
|
+
*
|
|
66
|
+
* On the initial page load, it hydrates the matching route from the
|
|
67
|
+
* server-rendered HTML. Subsequent in-app navigations fetch fresh
|
|
68
|
+
* server data and render the new page client-side.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createBrowserRouter(opts: ClientRouterOptions): RouterHandle;
|
|
71
|
+
/**
|
|
72
|
+
* Programmatic navigation (call from anywhere).
|
|
73
|
+
* Falls back to `window.location.href = url` if the router hasn't been initialised.
|
|
74
|
+
*/
|
|
75
|
+
export declare function navigate(url: string): void;
|
|
39
76
|
export { useServerData } from './hooks.ts';
|
package/dist/react/client.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
// src/react/client.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
useEffect,
|
|
5
|
-
useCallback,
|
|
6
|
-
useSyncExternalStore
|
|
7
|
-
} from "react";
|
|
8
|
-
import { hydrateRoot } from "react-dom/client";
|
|
2
|
+
import { createElement } from "react";
|
|
3
|
+
import { hydrateRoot, createRoot } from "react-dom/client";
|
|
9
4
|
|
|
10
5
|
// src/react/context.ts
|
|
11
6
|
import { createContext } from "react";
|
|
@@ -20,132 +15,12 @@ function getServerDataContext() {
|
|
|
20
15
|
}
|
|
21
16
|
var ServerDataContext = getServerDataContext();
|
|
22
17
|
|
|
23
|
-
// src/react/navigation.ts
|
|
24
|
-
import {
|
|
25
|
-
createElement,
|
|
26
|
-
createContext as createContext2,
|
|
27
|
-
useContext as useContext2
|
|
28
|
-
} from "react";
|
|
29
|
-
|
|
30
18
|
// src/react/hooks.ts
|
|
31
19
|
import { useContext } from "react";
|
|
32
20
|
function useServerData() {
|
|
33
21
|
return useContext(ServerDataContext);
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
// src/react/error-boundary.ts
|
|
37
|
-
import { Component } from "react";
|
|
38
|
-
var ErrorBoundary = class extends Component {
|
|
39
|
-
state = { error: null };
|
|
40
|
-
static getDerivedStateFromError(error) {
|
|
41
|
-
return { error };
|
|
42
|
-
}
|
|
43
|
-
componentDidCatch(error, info) {
|
|
44
|
-
this.props.onError?.(error, info);
|
|
45
|
-
}
|
|
46
|
-
render() {
|
|
47
|
-
if (this.state.error) {
|
|
48
|
-
return this.props.fallback;
|
|
49
|
-
}
|
|
50
|
-
return this.props.children;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// src/react/route-utils.ts
|
|
55
|
-
function defineRoute(config) {
|
|
56
|
-
return config;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/react/navigation.ts
|
|
60
|
-
var RouterContext = createContext2(null);
|
|
61
|
-
RouterContext.displayName = "ClientRouter";
|
|
62
|
-
function useParams() {
|
|
63
|
-
return useContext2(RouterContext)?.params ?? {};
|
|
64
|
-
}
|
|
65
|
-
function useNavigate() {
|
|
66
|
-
const ctx = useContext2(RouterContext);
|
|
67
|
-
if (!ctx) throw new Error("useNavigate() must be used within a client router");
|
|
68
|
-
return ctx.navigate;
|
|
69
|
-
}
|
|
70
|
-
function useNavigation() {
|
|
71
|
-
const ctx = useContext2(RouterContext);
|
|
72
|
-
return { state: ctx?.state ?? "idle" };
|
|
73
|
-
}
|
|
74
|
-
function Link({ href, children, ...props }) {
|
|
75
|
-
const router = useContext2(RouterContext);
|
|
76
|
-
const isExternal = href.startsWith("http://") || href.startsWith("https://") || href.startsWith("//") || href.startsWith("mailto:") || href.startsWith("tel:");
|
|
77
|
-
if (!router || isExternal || props.target !== void 0) {
|
|
78
|
-
return createElement("a", { href, ...props }, children);
|
|
79
|
-
}
|
|
80
|
-
const handleClick = (e) => {
|
|
81
|
-
if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return;
|
|
82
|
-
if (e.button !== 0) return;
|
|
83
|
-
e.preventDefault();
|
|
84
|
-
router.navigate(href);
|
|
85
|
-
};
|
|
86
|
-
return createElement("a", { href, onClick: handleClick, ...props }, children);
|
|
87
|
-
}
|
|
88
|
-
function useRevalidate() {
|
|
89
|
-
const ctx = useContext2(RouterContext);
|
|
90
|
-
if (!ctx) throw new Error("useRevalidate() must be used within a client router");
|
|
91
|
-
return ctx.revalidate;
|
|
92
|
-
}
|
|
93
|
-
function Form({ method = "post", action, children, ...props }) {
|
|
94
|
-
const router = useContext2(RouterContext);
|
|
95
|
-
if (!router) {
|
|
96
|
-
return createElement("form", { method, action, ...props }, children);
|
|
97
|
-
}
|
|
98
|
-
const handleSubmit = async (e) => {
|
|
99
|
-
e.preventDefault();
|
|
100
|
-
const form = e.target;
|
|
101
|
-
const formData = new FormData(form);
|
|
102
|
-
const url = action || window.location.pathname;
|
|
103
|
-
const fetchMethod = method === "get" ? "GET" : method.toUpperCase();
|
|
104
|
-
try {
|
|
105
|
-
const res = await fetch(url, {
|
|
106
|
-
method: fetchMethod,
|
|
107
|
-
body: fetchMethod === "GET" ? void 0 : formData,
|
|
108
|
-
redirect: "manual"
|
|
109
|
-
});
|
|
110
|
-
if (res.status >= 300 && res.status < 400) {
|
|
111
|
-
const loc = res.headers.get("Location");
|
|
112
|
-
if (loc) {
|
|
113
|
-
router.navigate(loc);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
await router.revalidate();
|
|
118
|
-
} catch (err) {
|
|
119
|
-
console.error("[weifuwu/react] Form submit failed:", err);
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
return createElement(
|
|
123
|
-
"form",
|
|
124
|
-
{ method, action, ...props, onSubmit: handleSubmit },
|
|
125
|
-
children
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
function matchPath(pathname, pattern) {
|
|
129
|
-
const patParts = pattern.split("/").filter(Boolean);
|
|
130
|
-
const pathParts = pathname.split("/").filter(Boolean);
|
|
131
|
-
const hasWildcard = patParts[patParts.length - 1] === "*";
|
|
132
|
-
if (!hasWildcard && patParts.length !== pathParts.length) return null;
|
|
133
|
-
if (hasWildcard && pathParts.length < patParts.length - 1) return null;
|
|
134
|
-
const params = {};
|
|
135
|
-
for (let i = 0; i < patParts.length; i++) {
|
|
136
|
-
if (patParts[i] === "*") {
|
|
137
|
-
params["*"] = pathParts.slice(i).join("/");
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
if (patParts[i].startsWith(":")) {
|
|
141
|
-
params[patParts[i].slice(1)] = decodeURIComponent(pathParts[i]);
|
|
142
|
-
} else if (patParts[i] !== pathParts[i]) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return params;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
24
|
// src/react/client.ts
|
|
150
25
|
function readInitialData() {
|
|
151
26
|
if (typeof document === "undefined") return {};
|
|
@@ -158,143 +33,150 @@ function readInitialData() {
|
|
|
158
33
|
}
|
|
159
34
|
return {};
|
|
160
35
|
}
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
36
|
+
async function fetchPageData(url) {
|
|
37
|
+
const res = await fetch(url);
|
|
38
|
+
const html = await res.text();
|
|
39
|
+
const match = html.match(/<script[^>]*id="__WEIFUWU_DATA__"[^>]*>([\s\S]*?)<\/script>/);
|
|
40
|
+
if (match) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(match[1]);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
function compilePattern(pattern) {
|
|
49
|
+
const paramNames = [];
|
|
50
|
+
const regexStr = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:([^/]+)/g, (_, name) => {
|
|
51
|
+
paramNames.push(name);
|
|
52
|
+
return "([^/]+)";
|
|
53
|
+
});
|
|
54
|
+
return { regex: new RegExp(`^${regexStr}$`), paramNames };
|
|
55
|
+
}
|
|
56
|
+
function matchRoute(pathname, routes) {
|
|
57
|
+
for (const route of routes) {
|
|
58
|
+
const m = pathname.match(route.regex);
|
|
59
|
+
if (m) {
|
|
60
|
+
const params = {};
|
|
61
|
+
route.paramNames.forEach((name, i) => {
|
|
62
|
+
params[name] = m[i + 1];
|
|
63
|
+
});
|
|
64
|
+
return { route, params };
|
|
166
65
|
}
|
|
167
|
-
return null;
|
|
168
66
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function extractComponent(mod) {
|
|
70
|
+
if (mod.default && typeof mod.default === "function") return mod.default;
|
|
71
|
+
for (const val of Object.values(mod)) {
|
|
72
|
+
if (typeof val === "function") return val;
|
|
73
|
+
}
|
|
74
|
+
throw new Error("No component export found in module");
|
|
75
|
+
}
|
|
76
|
+
function hydrate(App, opts) {
|
|
77
|
+
const container = opts?.container ?? document.getElementById("root");
|
|
78
|
+
const data = readInitialData();
|
|
79
|
+
let element = createElement(App);
|
|
80
|
+
if (opts?.layout) {
|
|
81
|
+
element = createElement(opts.layout, { children: element });
|
|
82
|
+
}
|
|
83
|
+
hydrateRoot(
|
|
84
|
+
container,
|
|
85
|
+
createElement(ServerDataContext.Provider, { value: data }, element)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
var _root = null;
|
|
89
|
+
function renderPage(container, layout, Component, data, isInitial) {
|
|
90
|
+
let element = createElement(Component);
|
|
91
|
+
if (layout) {
|
|
92
|
+
element = createElement(layout, { children: element });
|
|
178
93
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
94
|
+
const wrapped = createElement(ServerDataContext.Provider, { value: data }, element);
|
|
95
|
+
if (isInitial) {
|
|
96
|
+
_root = hydrateRoot(container, wrapped);
|
|
97
|
+
} else {
|
|
98
|
+
if (!_root) _root = createRoot(container);
|
|
99
|
+
_root.render(wrapped);
|
|
184
100
|
}
|
|
185
|
-
|
|
186
|
-
|
|
101
|
+
}
|
|
102
|
+
var _router = null;
|
|
103
|
+
function createBrowserRouter(opts) {
|
|
104
|
+
if (_router) return _router;
|
|
105
|
+
const container = opts.container ?? document.getElementById("root");
|
|
106
|
+
const layout = opts.layout;
|
|
107
|
+
const routes = Object.entries(opts.routes).map(([pattern, loader]) => {
|
|
108
|
+
const { regex, paramNames } = compilePattern(pattern);
|
|
109
|
+
return { pattern, regex, paramNames, loader };
|
|
110
|
+
});
|
|
111
|
+
let fallback = null;
|
|
112
|
+
if (opts.fallback) {
|
|
113
|
+
const { regex, paramNames } = compilePattern("*");
|
|
114
|
+
fallback = { pattern: "*", regex, paramNames, loader: opts.fallback };
|
|
187
115
|
}
|
|
188
|
-
async function
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
116
|
+
async function renderPath(pathname, isInitial, data) {
|
|
117
|
+
const matched = matchRoute(pathname, routes);
|
|
118
|
+
const route = matched?.route ?? fallback;
|
|
119
|
+
if (!route) {
|
|
120
|
+
window.location.href = pathname;
|
|
192
121
|
return;
|
|
193
122
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
} catch (err) {
|
|
203
|
-
console.error("[weifuwu/react] loader failed:", err);
|
|
204
|
-
state = { ...state, state: "idle" };
|
|
205
|
-
emit();
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (push) history.pushState({ weifuwu: true }, "", url);
|
|
210
|
-
state = { ...state, location: url, state: "idle" };
|
|
211
|
-
emit();
|
|
212
|
-
}
|
|
213
|
-
function RouterApp() {
|
|
214
|
-
const { location, data, state: navState } = useSyncExternalStore(
|
|
215
|
-
subscribe,
|
|
216
|
-
getSnapshot
|
|
217
|
-
);
|
|
218
|
-
const match = findRoute(location);
|
|
219
|
-
useEffect(() => {
|
|
220
|
-
const handler = () => navigate(window.location.pathname, { push: false });
|
|
221
|
-
window.addEventListener("popstate", handler);
|
|
222
|
-
return () => window.removeEventListener("popstate", handler);
|
|
223
|
-
}, []);
|
|
224
|
-
const ctxNavigate = useCallback(
|
|
225
|
-
(url) => navigate(url, { push: true }),
|
|
226
|
-
[]
|
|
227
|
-
);
|
|
228
|
-
const ctxRevalidate = useCallback(async () => {
|
|
229
|
-
const currentMatch = findRoute(window.location.pathname);
|
|
230
|
-
if (currentMatch?.route.loader) {
|
|
231
|
-
state = { ...state, state: "loading" };
|
|
232
|
-
emit();
|
|
123
|
+
try {
|
|
124
|
+
const mod = await route.loader();
|
|
125
|
+
const Component = extractComponent(mod);
|
|
126
|
+
const pageData = data ?? (isInitial ? readInitialData() : await fetchPageData(pathname));
|
|
127
|
+
renderPage(container, layout, Component, pageData, isInitial);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error("[weifuwu] router error:", err);
|
|
130
|
+
if (fallback && route !== fallback) {
|
|
233
131
|
try {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
} catch
|
|
238
|
-
|
|
239
|
-
state = { ...state, state: "idle" };
|
|
240
|
-
emit();
|
|
132
|
+
const fallbackMod = await fallback.loader();
|
|
133
|
+
const FallbackComponent = extractComponent(fallbackMod);
|
|
134
|
+
renderPage(container, layout, FallbackComponent, { error: String(err) }, false);
|
|
135
|
+
} catch {
|
|
136
|
+
container.innerHTML = "<h1>Something went wrong</h1>";
|
|
241
137
|
}
|
|
138
|
+
} else {
|
|
139
|
+
container.innerHTML = "<h1>Something went wrong</h1>";
|
|
242
140
|
}
|
|
243
|
-
}
|
|
244
|
-
const ctxValue = {
|
|
245
|
-
params: match?.params ?? {},
|
|
246
|
-
navigate: ctxNavigate,
|
|
247
|
-
revalidate: ctxRevalidate,
|
|
248
|
-
state: navState
|
|
249
|
-
};
|
|
250
|
-
const content = match ? createElement2(match.route.component) : createElement2("div", null, "Page not found");
|
|
251
|
-
return createElement2(
|
|
252
|
-
RouterContext.Provider,
|
|
253
|
-
{ value: ctxValue },
|
|
254
|
-
createElement2(
|
|
255
|
-
ServerDataContext.Provider,
|
|
256
|
-
{ value: data },
|
|
257
|
-
content
|
|
258
|
-
)
|
|
259
|
-
);
|
|
141
|
+
}
|
|
260
142
|
}
|
|
261
|
-
function
|
|
262
|
-
|
|
143
|
+
async function navigate2(url) {
|
|
144
|
+
const u = new URL(url, window.location.origin);
|
|
145
|
+
if (u.origin !== window.location.origin || u.pathname === window.location.pathname) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
history.pushState({}, "", url);
|
|
149
|
+
scrollTo(0, 0);
|
|
150
|
+
await renderPath(u.pathname, false);
|
|
263
151
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
152
|
+
window.addEventListener("popstate", () => {
|
|
153
|
+
renderPath(window.location.pathname, false);
|
|
154
|
+
});
|
|
155
|
+
document.addEventListener("click", (e) => {
|
|
156
|
+
const target = e.target.closest("a");
|
|
157
|
+
if (!target) return;
|
|
158
|
+
const href = target.getAttribute("href");
|
|
159
|
+
if (!href) return;
|
|
160
|
+
const u = new URL(href, window.location.origin);
|
|
161
|
+
if (u.origin !== window.location.origin || target.hasAttribute("download") || target.target === "_blank" || target.getAttribute("rel") === "external") return;
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
navigate2(href);
|
|
164
|
+
});
|
|
165
|
+
const initialData = readInitialData();
|
|
166
|
+
renderPath(window.location.pathname, true, initialData);
|
|
167
|
+
_router = { navigate: navigate2, renderPath };
|
|
168
|
+
return _router;
|
|
270
169
|
}
|
|
271
|
-
function
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
);
|
|
170
|
+
function navigate(url) {
|
|
171
|
+
if (_router) {
|
|
172
|
+
_router.navigate(url);
|
|
173
|
+
} else {
|
|
174
|
+
window.location.href = url;
|
|
277
175
|
}
|
|
278
|
-
const serverData = readInitialData();
|
|
279
|
-
hydrateRoot(
|
|
280
|
-
container,
|
|
281
|
-
createElement2(
|
|
282
|
-
ServerDataContext.Provider,
|
|
283
|
-
{ value: serverData },
|
|
284
|
-
createElement2(App)
|
|
285
|
-
)
|
|
286
|
-
);
|
|
287
176
|
}
|
|
288
177
|
export {
|
|
289
|
-
|
|
290
|
-
Form,
|
|
291
|
-
Link,
|
|
292
|
-
createClientRouter,
|
|
293
|
-
defineRoute,
|
|
178
|
+
createBrowserRouter,
|
|
294
179
|
hydrate,
|
|
295
|
-
|
|
296
|
-
useNavigation,
|
|
297
|
-
useParams,
|
|
298
|
-
useRevalidate,
|
|
180
|
+
navigate,
|
|
299
181
|
useServerData
|
|
300
182
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-the-fly TSX compilation for ctx.render().
|
|
3
|
+
*
|
|
4
|
+
* Compiles .tsx files with esbuild, caches by source-content hash.
|
|
5
|
+
* Externalizes react/react-dom — the framework's own instance is used.
|
|
6
|
+
* Persisted to disk for fast restarts.
|
|
7
|
+
*/
|
|
8
|
+
import type { ComponentType } from 'react';
|
|
9
|
+
export declare function setReactCacheDir(dir: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Load a .tsx/.ts module, compiling on-the-fly with esbuild.
|
|
12
|
+
* Results are:
|
|
13
|
+
* 1. Cached in memory by source-content hash
|
|
14
|
+
* 2. Persisted to disk ({cacheDir}/{sourceHash}.mjs) for instant restarts
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadTsxModule(entryPath: string): Promise<Record<string, unknown>>;
|
|
17
|
+
export declare function loadTsxComponent(entryPath: string): Promise<ComponentType>;
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
+
import { Component, type ReactNode } from 'react';
|
|
2
|
+
interface ErrorBoundaryProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
/** Fallback UI rendered when a child component throws. */
|
|
5
|
+
fallback?: ReactNode;
|
|
6
|
+
/** Called with the error (client-side only). */
|
|
7
|
+
onError?: (error: Error) => void;
|
|
8
|
+
}
|
|
9
|
+
interface ErrorBoundaryState {
|
|
10
|
+
hasError: boolean;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
}
|
|
1
13
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Catches React render errors on both server and client:
|
|
5
|
-
* - Server (SSR): renders fallback HTML instead of crashing
|
|
6
|
-
* - Client (hydration): catches errors, shows fallback
|
|
14
|
+
* Catch render errors in child components.
|
|
15
|
+
* Works during SSR (renders fallback) and client-side hydration.
|
|
7
16
|
*
|
|
8
|
-
*
|
|
17
|
+
* @example
|
|
9
18
|
* ```tsx
|
|
10
|
-
* <ErrorBoundary fallback={<
|
|
11
|
-
* <
|
|
19
|
+
* <ErrorBoundary fallback={<p>Something went wrong</p>}>
|
|
20
|
+
* <RiskyComponent />
|
|
12
21
|
* </ErrorBoundary>
|
|
13
22
|
* ```
|
|
14
23
|
*/
|
|
15
|
-
import { Component, type ReactNode, type ErrorInfo } from 'react';
|
|
16
|
-
export interface ErrorBoundaryProps {
|
|
17
|
-
fallback: ReactNode;
|
|
18
|
-
children: ReactNode;
|
|
19
|
-
/** Called when an error is caught (both SSR and client). */
|
|
20
|
-
onError?: (error: Error, info: ErrorInfo) => void;
|
|
21
|
-
}
|
|
22
|
-
interface ErrorBoundaryState {
|
|
23
|
-
error: Error | null;
|
|
24
|
-
}
|
|
25
24
|
export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
26
25
|
state: ErrorBoundaryState;
|
|
27
26
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
28
|
-
componentDidCatch(error: Error
|
|
27
|
+
componentDidCatch(error: Error): void;
|
|
29
28
|
render(): ReactNode;
|
|
30
29
|
}
|
|
31
30
|
export {};
|