weifuwu 0.16.4 → 0.16.6

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 CHANGED
@@ -451,7 +451,7 @@ The hydration bundle also injects `self.process = { env: {} }` as a safety net s
451
451
  #### useWebsocket — auto-reconnecting WebSocket
452
452
 
453
453
  ```tsx
454
- import { useWebsocket } from 'weifuwu'
454
+ import { useWebsocket } from 'weifuwu/react'
455
455
 
456
456
  function Chat() {
457
457
  const { send, lastMessage, readyState, close, reconnect } = useWebsocket('/ws/chat', {
@@ -472,7 +472,7 @@ function Chat() {
472
472
  #### useAction — async form submission
473
473
 
474
474
  ```tsx
475
- import { useAction } from 'weifuwu'
475
+ import { useAction } from 'weifuwu/react'
476
476
 
477
477
  function FeedbackForm() {
478
478
  const { submit, data, error, pending, reset } = useAction('/api/feedback', { method: 'POST' })
@@ -490,7 +490,7 @@ Auto-serializes JSON, auto-reads `_csrf` cookie and sends as `X-CSRF-Token`. Ret
490
490
  ### Client-side navigation
491
491
 
492
492
  ```tsx
493
- import { Link, useNavigate } from 'weifuwu'
493
+ import { Link, useNavigate } from 'weifuwu/react'
494
494
 
495
495
  function Nav() {
496
496
  const navigate = useNavigate()
package/cli.ts CHANGED
@@ -123,7 +123,7 @@ async function cmdInit(name: string) {
123
123
 
124
124
  await writeFile(join(targetDir, 'ui', 'pages', 'page.tsx'), [
125
125
  "import { useState } from 'react'",
126
- "import { useWebsocket } from 'weifuwu'",
126
+ "import { useWebsocket } from 'weifuwu/react'",
127
127
  '',
128
128
  'export default function Home() {',
129
129
  ' const [input, setInput] = useState("")',
package/dist/cli.js CHANGED
@@ -110,7 +110,7 @@ async function cmdInit(name) {
110
110
  ].join("\n"));
111
111
  await writeFile(join(targetDir, "ui", "pages", "page.tsx"), [
112
112
  "import { useState } from 'react'",
113
- "import { useWebsocket } from 'weifuwu'",
113
+ "import { useWebsocket } from 'weifuwu/react'",
114
114
  "",
115
115
  "export default function Home() {",
116
116
  ' const [input, setInput] = useState("")',
package/dist/index.d.ts CHANGED
@@ -60,11 +60,6 @@ export { seo, seoMiddleware, seoTags } from './seo.ts';
60
60
  export type { SeoOptions, RobotsRule, SitemapUrl, SitemapConfig, SeoHeadersConfig, SeoTagsConfig, } from './seo.ts';
61
61
  export { mailer } from './mailer.ts';
62
62
  export type { MailerOptions, MailOptions, Mailer } from './mailer.ts';
63
- export { useWebsocket } from './use-websocket.ts';
64
- export type { UseWebsocketOptions, UseWebsocketReturn } from './use-websocket.ts';
65
- export { useAction } from './use-action.ts';
66
- export type { UseActionOptions, UseActionReturn } from './use-action.ts';
67
- export { Link, useNavigate, navigate } from './client-router.ts';
68
63
  export { csrf } from './csrf.ts';
69
64
  export type { CsrfOptions } from './csrf.ts';
70
65
  export { logdb } from './logdb/index.ts';
package/dist/index.js CHANGED
@@ -123,12 +123,15 @@ function serve(handler, options) {
123
123
  resolveReady = r;
124
124
  });
125
125
  if (options?.shutdown !== false) {
126
- process.on("SIGTERM", () => {
126
+ let shuttingDown = false;
127
+ const shutdown = () => {
128
+ if (shuttingDown) return;
129
+ shuttingDown = true;
127
130
  server.close();
128
- });
129
- process.on("SIGINT", () => {
130
- server.close();
131
- });
131
+ process.exit(0);
132
+ };
133
+ process.on("SIGTERM", shutdown);
134
+ process.on("SIGINT", shutdown);
132
135
  }
133
136
  if (options?.signal) {
134
137
  if (options.signal.aborted) {
@@ -995,6 +998,7 @@ ${src}`;
995
998
  alias: resolveAliases(),
996
999
  banner: { js: "self.process={env:{}};" },
997
1000
  define: Object.keys(publicEnv).length > 0 ? publicEnv : void 0,
1001
+ loader: { ".node": "empty" },
998
1002
  write: false,
999
1003
  minify: true
1000
1004
  });
