weifuwu 0.33.0 → 0.33.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.
@@ -0,0 +1,320 @@
1
+ // src/client/signal.ts
2
+ var currentEffect = null;
3
+ var Signal = class {
4
+ #value;
5
+ #listeners = /* @__PURE__ */ new Set();
6
+ constructor(value) {
7
+ this.#value = value;
8
+ }
9
+ get value() {
10
+ if (currentEffect) this.#listeners.add(currentEffect);
11
+ return this.#value;
12
+ }
13
+ set value(v) {
14
+ if (v !== this.#value) {
15
+ this.#value = v;
16
+ const fns = [...this.#listeners];
17
+ for (const fn of fns) fn();
18
+ }
19
+ }
20
+ };
21
+ function signal(initial) {
22
+ return new Signal(initial);
23
+ }
24
+ function isSignal(value) {
25
+ return value instanceof Signal;
26
+ }
27
+ function effect(fn) {
28
+ const wrapper = () => {
29
+ const prev = currentEffect;
30
+ currentEffect = wrapper;
31
+ try {
32
+ fn();
33
+ } finally {
34
+ currentEffect = prev;
35
+ }
36
+ };
37
+ wrapper();
38
+ return () => {
39
+ };
40
+ }
41
+ function computed(fn) {
42
+ const s = new Signal(void 0);
43
+ effect(() => {
44
+ s.value = fn();
45
+ });
46
+ return s;
47
+ }
48
+
49
+ // src/client/jsx-runtime.ts
50
+ var currentCtx = null;
51
+ function setCtx(ctx) {
52
+ currentCtx = ctx;
53
+ }
54
+ function setProp(el, key, value) {
55
+ if (key === "class" || key === "className") {
56
+ if (isSignal(value)) {
57
+ effect(() => {
58
+ el.className = String(value.value);
59
+ });
60
+ } else {
61
+ el.className = String(value ?? "");
62
+ }
63
+ } else if (key === "style" && typeof value === "object" && value !== null) {
64
+ Object.assign(el.style, value);
65
+ } else if (key.startsWith("on") && typeof value === "function") {
66
+ el.addEventListener(key.slice(2).toLowerCase(), value);
67
+ } else if (key === "ref" && typeof value === "function") {
68
+ value(el);
69
+ } else if (isSignal(value)) {
70
+ effect(() => {
71
+ const v = value.value;
72
+ if (v == null || v === false) el.removeAttribute(key);
73
+ else if (v === true) el.setAttribute(key, "");
74
+ else el.setAttribute(key, String(v));
75
+ });
76
+ } else if (value != null && value !== false) {
77
+ if (value === true) el.setAttribute(key, "");
78
+ else el.setAttribute(key, String(value));
79
+ }
80
+ }
81
+ function appendChild(parent, child) {
82
+ if (child == null || child === false || child === true) return;
83
+ if (Array.isArray(child)) {
84
+ child.forEach((c) => appendChild(parent, c));
85
+ return;
86
+ }
87
+ if (child instanceof Node) {
88
+ parent.appendChild(child);
89
+ return;
90
+ }
91
+ if (isSignal(child)) {
92
+ const text = document.createTextNode("");
93
+ effect(() => {
94
+ text.textContent = String(child.value);
95
+ });
96
+ parent.appendChild(text);
97
+ return;
98
+ }
99
+ parent.appendChild(document.createTextNode(String(child)));
100
+ }
101
+ function jsx(type, props, ...children) {
102
+ if (typeof type === "function") {
103
+ const merged = children.length > 0 ? { ...props, children } : props;
104
+ return type(merged, currentCtx) ?? document.createDocumentFragment();
105
+ }
106
+ const el = document.createElement(type);
107
+ if (props) {
108
+ for (const [k, v] of Object.entries(props)) {
109
+ if (k !== "children") setProp(el, k, v);
110
+ }
111
+ }
112
+ const childList = children.length > 0 ? children : props?.children != null ? [props.children] : [];
113
+ for (const child of childList) appendChild(el, child);
114
+ return el;
115
+ }
116
+ function jsxs(type, props, ...children) {
117
+ return jsx(type, props, ...children);
118
+ }
119
+ function jsxDEV(type, props, _key, _isStatic, _source, _self) {
120
+ return jsx(type, props, ...props?.children ? [props.children] : []);
121
+ }
122
+ function Fragment(props, ...children) {
123
+ const frag = document.createDocumentFragment();
124
+ for (const child of children) appendChild(frag, child);
125
+ return frag;
126
+ }
127
+ function domMount(root, app) {
128
+ const container = typeof root === "string" ? document.querySelector(root) : root;
129
+ if (!container) throw new Error(`mount target not found: ${root}`);
130
+ container.innerHTML = "";
131
+ container.appendChild(app);
132
+ }
133
+ function toNode(v) {
134
+ if (v instanceof Node) return v;
135
+ if (typeof v === "function") return toNode(v());
136
+ return document.createTextNode(String(v ?? ""));
137
+ }
138
+ function Show({ when, children, fallback }) {
139
+ const el = document.createElement("div");
140
+ function render(show) {
141
+ el.textContent = "";
142
+ if (show && children != null) {
143
+ el.appendChild(toNode(children));
144
+ } else if (!show && fallback != null) {
145
+ el.appendChild(toNode(fallback));
146
+ }
147
+ }
148
+ if (isSignal(when)) {
149
+ effect(() => render(Boolean(when.value)));
150
+ } else {
151
+ render(Boolean(when));
152
+ }
153
+ return el;
154
+ }
155
+ function For({ each, children }) {
156
+ const el = document.createElement("div");
157
+ function render(list) {
158
+ el.textContent = "";
159
+ for (let i = 0; i < list.length; i++) {
160
+ el.appendChild(children(list[i], i));
161
+ }
162
+ }
163
+ if (isSignal(each)) {
164
+ effect(() => render(each.value));
165
+ } else {
166
+ render(each);
167
+ }
168
+ return el;
169
+ }
170
+
171
+ // src/client/app.ts
172
+ function createApp() {
173
+ const middlewares = [];
174
+ const provides = /* @__PURE__ */ new Map();
175
+ let ctx = {
176
+ route: {
177
+ path: window.location.pathname,
178
+ params: {},
179
+ query: Object.fromEntries(new URLSearchParams(window.location.search)),
180
+ hash: window.location.hash,
181
+ component: null
182
+ },
183
+ app: {
184
+ navigate(path) {
185
+ window.history.pushState({}, "", path);
186
+ ctx.route.path = path;
187
+ ctx.route.query = Object.fromEntries(new URLSearchParams(window.location.search));
188
+ ctx.route.hash = window.location.hash;
189
+ window.dispatchEvent(new CustomEvent("wefu:navigate", { detail: { path } }));
190
+ }
191
+ },
192
+ provide(key, value) {
193
+ provides.set(key, value);
194
+ },
195
+ inject(key) {
196
+ return provides.get(key) ?? null;
197
+ }
198
+ };
199
+ return {
200
+ get ctx() {
201
+ return ctx;
202
+ },
203
+ use(mw) {
204
+ middlewares.push(mw);
205
+ return this;
206
+ },
207
+ mount(rootSelector, RootComponent) {
208
+ for (const mw of middlewares) {
209
+ ctx = mw(ctx);
210
+ }
211
+ setCtx(ctx);
212
+ const app = jsx(RootComponent, {});
213
+ domMount(rootSelector, app);
214
+ setCtx(null);
215
+ }
216
+ };
217
+ }
218
+
219
+ // src/client/router.ts
220
+ function router(opts) {
221
+ const mode = opts.mode ?? "hash";
222
+ const matchers = opts.routes.map((route) => {
223
+ const parts = route.path.split("/").filter(Boolean);
224
+ const keys = [];
225
+ const reStr = "^/" + parts.map((p) => {
226
+ if (p.startsWith(":")) {
227
+ keys.push(p.slice(1));
228
+ return "([^/]+)";
229
+ }
230
+ return p;
231
+ }).join("/") + "$";
232
+ return { re: new RegExp(reStr), keys, route };
233
+ });
234
+ function matchPath(path) {
235
+ for (const { re, keys, route } of matchers) {
236
+ const m = path.match(re);
237
+ if (m) {
238
+ const params = {};
239
+ keys.forEach((k, i) => {
240
+ params[k] = decodeURIComponent(m[i + 1]);
241
+ });
242
+ return { component: route.component, params, title: route.title, auth: route.auth };
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+ return (ctx) => {
248
+ function handleRoute() {
249
+ const raw = mode === "hash" ? window.location.hash.slice(1) || "/" : window.location.pathname + window.location.search;
250
+ const [path, qs] = raw.split("?");
251
+ ctx.route.query = Object.fromEntries(new URLSearchParams(qs ?? ""));
252
+ const matched = matchPath(path);
253
+ if (matched) {
254
+ ctx.route.path = path;
255
+ ctx.route.params = matched.params;
256
+ ctx.route.component = matched.component;
257
+ ctx.route.title = matched.title;
258
+ ctx.route.auth = matched.auth;
259
+ if (matched.title) document.title = matched.title;
260
+ if (matched.auth && !ctx.user) {
261
+ setTimeout(() => ctx.app.navigate("/login"), 0);
262
+ return;
263
+ }
264
+ } else {
265
+ ctx.route.component = opts.notFound ?? null;
266
+ }
267
+ window.dispatchEvent(new CustomEvent("wefu:route", { detail: { path } }));
268
+ }
269
+ const origNavigate = ctx.app.navigate;
270
+ ctx.app.navigate = (path) => {
271
+ if (mode === "hash") {
272
+ window.location.hash = "#" + path;
273
+ } else {
274
+ window.history.pushState({}, "", path);
275
+ handleRoute();
276
+ }
277
+ };
278
+ if (mode === "hash") {
279
+ window.addEventListener("hashchange", handleRoute);
280
+ } else {
281
+ window.addEventListener("popstate", handleRoute);
282
+ }
283
+ handleRoute();
284
+ return ctx;
285
+ };
286
+ }
287
+ function RouteView(_props, ctx) {
288
+ const el = document.createElement("div");
289
+ function render() {
290
+ const Component = ctx.route.component;
291
+ if (!Component) {
292
+ el.textContent = "";
293
+ return;
294
+ }
295
+ el.textContent = "";
296
+ setCtx(ctx);
297
+ const page = jsx(Component, {});
298
+ el.appendChild(page);
299
+ setCtx(null);
300
+ }
301
+ render();
302
+ window.addEventListener("wefu:route", render);
303
+ return el;
304
+ }
305
+ export {
306
+ For,
307
+ Fragment,
308
+ RouteView,
309
+ Show,
310
+ computed,
311
+ createApp,
312
+ domMount,
313
+ effect,
314
+ isSignal,
315
+ jsx,
316
+ jsxDEV,
317
+ jsxs,
318
+ router,
319
+ signal
320
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * weifuwu/client router — 路由中间件 + RouteView 组件
3
+ *
4
+ * ```tsx
5
+ * import { createApp, router, RouteView } from 'weifuwu/client'
6
+ *
7
+ * const app = createApp()
8
+ * app.use(router({
9
+ * routes: [
10
+ * { path: '/', component: HomePage },
11
+ * { path: '/chat/:id', component: ChatPage },
12
+ * ],
13
+ * }))
14
+ *
15
+ * function AppShell(_, ctx) {
16
+ * return <div><Header /><RouteView /></div>
17
+ * }
18
+ * app.mount('#root', AppShell)
19
+ * ```
20
+ */
21
+ import type { WfuiContext, AppMiddleware, RouteDef } from './types.ts';
22
+ import type { Component } from './jsx-runtime.ts';
23
+ export interface RouterOptions {
24
+ /** 路由模式,默认 'hash' */
25
+ mode?: 'hash' | 'history';
26
+ /** 路由表 */
27
+ routes: RouteDef[];
28
+ /** 404 组件 */
29
+ notFound?: Component;
30
+ }
31
+ /**
32
+ * 路由中间件 — 注入 ctx.route.component + 改写 ctx.app.navigate
33
+ */
34
+ export declare function router(opts: RouterOptions): AppMiddleware;
35
+ /**
36
+ * RouteView — 渲染当前路由匹配的组件
37
+ *
38
+ * 放在布局组件中,URL 变化时自动切换内容。
39
+ *
40
+ * ```tsx
41
+ * function AppShell(_, ctx) {
42
+ * return (
43
+ * <div class="app">
44
+ * <Header user={ctx.user} />
45
+ * <main><RouteView /></main>
46
+ * </div>
47
+ * )
48
+ * }
49
+ * ```
50
+ */
51
+ export declare function RouteView(_props: {}, ctx: WfuiContext): Node;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * weifuwu/client signal — 响应式系统核心
3
+ *
4
+ * signal(value) 声明响应式数据
5
+ * effect(fn) 自动追踪依赖,变化时重跑 fn
6
+ * computed(fn) 衍生信号
7
+ */
8
+ type Listener = () => void;
9
+ export declare class Signal<T = unknown> {
10
+ #private;
11
+ constructor(value: T);
12
+ get value(): T;
13
+ set value(v: T);
14
+ }
15
+ export declare function signal<T>(initial: T): Signal<T>;
16
+ export declare function isSignal(value: unknown): value is Signal;
17
+ export declare function effect(fn: Listener): () => void;
18
+ export declare function computed<T>(fn: () => T): Signal<T>;
19
+ export {};
@@ -0,0 +1,45 @@
1
+ /**
2
+ * wefu 类型定义
3
+ */
4
+ import type { Component } from './jsx-runtime.ts';
5
+ /**
6
+ * wefu 上下文 — 组件通过第二个参数访问
7
+ *
8
+ * ```tsx
9
+ * function MyPage(props, ctx: WfuiContext) {
10
+ * ctx.app.navigate('/chat')
11
+ * ctx.provide('key', value)
12
+ * const val = ctx.inject('key')
13
+ * }
14
+ * ```
15
+ */
16
+ export interface WfuiContext {
17
+ route: {
18
+ path: string;
19
+ params: Record<string, string>;
20
+ query: Record<string, string>;
21
+ hash: string;
22
+ /** 当前匹配的路由组件(由 router 中间件注入) */
23
+ component: Component | null;
24
+ /** 当前匹配的路由配置 */
25
+ title?: string;
26
+ auth?: boolean;
27
+ };
28
+ app: {
29
+ navigate: (path: string) => void;
30
+ };
31
+ /** 跨组件共享数据(类似 React Context / Vue provide/inject) */
32
+ provide: <T>(key: string, value: T) => void;
33
+ inject: <T>(key: string) => T | null;
34
+ /** 中间件注入扩展 */
35
+ [key: string]: unknown;
36
+ }
37
+ /** 中间件签名 */
38
+ export type AppMiddleware = (ctx: WfuiContext) => WfuiContext | Promise<WfuiContext>;
39
+ /** 路由定义 */
40
+ export interface RouteDef {
41
+ path: string;
42
+ component: Component;
43
+ auth?: boolean;
44
+ title?: string;
45
+ }
package/dist/index.d.ts CHANGED
@@ -17,10 +17,6 @@ export { upload } from './middleware/upload.ts';
17
17
  export type { UploadOptions, UploadedFile, UploadModule } from './middleware/upload.ts';
18
18
  export { sandbox } from './middleware/sandbox.ts';
19
19
  export type { SandboxOptions, Sandbox } from './middleware/sandbox.ts';
20
- export { esbuildDev } from './middleware/esbuild-dev.ts';
21
- export type { EsbuildDevEntry, EsbuildDevOptions } from './middleware/esbuild-dev.ts';
22
- export { tailwindDev } from './middleware/tailwind-dev.ts';
23
- export type { TailwindDevEntry, TailwindDevOptions } from './middleware/tailwind-dev.ts';
24
20
  export { rateLimit } from './middleware/rate-limit.ts';
25
21
  export type { RateLimitOptions } from './middleware/rate-limit.ts';
26
22
  export { compress } from './middleware/compress.ts';
@@ -37,9 +33,6 @@ export { createHub } from './hub.ts';
37
33
  export type { Hub, HubOptions } from './hub.ts';
38
34
  export { queue } from './queue/index.ts';
39
35
  export type { QueueOptions, QueueJob, Queue, QueueInjected } from './queue/types.ts';
40
- export { react, reactRouter } from './react/index.ts';
41
- export type { ReactOptions, RenderOptions, ReactRouterOptions, ReactAppOptions } from './react/types.ts';
42
- export { useServerData, ServerDataContext, Link, ErrorBoundary } from './react/index.ts';
43
36
  export { ai } from './ai/index.ts';
44
37
  export type { AiOptions, Ai } from './ai/index.ts';
45
38
  export { agent } from './ai/agent.ts';