@@ -1773,7 +1777,7 @@ function upload(options) {
1773
1777
  // rate-limit.ts
1774
1778
  function rateLimit(options) {
1775
1779
  const max = options?.max ?? 100;
1776
- const window2 = options?.window ?? 6e4;
1780
+ const window = options?.window ?? 6e4;
1777
1781
  const getKey = options?.key ?? ((req) => {
1778
1782
  const forwarded = req.headers.get("x-forwarded-for");
1779
1783
  if (forwarded) return forwarded.split(",")[0].trim();
@@ -1790,19 +1794,19 @@ function rateLimit(options) {
1790
1794
  for (const [key, entry] of hits) {
1791
1795
  if (entry.reset < now) hits.delete(key);
1792
1796
  }
1793
- }, window2);
1797
+ }, window);
1794
1798
  if (interval.unref) interval.unref();
1795
1799
  const mw = async (req, ctx, next) => {
1796
1800
  const key = getKey(req);
1797
1801
  const now = Date.now();
1798
1802
  const entry = hits.get(key);
1799
1803
  if (!entry || entry.reset < now) {
1800
- hits.set(key, { count: 1, reset: now + window2 });
1804
+ hits.set(key, { count: 1, reset: now + window });
1801
1805
  const res2 = await next(req, ctx);
1802
1806
  const headers2 = new Headers(res2.headers);
1803
1807
  headers2.set("X-RateLimit-Limit", String(max));
1804
1808
  headers2.set("X-RateLimit-Remaining", String(max - 1));
1805
- headers2.set("X-RateLimit-Reset", String(Math.ceil((now + window2) / 1e3)));
1809
+ headers2.set("X-RateLimit-Reset", String(Math.ceil((now + window) / 1e3)));
1806
1810
  return new Response(res2.body, { status: res2.status, statusText: res2.statusText, headers: headers2 });
1807
1811
  }
1808
1812
  entry.count++;
@@ -6380,7 +6384,7 @@ async function buildRouter4(deps) {
6380
6384
  skills: allSkills,
6381
6385
  systemPrompt: session.system_prompt || systemPrompt
6382
6386
  });
6383
- const history2 = await getHistory(sql2, sessionId);
6387
+ const history = await getHistory(sql2, sessionId);
6384
6388
  await addTextMessage(sql2, sessionId, "user", content);
6385
6389
  const stream = executeGenerator({
6386
6390
  sessionId,
@@ -6388,7 +6392,7 @@ async function buildRouter4(deps) {
6388
6392
  model,
6389
6393
  tools,
6390
6394
  systemPrompt: sysPrompt,
6391
- messages: history2,
6395
+ messages: history,
6392
6396
  sql: sql2
6393
6397
  });
6394
6398
  return createSSEStream(stream);
@@ -6468,7 +6472,7 @@ function createWSHandler2(deps) {
6468
6472
  skills: allSkills,
6469
6473
  systemPrompt: session.system_prompt || systemPrompt
6470
6474
  });
6471
- const history2 = await getHistory(sql2, session_id);
6475
+ const history = await getHistory(sql2, session_id);
6472
6476
  await addTextMessage(sql2, session_id, "user", content);
6473
6477
  const stream = executeGenerator({
6474
6478
  sessionId: session_id,
@@ -6476,7 +6480,7 @@ function createWSHandler2(deps) {
6476
6480
  model,
6477
6481
  tools,
6478
6482
  systemPrompt: sysPrompt,
6479
- messages: history2,
6483
+ messages: history,
6480
6484
  sql: sql2,
6481
6485
  abortSignal: controller.signal
6482
6486
  });
@@ -6913,207 +6917,6 @@ function mailer(options) {
6913
6917
  return { send, close };
6914
6918
  }
6915
6919
 
6916
- // use-websocket.ts
6917
- import { useEffect, useRef, useCallback, useState } from "react";
6918
- var RECONNECT_DELAY = 3e3;
6919
- var MAX_RETRIES = 10;
6920
- function resolveUrl(url) {
6921
- return typeof url === "function" ? url() : url;
6922
- }
6923
- function useWebsocket(url, options) {
6924
- const { onMessage, reconnect: reconnectOpt = true, protocols, enabled = true } = options ?? {};
6925
- const [lastMessage, setLastMessage] = useState(null);
6926
- const [readyState, setReadyState] = useState(WebSocket.CLOSED);
6927
- const wsRef = useRef(null);
6928
- const retryRef = useRef(0);
6929
- const timerRef = useRef(void 0);
6930
- const mountedRef = useRef(true);
6931
- const shouldReconnectRef = useRef(true);
6932
- const urlRef = useRef(url);
6933
- const optsRef = useRef({ onMessage, reconnectOpt, protocols });
6934
- urlRef.current = url;
6935
- optsRef.current = { onMessage, reconnectOpt, protocols };
6936
- const cleanup = useCallback(() => {
6937
- clearTimeout(timerRef.current);
6938
- wsRef.current?.close();
6939
- wsRef.current = null;
6940
- }, []);
6941
- const connect = useCallback(() => {
6942
- if (!mountedRef.current || !enabled) return;
6943
- const resolved = resolveUrl(urlRef.current);
6944
- if (!resolved) return;
6945
- wsRef.current?.close();
6946
- const ws = new WebSocket(resolved, optsRef.current.protocols);
6947
- wsRef.current = ws;
6948
- setReadyState(WebSocket.CONNECTING);
6949
- ws.addEventListener("open", () => {
6950
- if (!mountedRef.current) return;
6951
- retryRef.current = 0;
6952
- setReadyState(WebSocket.OPEN);
6953
- });
6954
- ws.addEventListener("message", (e) => {
6955
- if (!mountedRef.current) return;
6956
- const data = typeof e.data === "string" ? e.data : String(e.data);
6957
- setLastMessage(data);
6958
- optsRef.current.onMessage?.(data);
6959
- });
6960
- ws.addEventListener("close", () => {
6961
- if (!mountedRef.current) return;
6962
- setReadyState(WebSocket.CLOSED);
6963
- const ro = optsRef.current.reconnectOpt;
6964
- if (ro && shouldReconnectRef.current && mountedRef.current) {
6965
- const maxRetries = typeof ro === "object" ? ro.maxRetries ?? MAX_RETRIES : MAX_RETRIES;
6966
- const delay = typeof ro === "object" ? ro.delay ?? RECONNECT_DELAY : RECONNECT_DELAY;
6967
- if (retryRef.current < maxRetries) {
6968
- retryRef.current++;
6969
- timerRef.current = setTimeout(() => connect(), delay);
6970
- }
6971
- }
6972
- });
6973
- }, [enabled]);
6974
- useEffect(() => {
6975
- mountedRef.current = true;
6976
- shouldReconnectRef.current = true;
6977
- if (enabled) connect();
6978
- return () => {
6979
- mountedRef.current = false;
6980
- cleanup();
6981
- };
6982
- }, [enabled, connect, cleanup]);
6983
- const send = useCallback((data) => {
6984
- wsRef.current?.send(data);
6985
- }, []);
6986
- const close = useCallback(() => {
6987
- shouldReconnectRef.current = false;
6988
- cleanup();
6989
- setReadyState(WebSocket.CLOSED);
6990
- }, [cleanup]);
6991
- const reconnectFn = useCallback(() => {
6992
- retryRef.current = 0;
6993
- shouldReconnectRef.current = true;
6994
- cleanup();
6995
- connect();
6996
- }, [cleanup, connect]);
6997
- return { send, close, readyState, lastMessage, reconnect: reconnectFn };
6998
- }
6999
-
7000
- // use-action.ts
7001
- import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
7002
- function getCsrfToken() {
7003
- if (typeof document === "undefined") return void 0;
7004
- const match = document.cookie.match(/(?:^|;\s*)_csrf=([^;]+)/);
7005
- return match ? decodeURIComponent(match[1]) : void 0;
7006
- }
7007
- function useAction(url, options) {
7008
- const { method = "POST", headers, onSuccess, onError } = options ?? {};
7009
- const [data, setData] = useState2(null);
7010
- const [error, setError] = useState2(null);
7011
- const [pending, setPending] = useState2(false);
7012
- const mountedRef = useRef2(true);
7013
- const submit = useCallback2(async (body) => {
7014
- setPending(true);
7015
- setError(null);
7016
- try {
7017
- const csrfToken = getCsrfToken();
7018
- const hdrs = { ...headers };
7019
- if (csrfToken) hdrs["x-csrf-token"] = csrfToken;
7020
- if (body && typeof body === "object" && !(body instanceof FormData)) {
7021
- hdrs["content-type"] = "application/json";
7022
- }
7023
- const res = await fetch(url, {
7024
- method,
7025
- headers: hdrs,
7026
- body: body instanceof FormData ? body : body !== void 0 ? JSON.stringify(body) : void 0
7027
- });
7028
- if (!res.ok) {
7029
- const text2 = await res.text();
7030
- throw new Error(text2 || `HTTP ${res.status}`);
7031
- }
7032
- const result = res.status === 204 ? void 0 : await res.json();
7033
- if (mountedRef.current) {
7034
- setData(result);
7035
- onSuccess?.(result);
7036
- }
7037
- return result;
7038
- } catch (err) {
7039
- const e = err instanceof Error ? err : new Error(String(err));
7040
- if (mountedRef.current) {
7041
- setError(e);
7042
- onError?.(e);
7043
- }
7044
- return void 0;
7045
- } finally {
7046
- if (mountedRef.current) setPending(false);
7047
- }
7048
- }, [url, method, headers, onSuccess, onError]);
7049
- const reset = useCallback2(() => {
7050
- setData(null);
7051
- setError(null);
7052
- }, []);
7053
- return { submit, data, error, pending, reset };
7054
- }
7055
-
7056
- // client-router.ts
7057
- import { createElement as createElement2, useCallback as useCallback3 } from "react";
7058
- async function navigate(href) {
7059
- if (typeof document === "undefined") return;
7060
- const url = new URL(href, location.origin);
7061
- if (url.origin !== location.origin) {
7062
- location.href = href;
7063
- return;
7064
- }
7065
- const html = await fetch(url.pathname + url.search, {
7066
- headers: { accept: "text/html" }
7067
- }).then((r) => r.text());
7068
- const doc = new DOMParser().parseFromString(html, "text/html");
7069
- const rootEl = doc.getElementById("__weifuwu_root");
7070
- if (!rootEl) {
7071
- location.href = href;
7072
- return;
7073
- }
7074
- const newHtml = rootEl.innerHTML;
7075
- const propsMatch = html.match(/window\.__WEIFUWU_PROPS=(.+?)<\/script>/);
7076
- if (!propsMatch) {
7077
- location.href = href;
7078
- return;
7079
- }
7080
- const bundleMatch = html.match(/src="(\/__wfw\/client\/[^"]+\.js)"/);
7081
- const bundleUrl = bundleMatch ? bundleMatch[1] : null;
7082
- const currentRoot = document.getElementById("__weifuwu_root");
7083
- if (!currentRoot) {
7084
- location.href = href;
7085
- return;
7086
- }
7087
- ;
7088
- window.__WEIFUWU_ROOT?.unmount();
7089
- currentRoot.innerHTML = newHtml;
7090
- window.__WEIFUWU_PROPS = JSON.parse(propsMatch[1]);
7091
- history.pushState(null, "", url.pathname + url.search);
7092
- if (bundleUrl) {
7093
- const cacheBust = bundleUrl.includes("?") ? "&_t=" : "?_t=";
7094
- try {
7095
- await import(
7096
- /* @vite-ignore */
7097
- `${bundleUrl}${cacheBust}${Date.now()}`
7098
- );
7099
- } catch (e) {
7100
- console.error("[weifuwu/router] hydration failed:", e);
7101
- }
7102
- }
7103
- }
7104
- function useNavigate() {
7105
- return useCallback3((href) => navigate(href), []);
7106
- }
7107
- function Link({ href, children, onClick, ...props }) {
7108
- const handleClick = useCallback3((e) => {
7109
- if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
7110
- e.preventDefault();
7111
- navigate(href);
7112
- onClick?.(e);
7113
- }, [href, onClick]);
7114
- return createElement2("a", { href, onClick: handleClick, ...props }, children);
7115
- }
7116
-
7117
6920
  // csrf.ts
7118
6921
  function csrf(options) {
7119
6922
  const cookieName = options?.cookie ?? "_csrf";
@@ -8305,7 +8108,6 @@ function registerWorker(url) {
8305
8108
  };
8306
8109
  }
8307
8110
  export {
8308
- Link,
8309
8111
  Router,
8310
8112
  TsxContext,
8311
8113
  agent,
@@ -8339,7 +8141,6 @@ export {
8339
8141
  logger,
8340
8142
  mailer,
8341
8143
  messager,
8342
- navigate,
8343
8144
  openai,
8344
8145
  opencode,
8345
8146
  postgres,
@@ -8362,10 +8163,7 @@ export {
8362
8163
  tool2 as tool,
8363
8164
  tsx,
8364
8165
  upload,
8365
- useAction,
8366
- useNavigate,
8367
8166
  useTsx,
8368
- useWebsocket,
8369
8167
  user,
8370
8168
  validate
8371
8169
  };
@@ -0,0 +1,6 @@
1
+ export { useWebsocket } from './use-websocket.ts';
2
+ export type { UseWebsocketOptions, UseWebsocketReturn } from './use-websocket.ts';
3
+ export { useAction } from './use-action.ts';
4
+ export type { UseActionOptions, UseActionReturn } from './use-action.ts';
5
+ export { Link, useNavigate, navigate } from './client-router.ts';
6
+ export { TsxContext, useTsx } from './tsx-context.ts';
package/dist/react.js ADDED
@@ -0,0 +1,216 @@
1
+ // use-websocket.ts
2
+ import { useEffect, useRef, useCallback, useState } from "react";
3
+ var RECONNECT_DELAY = 3e3;
4
+ var MAX_RETRIES = 10;
5
+ function resolveUrl(url) {
6
+ return typeof url === "function" ? url() : url;
7
+ }
8
+ function useWebsocket(url, options) {
9
+ const { onMessage, reconnect: reconnectOpt = true, protocols, enabled = true } = options ?? {};
10
+ const [lastMessage, setLastMessage] = useState(null);
11
+ const [readyState, setReadyState] = useState(WebSocket.CLOSED);
12
+ const wsRef = useRef(null);
13
+ const retryRef = useRef(0);
14
+ const timerRef = useRef(void 0);
15
+ const mountedRef = useRef(true);
16
+ const shouldReconnectRef = useRef(true);
17
+ const urlRef = useRef(url);
18
+ const optsRef = useRef({ onMessage, reconnectOpt, protocols });
19
+ urlRef.current = url;
20
+ optsRef.current = { onMessage, reconnectOpt, protocols };
21
+ const cleanup = useCallback(() => {
22
+ clearTimeout(timerRef.current);
23
+ wsRef.current?.close();
24
+ wsRef.current = null;
25
+ }, []);
26
+ const connect = useCallback(() => {
27
+ if (!mountedRef.current || !enabled) return;
28
+ const resolved = resolveUrl(urlRef.current);
29
+ if (!resolved) return;
30
+ wsRef.current?.close();
31
+ const ws = new WebSocket(resolved, optsRef.current.protocols);
32
+ wsRef.current = ws;
33
+ setReadyState(WebSocket.CONNECTING);
34
+ ws.addEventListener("open", () => {
35
+ if (!mountedRef.current) return;
36
+ retryRef.current = 0;
37
+ setReadyState(WebSocket.OPEN);
38
+ });
39
+ ws.addEventListener("message", (e) => {
40
+ if (!mountedRef.current) return;
41
+ const data = typeof e.data === "string" ? e.data : String(e.data);
42
+ setLastMessage(data);
43
+ optsRef.current.onMessage?.(data);
44
+ });
45
+ ws.addEventListener("close", () => {
46
+ if (!mountedRef.current) return;
47
+ setReadyState(WebSocket.CLOSED);
48
+ const ro = optsRef.current.reconnectOpt;
49
+ if (ro && shouldReconnectRef.current && mountedRef.current) {
50
+ const maxRetries = typeof ro === "object" ? ro.maxRetries ?? MAX_RETRIES : MAX_RETRIES;
51
+ const delay = typeof ro === "object" ? ro.delay ?? RECONNECT_DELAY : RECONNECT_DELAY;
52
+ if (retryRef.current < maxRetries) {
53
+ retryRef.current++;
54
+ timerRef.current = setTimeout(() => connect(), delay);
55
+ }
56
+ }
57
+ });
58
+ }, [enabled]);
59
+ useEffect(() => {
60
+ mountedRef.current = true;
61
+ shouldReconnectRef.current = true;
62
+ if (enabled) connect();
63
+ return () => {
64
+ mountedRef.current = false;
65
+ cleanup();
66
+ };
67
+ }, [enabled, connect, cleanup]);
68
+ const send = useCallback((data) => {
69
+ wsRef.current?.send(data);
70
+ }, []);
71
+ const close = useCallback(() => {
72
+ shouldReconnectRef.current = false;
73
+ cleanup();
74
+ setReadyState(WebSocket.CLOSED);
75
+ }, [cleanup]);
76
+ const reconnectFn = useCallback(() => {
77
+ retryRef.current = 0;
78
+ shouldReconnectRef.current = true;
79
+ cleanup();
80
+ connect();
81
+ }, [cleanup, connect]);
82
+ return { send, close, readyState, lastMessage, reconnect: reconnectFn };
83
+ }
84
+
85
+ // use-action.ts
86
+ import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
87
+ function getCsrfToken() {
88
+ if (typeof document === "undefined") return void 0;
89
+ const match = document.cookie.match(/(?:^|;\s*)_csrf=([^;]+)/);
90
+ return match ? decodeURIComponent(match[1]) : void 0;
91
+ }
92
+ function useAction(url, options) {
93
+ const { method = "POST", headers, onSuccess, onError } = options ?? {};
94
+ const [data, setData] = useState2(null);
95
+ const [error, setError] = useState2(null);
96
+ const [pending, setPending] = useState2(false);
97
+ const mountedRef = useRef2(true);
98
+ const submit = useCallback2(async (body) => {
99
+ setPending(true);
100
+ setError(null);
101
+ try {
102
+ const csrfToken = getCsrfToken();
103
+ const hdrs = { ...headers };
104
+ if (csrfToken) hdrs["x-csrf-token"] = csrfToken;
105
+ if (body && typeof body === "object" && !(body instanceof FormData)) {
106
+ hdrs["content-type"] = "application/json";
107
+ }
108
+ const res = await fetch(url, {
109
+ method,
110
+ headers: hdrs,
111
+ body: body instanceof FormData ? body : body !== void 0 ? JSON.stringify(body) : void 0
112
+ });
113
+ if (!res.ok) {
114
+ const text = await res.text();
115
+ throw new Error(text || `HTTP ${res.status}`);
116
+ }
117
+ const result = res.status === 204 ? void 0 : await res.json();
118
+ if (mountedRef.current) {
119
+ setData(result);
120
+ onSuccess?.(result);
121
+ }
122
+ return result;
123
+ } catch (err) {
124
+ const e = err instanceof Error ? err : new Error(String(err));
125
+ if (mountedRef.current) {
126
+ setError(e);
127
+ onError?.(e);
128
+ }
129
+ return void 0;
130
+ } finally {
131
+ if (mountedRef.current) setPending(false);
132
+ }
133
+ }, [url, method, headers, onSuccess, onError]);
134
+ const reset = useCallback2(() => {
135
+ setData(null);
136
+ setError(null);
137
+ }, []);
138
+ return { submit, data, error, pending, reset };
139
+ }
140
+
141
+ // client-router.ts
142
+ import { createElement, useCallback as useCallback3 } from "react";
143
+ async function navigate(href) {
144
+ if (typeof document === "undefined") return;
145
+ const url = new URL(href, location.origin);
146
+ if (url.origin !== location.origin) {
147
+ location.href = href;
148
+ return;
149
+ }
150
+ const html = await fetch(url.pathname + url.search, {
151
+ headers: { accept: "text/html" }
152
+ }).then((r) => r.text());
153
+ const doc = new DOMParser().parseFromString(html, "text/html");
154
+ const rootEl = doc.getElementById("__weifuwu_root");
155
+ if (!rootEl) {
156
+ location.href = href;
157
+ return;
158
+ }
159
+ const newHtml = rootEl.innerHTML;
160
+ const propsMatch = html.match(/window\.__WEIFUWU_PROPS=(.+?)<\/script>/);
161
+ if (!propsMatch) {
162
+ location.href = href;
163
+ return;
164
+ }
165
+ const bundleMatch = html.match(/src="(\/__wfw\/client\/[^"]+\.js)"/);
166
+ const bundleUrl = bundleMatch ? bundleMatch[1] : null;
167
+ const currentRoot = document.getElementById("__weifuwu_root");
168
+ if (!currentRoot) {
169
+ location.href = href;
170
+ return;
171
+ }
172
+ ;
173
+ window.__WEIFUWU_ROOT?.unmount();
174
+ currentRoot.innerHTML = newHtml;
175
+ window.__WEIFUWU_PROPS = JSON.parse(propsMatch[1]);
176
+ history.pushState(null, "", url.pathname + url.search);
177
+ if (bundleUrl) {
178
+ const cacheBust = bundleUrl.includes("?") ? "&_t=" : "?_t=";
179
+ try {
180
+ await import(
181
+ /* @vite-ignore */
182
+ `${bundleUrl}${cacheBust}${Date.now()}`
183
+ );
184
+ } catch (e) {
185
+ console.error("[weifuwu/router] hydration failed:", e);
186
+ }
187
+ }
188
+ }
189
+ function useNavigate() {
190
+ return useCallback3((href) => navigate(href), []);
191
+ }
192
+ function Link({ href, children, onClick, ...props }) {
193
+ const handleClick = useCallback3((e) => {
194
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
195
+ e.preventDefault();
196
+ navigate(href);
197
+ onClick?.(e);
198
+ }, [href, onClick]);
199
+ return createElement("a", { href, onClick: handleClick, ...props }, children);
200
+ }
201
+
202
+ // tsx-context.ts
203
+ import { createContext, useContext } from "react";
204
+ var TsxContext = createContext({ params: {}, query: {} });
205
+ function useTsx() {
206
+ return useContext(TsxContext);
207
+ }
208
+ export {
209
+ Link,
210
+ TsxContext,
211
+ navigate,
212
+ useAction,
213
+ useNavigate,
214
+ useTsx,
215
+ useWebsocket
216
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.16.4",
3
+ "version": "0.16.6",
4
4
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": "./dist/index.js",
9
- "./tsx": "./dist/index.js"
9
+ "./react": "./dist/react.js"
10
10
  },
11
11
  "bin": {
12
12
  "weifuwu": "dist/cli.js"
@@ -18,7 +18,7 @@
18
18
  "LICENSE"
19
19
  ],
20
20
  "scripts": {
21
- "build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --packages=external && esbuild cli.ts --bundle --format=esm --platform=node --outfile=dist/cli.js --packages=external",
21
+ "build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --packages=external && esbuild cli.ts --bundle --format=esm --platform=node --outfile=dist/cli.js --packages=external && esbuild react.ts --bundle --format=esm --outfile=dist/react.js --external:react --external:react-dom",
22
22
  "prepublishOnly": "npm run build && tsc --emitDeclarationOnly --outdir dist",
23
23
  "test": "node --test 'test/**/*.test.ts'"
24
24
  },