weifuwu 0.30.0 → 0.30.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.
@@ -1,2573 +0,0 @@
1
- // src/react/ssr/tsx-context.ts
2
- import { useSyncExternalStore, createContext } from "react";
3
- var DEFAULT_CTX = {
4
- params: {},
5
- query: {},
6
- parsed: {},
7
- loaderData: {},
8
- env: {},
9
- user: {},
10
- flash: {}
11
- };
12
- var KEY = "__WEIFUWU_CTX_STORE";
13
- function getStore() {
14
- if (typeof globalThis !== "undefined" && globalThis[KEY]) {
15
- return globalThis[KEY];
16
- }
17
- const s = {
18
- _ctx: DEFAULT_CTX,
19
- _snapshot: {
20
- params: DEFAULT_CTX.params,
21
- query: DEFAULT_CTX.query,
22
- user: DEFAULT_CTX.user,
23
- parsed: DEFAULT_CTX.parsed,
24
- theme: DEFAULT_CTX.theme,
25
- i18n: DEFAULT_CTX.i18n,
26
- loaderData: DEFAULT_CTX.loaderData,
27
- env: DEFAULT_CTX.env
28
- },
29
- _listeners: /* @__PURE__ */ new Set(),
30
- _rebuilders: [],
31
- _alsGetStore: null
32
- };
33
- if (typeof globalThis !== "undefined") {
34
- ;
35
- globalThis[KEY] = s;
36
- }
37
- return s;
38
- }
39
- var store = getStore();
40
- function addCtxRebuilder(fn) {
41
- store._rebuilders.push(fn);
42
- }
43
- var subscribe = (cb) => {
44
- store._listeners.add(cb);
45
- return () => {
46
- store._listeners.delete(cb);
47
- };
48
- };
49
- var getSnapshot = () => store._snapshot;
50
- function __registerAls(getStore2) {
51
- store._alsGetStore = getStore2;
52
- }
53
- function setCtx(value) {
54
- if (typeof window !== "undefined") {
55
- for (const r of store._rebuilders) {
56
- const rebuilt = r(value);
57
- if (rebuilt) Object.assign(value, rebuilt);
58
- }
59
- }
60
- store._ctx = { ...store._ctx, ...value };
61
- store._snapshot = {
62
- params: store._ctx.params,
63
- query: store._ctx.query,
64
- user: store._ctx.user,
65
- parsed: store._ctx.parsed,
66
- theme: store._ctx.theme,
67
- i18n: store._ctx.i18n,
68
- loaderData: store._ctx.loaderData,
69
- env: store._ctx.env
70
- };
71
- if (typeof window !== "undefined") {
72
- ;
73
- window.__WEIFUWU_CTX = { ...window.__WEIFUWU_CTX, ...value };
74
- }
75
- store._listeners.forEach((fn) => fn());
76
- }
77
- function useCtx() {
78
- if (typeof window !== "undefined") {
79
- return useSyncExternalStore(subscribe, getSnapshot);
80
- }
81
- const alsStore = store._alsGetStore?.();
82
- return alsStore ?? store._ctx;
83
- }
84
- function useLoaderData() {
85
- const ctx = useCtx();
86
- return ctx.loaderData;
87
- }
88
- var TsxContext = createContext(DEFAULT_CTX);
89
-
90
- // src/react/ssr/ssr.ts
91
- import { createElement as createElement3 } from "react";
92
- import { createHash as createHash4 } from "node:crypto";
93
- import { existsSync as existsSync6, readdirSync } from "node:fs";
94
- import { readdir, stat } from "node:fs/promises";
95
- import { dirname as dirname4, join as join4, resolve as resolve6, relative as relative3 } from "node:path";
96
- import { AsyncLocalStorage } from "node:async_hooks";
97
-
98
- // src/react/ssr/compile.ts
99
- import * as esbuild2 from "esbuild";
100
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
101
- import { join, resolve as resolve2, dirname as dirname2 } from "node:path";
102
- import { pathToFileURL } from "node:url";
103
- import { createHash } from "node:crypto";
104
- import { createRequire as createRequire2 } from "node:module";
105
-
106
- // src/core/env.ts
107
- function isBundled() {
108
- return typeof __WFW_BUNDLED__ !== "undefined" ? __WFW_BUNDLED__ : false;
109
- }
110
- function isDev() {
111
- const env2 = process.env.NODE_ENV;
112
- return env2 !== "production" && env2 !== "test";
113
- }
114
- function isProd() {
115
- return process.env.NODE_ENV === "production";
116
- }
117
-
118
- // src/core/router.ts
119
- import { WebSocketServer } from "ws";
120
-
121
- // src/hub.ts
122
- function createHub(opts) {
123
- const prefix = opts?.prefix ?? "hub:";
124
- const channels = /* @__PURE__ */ new Map();
125
- const wsKeys = /* @__PURE__ */ new Map();
126
- let redisPub;
127
- let redisSub = null;
128
- if (opts?.redis) {
129
- redisPub = opts.redis;
130
- redisSub = opts.redis.duplicate();
131
- redisSub.on("message", (rawChannel, rawData) => {
132
- if (!rawChannel.startsWith(prefix)) return;
133
- const key = rawChannel.slice(prefix.length);
134
- const members = channels.get(key);
135
- if (!members) return;
136
- for (const ws of members) {
137
- try {
138
- ws.send(rawData);
139
- } catch {
140
- }
141
- }
142
- });
143
- }
144
- function join5(key, ws) {
145
- if (!channels.has(key)) {
146
- channels.set(key, /* @__PURE__ */ new Set());
147
- redisSub?.subscribe(`${prefix}${key}`);
148
- }
149
- channels.get(key).add(ws);
150
- let keys = wsKeys.get(ws);
151
- if (!keys) {
152
- keys = /* @__PURE__ */ new Set();
153
- wsKeys.set(ws, keys);
154
- }
155
- keys.add(key);
156
- if (typeof ws.addEventListener === "function") {
157
- ws.addEventListener("close", () => removeFromChannels(ws));
158
- ws.addEventListener("error", () => removeFromChannels(ws));
159
- }
160
- }
161
- function removeFromChannels(ws) {
162
- const keys = wsKeys.get(ws);
163
- if (keys) {
164
- for (const key of keys) {
165
- const members = channels.get(key);
166
- if (members) {
167
- members.delete(ws);
168
- if (members.size === 0) channels.delete(key);
169
- }
170
- }
171
- wsKeys.delete(ws);
172
- }
173
- }
174
- function leave(ws) {
175
- removeFromChannels(ws);
176
- }
177
- function broadcast(key, data) {
178
- const msg = JSON.stringify(data);
179
- const members = channels.get(key);
180
- if (members) {
181
- const dead = [];
182
- for (const ws of members) {
183
- try {
184
- ws.send(msg);
185
- } catch {
186
- dead.push(ws);
187
- }
188
- }
189
- for (const ws of dead) removeFromChannels(ws);
190
- }
191
- redisPub?.publish(`${prefix}${key}`, msg);
192
- }
193
- async function close() {
194
- for (const ws of wsKeys.keys()) {
195
- removeFromChannels(ws);
196
- }
197
- channels.clear();
198
- wsKeys.clear();
199
- if (redisSub) {
200
- redisSub.removeAllListeners("message");
201
- await redisSub.quit();
202
- }
203
- redisPub = void 0;
204
- redisSub = null;
205
- }
206
- return { join: join5, leave, broadcast, close };
207
- }
208
-
209
- // src/core/router.ts
210
- var createTrieNode = () => ({
211
- children: /* @__PURE__ */ new Map(),
212
- handlers: /* @__PURE__ */ new Map(),
213
- middlewares: /* @__PURE__ */ new Map()
214
- });
215
- var createWsNode = () => ({
216
- children: /* @__PURE__ */ new Map(),
217
- middlewares: []
218
- });
219
- function createParamChild(node, segment, createNode) {
220
- const paramName = segment.slice(1);
221
- if (!node.children.has(":")) {
222
- const child2 = createNode();
223
- child2.param = paramName;
224
- node.children.set(":", child2);
225
- }
226
- const child = node.children.get(":");
227
- if (child.param !== paramName) {
228
- throw new Error(
229
- `Param name conflict: ":${child.param}" already registered, cannot register ":"${paramName}"`
230
- );
231
- }
232
- return child;
233
- }
234
- function getOrCreateChild(node, segment, createNode, allowWildcard) {
235
- if (allowWildcard && segment === "*") {
236
- node.wildcard = true;
237
- return node;
238
- }
239
- if (segment.startsWith(":")) return createParamChild(node, segment, createNode);
240
- if (!node.children.has(segment)) node.children.set(segment, createNode());
241
- return node.children.get(segment);
242
- }
243
- function matchChild(node, segment, params, allowWildcard = false) {
244
- if (node.children.has(segment)) return node.children.get(segment);
245
- if (node.children.has(":")) {
246
- const child = node.children.get(":");
247
- if (child.param) params[child.param] = decodeURIComponent(segment);
248
- return child;
249
- }
250
- if (allowWildcard && node.wildcard) return node;
251
- return null;
252
- }
253
- var Router = class _Router {
254
- root = createTrieNode();
255
- wsRoot = createWsNode();
256
- globalMws = [];
257
- errorHandler;
258
- _hasWildcard = false;
259
- _hub;
260
- _wss;
261
- /** Track which ctx fields have been injected so far (for dependency checking). */
262
- _ctxFields = /* @__PURE__ */ new Set();
263
- get wss() {
264
- if (!this._wss) this._wss = new WebSocketServer({ noServer: true });
265
- return this._wss;
266
- }
267
- get hub() {
268
- if (!this._hub) this._hub = createHub();
269
- return this._hub;
270
- }
271
- /** Inject a custom hub (e.g. with Redis for cross-process broadcast). */
272
- wsHub(hub) {
273
- this._hub = hub;
274
- return this;
275
- }
276
- use(arg1) {
277
- this.globalMws.push(arg1);
278
- this._checkMiddlewareMeta(arg1, "global");
279
- return this;
280
- }
281
- /**
282
- * Mount a sub-router at the given path prefix.
283
- * All routes from the sub-router are registered with the prefix.
284
- *
285
- * ```ts
286
- * const admin = new Router()
287
- * admin.get('/dashboard', handler)
288
- * app.mount('/admin', admin) // → GET /admin/dashboard
289
- * ```
290
- */
291
- mount(path, router) {
292
- this._mountRouter(path, router);
293
- return this;
294
- }
295
- /**
296
- * Check a middleware's dependency metadata and emit warnings if
297
- * required fields haven't been injected yet.
298
- * Attach __meta to a middleware function:
299
- *
300
- * ```ts
301
- * mw.__meta = { injects: ['sql'], depends: ['session'] }
302
- * ```
303
- */
304
- _checkMiddlewareMeta(mw, location2) {
305
- const meta = mw.__meta ?? (typeof mw === "object" && mw && "middleware" in mw ? mw.middleware().__meta : void 0);
306
- if (!meta) return;
307
- for (const dep of meta.depends) {
308
- if (!this._ctxFields.has(dep)) {
309
- console.warn(
310
- `[weifuwu] Middleware at "${location2}" depends on ctx.${dep} but it hasn't been registered yet.
311
- Register the provider before this middleware:
312
- app.use(${dep}()) // add before this middleware
313
- Current ctx fields: [${[...this._ctxFields].join(", ")}]`
314
- );
315
- }
316
- }
317
- for (const field of meta.injects) {
318
- this._ctxFields.add(field);
319
- }
320
- }
321
- // Route registration — returns Router<T> unchanged.
322
- // Route-level middleware and handlers get Context<T>.
323
- get(path, ...args) {
324
- return this._route("GET", path, ...args);
325
- }
326
- post(path, ...args) {
327
- return this._route("POST", path, ...args);
328
- }
329
- put(path, ...args) {
330
- return this._route("PUT", path, ...args);
331
- }
332
- delete(path, ...args) {
333
- return this._route("DELETE", path, ...args);
334
- }
335
- patch(path, ...args) {
336
- return this._route("PATCH", path, ...args);
337
- }
338
- head(path, ...args) {
339
- return this._route("HEAD", path, ...args);
340
- }
341
- options(path, ...args) {
342
- return this._route("OPTIONS", path, ...args);
343
- }
344
- all(path, ...args) {
345
- return this._route("*", path, ...args);
346
- }
347
- onError(handler) {
348
- this.errorHandler = handler;
349
- return this;
350
- }
351
- _route(method, path, ...args) {
352
- return this._routeImpl(method, path, args);
353
- }
354
- /** Internal route registration — no type constraints (used by _mountRouter). */
355
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
- _routeImpl(method, path, args) {
357
- const last = args[args.length - 1];
358
- if (last instanceof _Router) {
359
- this._mountRouter(path, last, args.slice(0, -1));
360
- return this;
361
- }
362
- const handler = args.pop();
363
- const middlewares = args;
364
- const segments = this.splitPath(path);
365
- let node = this.root;
366
- for (const segment of segments) {
367
- if (segment === "*") {
368
- this._hasWildcard = true;
369
- const remaining = segments.indexOf("*") < segments.length - 1;
370
- if (remaining) {
371
- console.warn(`Route "${path}": segments after "*" are ignored`);
372
- }
373
- node.wildcard = true;
374
- node.handlers.set(method, handler);
375
- if (middlewares.length > 0) node.middlewares.set(method, middlewares);
376
- return this;
377
- }
378
- node = getOrCreateChild(node, segment, createTrieNode, false);
379
- }
380
- if (!isProd() && node.handlers.has(method)) {
381
- console.warn(`[router] route conflict: ${method} ${path} overwrites existing handler`);
382
- }
383
- node.handlers.set(method, handler);
384
- if (middlewares.length > 0) node.middlewares.set(method, middlewares);
385
- return this;
386
- }
387
- ws(path, ...args) {
388
- const handler = args.pop();
389
- const middlewares = args;
390
- const segments = this.splitPath(path);
391
- let node = this.wsRoot;
392
- for (const segment of segments) {
393
- node = getOrCreateChild(node, segment, createWsNode, true);
394
- }
395
- node.handler = handler;
396
- node.middlewares = middlewares;
397
- return this;
398
- }
399
- handler() {
400
- return (req, ctx) => {
401
- const url = new URL(req.url);
402
- return this.handle(req, ctx, this.splitPath(url.pathname));
403
- };
404
- }
405
- /** Returns a human-readable list of all registered routes. Useful for debugging. */
406
- routes() {
407
- const result = [];
408
- if (this.globalMws.length > 0) {
409
- result.push(`MIDDLEWARE [${this.globalMws.length} global]`);
410
- }
411
- this._collectRoutes(this.root, "", result);
412
- this._collectWsRoutes(this.wsRoot, "", result);
413
- return result;
414
- }
415
- _collectRoutes(node, prefix, result) {
416
- for (const [method] of node.handlers) {
417
- const m = method === "*" ? "ANY" : method;
418
- const path = (prefix || "/") + (node.wildcard ? "/*" : "");
419
- const middlewares = node.middlewares.get(method);
420
- const mwCount = middlewares ? ` (+${middlewares.length} mw)` : "";
421
- result.push(`${m.padEnd(7)} ${path}${mwCount}`);
422
- }
423
- for (const [seg, child] of node.children) {
424
- const segment = seg === ":" ? `:${child.param}` : seg;
425
- this._collectRoutes(child, prefix + "/" + segment, result);
426
- }
427
- }
428
- _collectWsRoutes(node, prefix, result) {
429
- if (node.handler) {
430
- const path = prefix || "/";
431
- const mwCount = node.middlewares.length ? ` (+${node.middlewares.length} mw)` : "";
432
- result.push(`WS ${path}${mwCount}`);
433
- }
434
- for (const [seg, child] of node.children) {
435
- const segment = seg === ":" ? `:${child.param}` : seg;
436
- this._collectWsRoutes(child, prefix + "/" + segment, result);
437
- }
438
- }
439
- websocketHandler() {
440
- const wsRoot = this.wsRoot;
441
- const router = this;
442
- return (req, socket, head) => {
443
- const url = new URL(req.url ?? "/", "http://localhost");
444
- const segments = url.pathname.split("/").filter(Boolean);
445
- const match = router.matchWsTrie(wsRoot, segments);
446
- if (!match) {
447
- socket.destroy();
448
- return;
449
- }
450
- const query = Object.fromEntries(url.searchParams);
451
- const ctx = { params: match.params, query };
452
- const allMws = router.globalMws.length === 0 && match.middlewares.length === 0 ? [] : [...router.globalMws, ...match.middlewares];
453
- if (allMws.length === 0) {
454
- upgradeSocket(router.wss, req, socket, head, match.handler, ctx, router.hub);
455
- return;
456
- }
457
- const finalHandler = () => {
458
- try {
459
- upgradeSocket(router.wss, req, socket, head, match.handler, ctx, router.hub);
460
- } catch {
461
- socket.destroy();
462
- return new Response("WebSocket upgrade failed", { status: 500 });
463
- }
464
- return new Response(null, { status: 200 });
465
- };
466
- const webReq = new Request(url.href, {
467
- method: req.method ?? "GET",
468
- headers: nodeReqHeadersToRecord(req.headers)
469
- });
470
- void router.runChain(allMws, finalHandler, webReq, ctx).then((result) => {
471
- if (result.status >= 400) {
472
- sendHttpResponseOnSocket(socket, result);
473
- }
474
- }).catch((err) => {
475
- console.error("[router] WS middleware chain error:", err);
476
- socket.destroy();
477
- });
478
- };
479
- }
480
- _mountRouter(prefix, sub, extraMws = []) {
481
- const base = prefix === "/" ? "" : prefix.replace(/\/$/, "");
482
- const mountMw = (req, ctx, next) => {
483
- ctx.mountPath = (ctx.mountPath || "") + base;
484
- return next(req, ctx);
485
- };
486
- const allExtra = extraMws.length === 0 && sub.globalMws.length === 0 ? [mountMw] : [mountMw, ...extraMws, ...sub.globalMws];
487
- const routes = [];
488
- this._collect(sub.root, "", routes);
489
- for (const { method, path, handler, middlewares } of routes) {
490
- this._routeImpl(method, base + path, [...allExtra, ...middlewares, handler]);
491
- }
492
- const wsRoutes = [];
493
- this._collectWs(sub.wsRoot, "", wsRoutes);
494
- for (const { path, handler, middlewares } of wsRoutes) {
495
- this.ws(
496
- base + path,
497
- ...allExtra,
498
- ...middlewares,
499
- handler
500
- );
501
- }
502
- }
503
- _collect(node, prefix, result) {
504
- for (const [method, handler] of node.handlers) {
505
- const rmws = node.middlewares.get(method) || [];
506
- const suffix = node.wildcard ? "/*" : "";
507
- result.push({
508
- method,
509
- path: (prefix || "/") + suffix,
510
- handler,
511
- middlewares: [...rmws]
512
- });
513
- }
514
- for (const [seg, child] of node.children) {
515
- const next = seg === ":" ? `/:${child.param}` : `/${seg}`;
516
- this._collect(child, prefix + next, result);
517
- }
518
- }
519
- _collectWs(node, prefix, result, mwsAcc = []) {
520
- const mws = [...mwsAcc, ...node.middlewares];
521
- if (node.handler) result.push({ path: prefix || "/", handler: node.handler, middlewares: mws });
522
- for (const [seg, child] of node.children) {
523
- const next = seg === ":" ? `/:${child.param}` : `/${seg}`;
524
- this._collectWs(child, prefix + next, result, mws);
525
- }
526
- }
527
- splitPath(path) {
528
- return path.split("/").filter(Boolean);
529
- }
530
- matchTrie(method, segments) {
531
- let node = this.root;
532
- const params = {};
533
- let wildcardHandler = null;
534
- let wildcardMws = [];
535
- let wildcardIdx = -1;
536
- for (let i = 0; i < segments.length; i++) {
537
- if (this._hasWildcard && node.wildcard) {
538
- let h = node.handlers.get("*") || node.handlers.get(method);
539
- if (!h && method === "HEAD") {
540
- h = node.handlers.get("GET");
541
- }
542
- if (h) {
543
- wildcardHandler = h;
544
- wildcardMws = node.middlewares.get(method) || node.middlewares.get("*") || [];
545
- wildcardIdx = i;
546
- }
547
- }
548
- const segment = segments[i];
549
- const next = matchChild(node, segment, params, false);
550
- if (!next) {
551
- if (wildcardHandler) {
552
- params["*"] = segments.slice(wildcardIdx).join("/");
553
- return { kind: "route", handler: wildcardHandler, mws: wildcardMws, params };
554
- }
555
- return null;
556
- }
557
- node = next;
558
- }
559
- let handler = node.handlers.get(method) || node.handlers.get("*");
560
- if (!handler && method === "HEAD") {
561
- handler = node.handlers.get("GET");
562
- }
563
- if (handler) {
564
- if (node.wildcard) params["*"] = segments.slice(segments.length).join("/");
565
- return {
566
- kind: "route",
567
- handler,
568
- mws: node.middlewares.get(method) || node.middlewares.get("*") || [],
569
- params
570
- };
571
- }
572
- if (wildcardHandler) {
573
- params["*"] = segments.slice(wildcardIdx).join("/");
574
- return { kind: "route", handler: wildcardHandler, mws: wildcardMws, params };
575
- }
576
- if (node.handlers.size > 0) {
577
- return {
578
- kind: "not-allowed",
579
- methods: [...node.handlers.keys()].filter((k) => k !== "*"),
580
- params
581
- };
582
- }
583
- return null;
584
- }
585
- matchWsTrie(root, segments) {
586
- let node = root;
587
- const params = {};
588
- for (const segment of segments) {
589
- const next = matchChild(node, segment, params, true);
590
- if (!next) return null;
591
- node = next;
592
- }
593
- return node.handler ? { handler: node.handler, middlewares: node.middlewares, params } : null;
594
- }
595
- async handleError(e, req, ctx) {
596
- const err = e instanceof Error ? e : new Error(String(e));
597
- console.error(err);
598
- return this.errorHandler ? await this.errorHandler(err, req, ctx) : new Response("Internal Server Error", { status: 500 });
599
- }
600
- _notFoundResponse(method, segments) {
601
- if (!isProd()) {
602
- return Response.json(
603
- { error: "Not Found", path: "/" + segments.join("/"), method },
604
- { status: 404 }
605
- );
606
- }
607
- return new Response("Not Found", { status: 404 });
608
- }
609
- async handle(req, ctx, segments) {
610
- const match = this.matchTrie(req.method, segments);
611
- if (match) {
612
- Object.assign(ctx.params, match.params);
613
- switch (match.kind) {
614
- case "route": {
615
- const mws = [...this.globalMws, ...match.mws];
616
- try {
617
- return await this.runChain(mws, match.handler, req, ctx);
618
- } catch (e) {
619
- return this.handleError(e, req, ctx);
620
- }
621
- }
622
- case "not-allowed": {
623
- if (this.globalMws.length > 0) {
624
- try {
625
- return await this.runChain(
626
- this.globalMws,
627
- () => new Response("Method Not Allowed", {
628
- status: 405,
629
- headers: { Allow: match.methods.join(", ") }
630
- }),
631
- req,
632
- ctx
633
- );
634
- } catch (e) {
635
- return this.handleError(e, req, ctx);
636
- }
637
- }
638
- return new Response("Method Not Allowed", {
639
- status: 405,
640
- headers: { Allow: match.methods.join(", ") }
641
- });
642
- }
643
- }
644
- }
645
- if (this.globalMws.length > 0) {
646
- try {
647
- return await this.runChain(
648
- this.globalMws,
649
- () => this._notFoundResponse(req.method, segments),
650
- req,
651
- ctx
652
- );
653
- } catch (e) {
654
- return this.handleError(e, req, ctx);
655
- }
656
- }
657
- return this._notFoundResponse(req.method, segments);
658
- }
659
- async runChain(middlewares, finalHandler, req, ctx) {
660
- if (middlewares.length === 0) return await finalHandler(req, ctx);
661
- return await runChainLoop(middlewares, 0, finalHandler, req, ctx);
662
- }
663
- };
664
- function runChainLoop(middlewares, index, finalHandler, req, ctx) {
665
- if (index < middlewares.length) {
666
- const mw = middlewares[index];
667
- let called = false;
668
- const dispatch = (r, c) => {
669
- if (called) {
670
- console.warn(
671
- "[router] next() called more than once in middleware \u2014 ignoring duplicate call"
672
- );
673
- return Promise.resolve(new Response("", { status: 499 }));
674
- }
675
- called = true;
676
- return runChainLoop(middlewares, index + 1, finalHandler, r, c);
677
- };
678
- return Promise.resolve(mw(req, ctx, dispatch));
679
- }
680
- return Promise.resolve(finalHandler(req, ctx));
681
- }
682
- function upgradeSocket(wss, req, socket, head, handler, ctx, hub) {
683
- wss.handleUpgrade(req, socket, head, (ws) => {
684
- const connCtx = { ...ctx, params: { ...ctx.params }, query: { ...ctx.query } };
685
- const wsState = {};
686
- connCtx.ws = {
687
- get state() {
688
- return wsState;
689
- },
690
- json(data) {
691
- ws.send(JSON.stringify(data));
692
- },
693
- join(room) {
694
- hub.join(room, ws);
695
- },
696
- leave(_room) {
697
- hub.leave(ws);
698
- },
699
- sendRoom(room, data) {
700
- hub.broadcast(room, data);
701
- }
702
- };
703
- if (handler.open) {
704
- handler.open(ws, connCtx);
705
- }
706
- ws.on("message", (data) => {
707
- handler.message?.(ws, connCtx, data);
708
- });
709
- ws.on("close", () => {
710
- hub.leave(ws);
711
- handler.close?.(ws, connCtx);
712
- });
713
- ws.on("error", (err) => {
714
- handler.error?.(ws, connCtx, err);
715
- });
716
- });
717
- }
718
- function nodeReqHeadersToRecord(headers) {
719
- const result = {};
720
- for (const [k, v] of Object.entries(headers)) {
721
- if (v !== void 0) result[k] = Array.isArray(v) ? v.join(", ") : v;
722
- }
723
- return result;
724
- }
725
- function sendHttpResponseOnSocket(socket, response) {
726
- const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}`;
727
- const headerLines = [statusLine];
728
- response.headers.forEach((value, key) => {
729
- headerLines.push(`${key}: ${value}`);
730
- });
731
- headerLines.push("Connection: close");
732
- headerLines.push("");
733
- const headerStr = headerLines.join("\r\n");
734
- response.arrayBuffer().then((buf) => {
735
- socket.write(headerStr + "\r\n");
736
- if (buf.byteLength > 0) socket.write(Buffer.from(buf));
737
- socket.end();
738
- }).catch(() => {
739
- socket.write(headerStr + "\r\n");
740
- socket.end();
741
- });
742
- }
743
-
744
- // src/react/ssr/server-registry.ts
745
- import * as esbuild from "esbuild";
746
- import { existsSync, readFileSync, statSync } from "node:fs";
747
- import { resolve, dirname } from "node:path";
748
- import vm from "node:vm";
749
- import { createRequire } from "node:module";
750
- var _userRequire = null;
751
- function getUserRequire() {
752
- if (!_userRequire) {
753
- try {
754
- _userRequire = createRequire(resolve(process.cwd(), "package.json"));
755
- } catch {
756
- _userRequire = createRequire(import.meta.url);
757
- }
758
- }
759
- return _userRequire;
760
- }
761
- var _alias = null;
762
- function resolveAliases() {
763
- if (_alias) return _alias;
764
- const configFiles = ["tsconfig.json", "jsconfig.json"];
765
- for (const file of configFiles) {
766
- const p = resolve(file);
767
- if (existsSync(p)) {
768
- try {
769
- const config = JSON.parse(readFileSync(p, "utf-8"));
770
- const paths = config.compilerOptions?.paths;
771
- if (paths) {
772
- const alias = {};
773
- for (const [key, values] of Object.entries(paths)) {
774
- const cleanKey = key.replace("/*", "");
775
- const val = values[0]?.replace("/*", "");
776
- if (val) alias[cleanKey] = resolve(dirname(p), val);
777
- }
778
- _alias = alias;
779
- return alias;
780
- }
781
- } catch {
782
- }
783
- }
784
- }
785
- _alias = {};
786
- return {};
787
- }
788
- function applyAlias(id2, _moduleDir) {
789
- const aliases = resolveAliases();
790
- for (const [prefix, target] of Object.entries(aliases)) {
791
- if (id2.startsWith(prefix)) {
792
- const rest = id2.slice(prefix.length);
793
- return target + rest;
794
- }
795
- }
796
- return null;
797
- }
798
- var exts = [".tsx", ".ts", ".jsx", ".js"];
799
- function tryResolve(base) {
800
- if (existsSync(base)) {
801
- const stat2 = statSync(base);
802
- if (stat2.isFile()) return base;
803
- if (stat2.isDirectory()) {
804
- for (const ext of exts) {
805
- const p = resolve(base, `index${ext}`);
806
- if (existsSync(p)) return p;
807
- }
808
- return null;
809
- }
810
- }
811
- for (const ext of exts) {
812
- const p = base + ext;
813
- if (existsSync(p)) return p;
814
- }
815
- return null;
816
- }
817
- var registry = /* @__PURE__ */ new Map();
818
- var _ctx = vm.createContext(Object.create(globalThis));
819
- function transformToCjs(absPath, source) {
820
- const isTsx = absPath.endsWith(".tsx");
821
- const result = esbuild.transformSync(source, {
822
- loader: isTsx ? "tsx" : "ts",
823
- format: "cjs",
824
- jsx: isTsx ? "automatic" : void 0,
825
- jsxImportSource: isTsx ? "react" : void 0,
826
- sourcemap: false
827
- });
828
- return result.code;
829
- }
830
- function makeRequire(modulePath) {
831
- const moduleDir = dirname(modulePath);
832
- return (id2) => {
833
- if (id2.startsWith(".")) {
834
- const base = resolve(moduleDir, id2);
835
- const file = tryResolve(base);
836
- if (!file) {
837
- throw new Error(
838
- `[server-registry] Cannot resolve '${id2}' from '${modulePath}'. Tried: ${[base, ...exts.map((e) => base + e)].filter((p) => !p.endsWith(base)).join(", ")}`
839
- );
840
- }
841
- return getServerModule(file);
842
- }
843
- const aliased = applyAlias(id2, moduleDir);
844
- if (aliased) {
845
- const file = tryResolve(aliased);
846
- if (file) return getServerModule(file);
847
- }
848
- return getUserRequire()(id2);
849
- };
850
- }
851
- function evaluateModule(code, modulePath) {
852
- const mod = { exports: {} };
853
- const require2 = makeRequire(modulePath);
854
- const _dirname = dirname(modulePath);
855
- const _filename = modulePath;
856
- const wrapped = `(function(require,module,exports,__dirname,__filename){
857
- ${code}
858
- })`;
859
- try {
860
- new vm.Script(wrapped).runInContext(_ctx)(require2, mod, mod.exports, _dirname, _filename);
861
- } catch (err) {
862
- const msg = err instanceof Error ? err.message : String(err);
863
- const cause = err instanceof Error ? err : void 0;
864
- throw new Error(
865
- `[server-registry] Error evaluating '${modulePath}': ${msg}`,
866
- cause ? { cause } : void 0
867
- );
868
- }
869
- return mod.exports;
870
- }
871
- function getServerModule(absPath) {
872
- const normalized = resolve(absPath);
873
- if (registry.has(normalized)) return registry.get(normalized).exports;
874
- const source = readFileSync(normalized, "utf-8");
875
- const code = transformToCjs(normalized, source);
876
- const exports = evaluateModule(code, normalized);
877
- registry.set(normalized, { exports });
878
- return exports;
879
- }
880
- function clearServerModule(absPath) {
881
- if (absPath) {
882
- const normalized = resolve(absPath);
883
- registry.delete(normalized);
884
- } else {
885
- registry.clear();
886
- _alias = null;
887
- }
888
- }
889
-
890
- // src/react/ssr/compile.ts
891
- var _userRequire2 = null;
892
- var OUT_DIR = ".weifuwu/ssr";
893
- var cache = /* @__PURE__ */ new Map();
894
- var externals = [
895
- "react",
896
- "react-dom",
897
- "esbuild",
898
- "graphql",
899
- "ws",
900
- "zod",
901
- "@graphql-tools/schema",
902
- "ai"
903
- ];
904
- var _alias2 = null;
905
- function resolveAliases2() {
906
- if (_alias2) return _alias2;
907
- const configFiles = ["tsconfig.json", "jsconfig.json"];
908
- for (const file of configFiles) {
909
- const p = resolve2(file);
910
- if (existsSync2(p)) {
911
- try {
912
- const config = JSON.parse(readFileSync2(p, "utf-8"));
913
- const paths = config.compilerOptions?.paths;
914
- if (paths) {
915
- const alias = {};
916
- for (const [key, values] of Object.entries(paths)) {
917
- const cleanKey = key.replace("/*", "");
918
- const val = values[0]?.replace("/*", "");
919
- if (val) alias[cleanKey] = resolve2(dirname2(p), val);
920
- }
921
- _alias2 = alias;
922
- return alias;
923
- }
924
- } catch {
925
- }
926
- }
927
- }
928
- _alias2 = {};
929
- return {};
930
- }
931
- function id(s) {
932
- return createHash("md5").update(s).digest("hex").slice(0, 8);
933
- }
934
- function clearCompileCache() {
935
- cache.clear();
936
- clearServerModule();
937
- _alias2 = null;
938
- }
939
- async function compileTsx(path) {
940
- const absPath = resolve2(path);
941
- if (cache.has(absPath)) return cache.get(absPath);
942
- const outDir = resolve2(OUT_DIR);
943
- mkdirSync(outDir, { recursive: true });
944
- const hash = id(absPath);
945
- const outPath = join(outDir, hash + ".js");
946
- await esbuild2.build({
947
- entryPoints: { [hash]: absPath },
948
- outdir: outDir,
949
- format: "esm",
950
- platform: "node",
951
- jsx: "automatic",
952
- jsxImportSource: "react",
953
- bundle: true,
954
- external: externals,
955
- alias: resolveAliases2(),
956
- write: true,
957
- allowOverwrite: true
958
- });
959
- const mod = await import(pathToFileURL(outPath).href);
960
- cache.set(absPath, mod);
961
- return mod;
962
- }
963
- function compileTsxDev(path) {
964
- const absPath = resolve2(path);
965
- const mod = getServerModule(absPath);
966
- cache.set(absPath, mod);
967
- return mod;
968
- }
969
- function compile(path) {
970
- return isDev() ? Promise.resolve(compileTsxDev(path)) : compileTsx(path);
971
- }
972
- var vendorBundle = null;
973
- var vendorHash = "";
974
- async function compileVendorBundle() {
975
- if (vendorBundle) return vendorBundle;
976
- if (!_userRequire2) _userRequire2 = createRequire2(join(process.cwd(), "package.json"));
977
- const modules = {
978
- react: [],
979
- "react-dom": ["react"],
980
- "react-dom/client": ["react"],
981
- "react/jsx-runtime": ["react"]
982
- };
983
- for (const request of Object.keys(modules)) {
984
- const mod = _userRequire2(request);
985
- const keys = Object.keys(mod).filter((k) => !k.startsWith("_") && k !== "default");
986
- modules[request] = keys;
987
- }
988
- const baseDir = import.meta.dirname ?? __dirname;
989
- const reactAbsPath = isBundled() ? resolve2(baseDir, "react.js") : resolve2(baseDir, "ssr", "react.ts");
990
- const reactSrc = readFileSync2(reactAbsPath, "utf-8");
991
- const wfwKeys = [];
992
- if (reactAbsPath.endsWith(".ts")) {
993
- for (const line of reactSrc.split("\n")) {
994
- const m = line.match(/^export\s+\{[^}]+\}\s*from/);
995
- if (m) {
996
- const names = line.slice(line.indexOf("{") + 1, line.indexOf("}")).split(",").map((s) => s.trim()).filter(Boolean);
997
- for (const n of names) {
998
- if (!n.startsWith("type ") && !wfwKeys.includes(n)) wfwKeys.push(n);
999
- }
1000
- }
1001
- }
1002
- } else {
1003
- const exportMatch = reactSrc.match(/\bexport\s*\{([^}]+)\}\s*;/);
1004
- if (exportMatch) {
1005
- const names = exportMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
1006
- for (const n of names) {
1007
- if (!n.startsWith("type ") && !wfwKeys.includes(n)) wfwKeys.push(n);
1008
- }
1009
- }
1010
- }
1011
- const used = /* @__PURE__ */ new Set();
1012
- const stmts = [""];
1013
- for (const [request, keys] of Object.entries(modules)) {
1014
- const unique = keys.filter((k) => !used.has(k) && used.add(k));
1015
- if (unique.length > 0)
1016
- stmts.push(`export { ${unique.join(", ")} } from ${JSON.stringify(request)};`);
1017
- }
1018
- const uidWfw = wfwKeys.filter((k) => !used.has(k) && used.add(k));
1019
- if (uidWfw.length > 0)
1020
- stmts.push(`export { ${uidWfw.join(", ")} } from ${JSON.stringify(reactAbsPath)};`);
1021
- const result = await esbuild2.build({
1022
- stdin: { contents: stmts.join("\n"), resolveDir: process.cwd() },
1023
- format: "esm",
1024
- bundle: true,
1025
- write: false
1026
- });
1027
- vendorBundle = new TextDecoder().decode(result.outputFiles[0].contents);
1028
- const hashBytes = new TextEncoder().encode(vendorBundle);
1029
- const hashBuffer = await crypto.subtle.digest("SHA-1", hashBytes);
1030
- vendorHash = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
1031
- return vendorBundle;
1032
- }
1033
-
1034
- // src/react/ssr/stream.ts
1035
- import { TextDecoder as TextDecoder2, TextEncoder as TextEncoder2 } from "node:util";
1036
- function concatUint8(chunks) {
1037
- const len = chunks.reduce((a, c) => a + c.length, 0);
1038
- const out = new Uint8Array(len);
1039
- let off = 0;
1040
- for (const c of chunks) {
1041
- out.set(c, off);
1042
- off += c.length;
1043
- }
1044
- return out;
1045
- }
1046
- async function readStream(stream) {
1047
- const chunks = [];
1048
- const reader = stream.getReader();
1049
- while (true) {
1050
- const { done, value } = await reader.read();
1051
- if (done) break;
1052
- chunks.push(value);
1053
- }
1054
- return new TextDecoder2().decode(concatUint8(chunks));
1055
- }
1056
- var _publicEnv = null;
1057
- function getPublicEnv2() {
1058
- if (_publicEnv) return _publicEnv;
1059
- _publicEnv = {};
1060
- for (const key of Object.keys(process.env)) {
1061
- if (key.startsWith("WEIFUWU_PUBLIC_")) {
1062
- _publicEnv[key] = process.env[key];
1063
- }
1064
- }
1065
- return _publicEnv;
1066
- }
1067
- function buildHeadPayload(opts) {
1068
- const { ctx, base, tailwind } = opts;
1069
- let result = "";
1070
- result += `<script>window.__wfw={_cache:{},_k:function(u){return u.split('?')[0]},h:async function(u){var k=this._k(u);if(this._cache[k])return this._cache[k];var m=await import(u);this._cache[k]=m;return m},_update:function(u,mod){var k=this._k(u);this._cache[k]=mod}}</script>
1071
- `;
1072
- const vUrl = `${base}/__wfw/v/bundle?h=${vendorHash}`;
1073
- result += `<script type="importmap">{
1074
- "imports": {
1075
- "react": "${vUrl}",
1076
- "react-dom": "${vUrl}",
1077
- "react-dom/client": "${vUrl}",
1078
- "react/jsx-runtime": "${vUrl}",
1079
- "weifuwu/react": "${vUrl}"
1080
- }
1081
- }</script>
1082
- `;
1083
- if (ctx.theme?.value) {
1084
- result += `<script>!function(){var t=(document.cookie.match(/(?:^|;\\s*)theme=([^;]+)/)||[])[1]||'system';if(t==='system'){t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'}document.documentElement.setAttribute('data-theme',t)}()</script>
1085
- `;
1086
- }
1087
- if (tailwind?.css) {
1088
- result += `<link rel="stylesheet" href="${tailwind.url}" />
1089
- `;
1090
- }
1091
- const loaderData = opts.loaderData || {};
1092
- const ctxData = {
1093
- params: ctx.params,
1094
- query: ctx.query,
1095
- parsed: ctx.parsed,
1096
- theme: ctx.theme,
1097
- i18n: ctx.i18n,
1098
- flash: ctx.flash,
1099
- loaderData
1100
- };
1101
- const rawUser = ctx.user;
1102
- if (rawUser && typeof rawUser === "object") {
1103
- const safeUser = {};
1104
- for (const k of ["id", "name", "email", "role", "avatar"]) {
1105
- if (k in rawUser) safeUser[k] = rawUser[k];
1106
- }
1107
- ctxData.user = safeUser;
1108
- }
1109
- const publicEnv = getPublicEnv2();
1110
- if (Object.keys(publicEnv).length > 0) {
1111
- ctxData.env = publicEnv;
1112
- }
1113
- result += `<script>window.__WEIFUWU_CTX=${JSON.stringify(ctxData)}</script>
1114
- `;
1115
- return result;
1116
- }
1117
- function buildBodyScripts(opts, hydrationScript) {
1118
- const parts = [];
1119
- if (hydrationScript) parts.push(hydrationScript);
1120
- return parts.join("\n");
1121
- }
1122
- function streamResponse(reactStream, opts, hydrationScript) {
1123
- const decoder = new TextDecoder2();
1124
- const encoder = new TextEncoder2();
1125
- const output = new ReadableStream({
1126
- async start(controller) {
1127
- try {
1128
- const reader = reactStream.getReader();
1129
- let html = "";
1130
- while (true) {
1131
- const { done, value } = await reader.read();
1132
- if (done) break;
1133
- html += decoder.decode(value, { stream: true });
1134
- }
1135
- html += decoder.decode();
1136
- const headTmpl = html.match(/<template id="__wfw_head">([\s\S]*?)<\/template>/);
1137
- if (headTmpl) {
1138
- const extractedHead = headTmpl[1];
1139
- html = html.replace(headTmpl[0], "");
1140
- const headIdx2 = html.indexOf("</head>");
1141
- if (headIdx2 !== -1) {
1142
- html = html.slice(0, headIdx2) + "\n" + extractedHead + html.slice(headIdx2);
1143
- }
1144
- }
1145
- const headPayload = buildHeadPayload(opts);
1146
- const headIdx = html.indexOf("</head>");
1147
- if (headIdx !== -1) {
1148
- html = html.slice(0, headIdx) + headPayload + html.slice(headIdx);
1149
- }
1150
- let bodyScripts = "";
1151
- const built = buildBodyScripts(opts, hydrationScript);
1152
- if (built) bodyScripts += built;
1153
- if (opts.isDev) {
1154
- const wsUrl = `${opts.base}/__weifuwu/livereload`;
1155
- bodyScripts += `
1156
- <script>
1157
- (function(){
1158
- var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'${wsUrl}');
1159
- var t=0;
1160
- var _w=window;
1161
- ws.onmessage=function(e){
1162
- try{
1163
- var m=JSON.parse(e.data);
1164
- if(m.type==='update'&&m.url&&m.code){
1165
- var blob=new Blob([m.code],{type:'application/javascript'});
1166
- var blobUrl=URL.createObjectURL(blob);
1167
- import(blobUrl).then(function(mod){
1168
- if(_w.__wfw) _w.__wfw._update(m.url,mod);
1169
- var pageUrl=_w.__WFW_PAGE_URL;
1170
- if(pageUrl&&_w.__WFW_REFRESH){
1171
- import(pageUrl.split('?')[0]+'?t='+Date.now()).then(function(pageMod){
1172
- if(pageMod.default) _w.__WFW_REFRESH(pageMod.default);
1173
- if(m.css){
1174
- var s=document.querySelector('style[data-lr]')||function(){
1175
- var x=document.createElement('style');
1176
- x.setAttribute('data-lr','');
1177
- document.head.appendChild(x);
1178
- return x
1179
- }();
1180
- s.textContent=m.css
1181
- }
1182
- });
1183
- }else{location.reload()}
1184
- }).catch(function(){location.reload()});
1185
- return
1186
- }
1187
- if(m.type==='css'){
1188
- var s=document.querySelector('style[data-lr]')||function(){
1189
- var x=document.createElement('style');
1190
- x.setAttribute('data-lr','');
1191
- document.head.appendChild(x);
1192
- return x
1193
- }();
1194
- s.textContent=m.css
1195
- return
1196
- }
1197
- }catch(_){}
1198
- if(e.data==='reload'&&Date.now()-t>1e3){t=Date.now();location.reload()}
1199
- };
1200
- ws.onclose=function(){
1201
- if(Date.now()-t>1e3){
1202
- t=Date.now();
1203
- setTimeout(function(){location.reload()},500)
1204
- }
1205
- };
1206
- })();
1207
- </script>`;
1208
- }
1209
- if (bodyScripts) {
1210
- const bodyIdx = html.lastIndexOf("</body>");
1211
- if (bodyIdx !== -1) {
1212
- html = html.slice(0, bodyIdx) + bodyScripts + html.slice(bodyIdx);
1213
- }
1214
- }
1215
- controller.enqueue(encoder.encode(html));
1216
- } catch {
1217
- const fallback = '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>500</title></head><body><h1>500 - Internal Server Error</h1></body></html>';
1218
- controller.enqueue(encoder.encode(fallback));
1219
- } finally {
1220
- controller.close();
1221
- }
1222
- }
1223
- });
1224
- return new Response(output, {
1225
- status: opts.status ?? 200,
1226
- headers: { "content-type": "text/html; charset=utf-8" }
1227
- });
1228
- }
1229
-
1230
- // src/react/ssr/ssr-entries.ts
1231
- var ssrEntries = /* @__PURE__ */ new Map();
1232
-
1233
- // src/react/ssr/tailwind.ts
1234
- import { createHash as createHash2 } from "node:crypto";
1235
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
1236
- import { join as join2, relative, resolve as resolve3 } from "node:path";
1237
- var extraSources = /* @__PURE__ */ new Set();
1238
- var cssCache = /* @__PURE__ */ new Map();
1239
- function addTailwindSource(dir) {
1240
- extraSources.add(resolve3(dir));
1241
- }
1242
- function tailwindContext(dir) {
1243
- const cssDir = resolve3(dir);
1244
- const cssPath = join2(cssDir, "app", "globals.css");
1245
- return async (req, ctx, next) => {
1246
- if (!cssCache.has(cssPath)) {
1247
- await compileTailwindCss(cssPath, cssDir);
1248
- }
1249
- const entry = cssCache.get(cssPath);
1250
- if (entry) {
1251
- const base = (ctx.mountPath || "").replace(/\/$/, "");
1252
- const url = base ? `${base}/__wfw/style/${entry.hash}.css` : `/__wfw/style/${entry.hash}.css`;
1253
- ctx.tailwind = { css: entry.css, url };
1254
- }
1255
- return next(req, ctx);
1256
- };
1257
- }
1258
- function tailwindRouter(dir) {
1259
- const cssDir = resolve3(dir);
1260
- const cssPath = join2(cssDir, "app", "globals.css");
1261
- const r = new Router();
1262
- r.get("/__wfw/style/:hash.css", async (_req, _ctx2) => {
1263
- if (!cssCache.has(cssPath)) {
1264
- await compileTailwindCss(cssPath, cssDir);
1265
- }
1266
- const entry = cssCache.get(cssPath);
1267
- if (!entry) return new Response("", { status: 404 });
1268
- return new Response(entry.css, {
1269
- headers: { "content-type": "text/css; charset=utf-8" }
1270
- });
1271
- });
1272
- return r;
1273
- }
1274
- async function compileTailwindCss(cssPath, cssDir) {
1275
- try {
1276
- if (!existsSync3(cssPath)) {
1277
- mkdirSync2(cssDir, { recursive: true });
1278
- writeFileSync(cssPath, '@import "tailwindcss"\n', "utf-8");
1279
- }
1280
- const { default: tailwindPlugin } = await import("@tailwindcss/postcss");
1281
- const { default: postcss } = await import("postcss");
1282
- let src = readFileSync3(cssPath, "utf-8");
1283
- src = `@source "./";
1284
- ${src}`;
1285
- for (const srcDir of extraSources) {
1286
- const rel = relative(cssDir, srcDir) || ".";
1287
- src = `@source "${rel.startsWith(".") ? rel : "./" + rel}";
1288
- ${src}`;
1289
- }
1290
- const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
1291
- const hash = createHash2("md5").update(result.css).digest("hex").slice(0, 8);
1292
- cssCache.set(cssPath, { css: result.css, hash });
1293
- return result.css;
1294
- } catch (err) {
1295
- console.warn("Tailwind CSS processing failed:", err.message);
1296
- return "";
1297
- }
1298
- }
1299
-
1300
- // src/react/ssr/live.ts
1301
- import chokidar from "chokidar";
1302
- import { existsSync as existsSync5 } from "node:fs";
1303
- import { join as join3, resolve as resolve5 } from "node:path";
1304
-
1305
- // src/react/ssr/module-server.ts
1306
- import * as esbuild3 from "esbuild";
1307
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
1308
- import { resolve as resolve4, dirname as dirname3, relative as relative2 } from "node:path";
1309
- import { createHash as createHash3 } from "node:crypto";
1310
- var moduleCache = /* @__PURE__ */ new Map();
1311
- var hashCache = /* @__PURE__ */ new Map();
1312
- function clearModuleCache(filePath) {
1313
- if (filePath) {
1314
- const abs = resolve4(filePath);
1315
- for (const key of moduleCache.keys()) {
1316
- if (key.endsWith(abs)) moduleCache.delete(key);
1317
- }
1318
- hashCache.delete(abs);
1319
- } else {
1320
- moduleCache.clear();
1321
- hashCache.clear();
1322
- }
1323
- }
1324
- var _importRoots = [];
1325
- function _setImportRoots(roots) {
1326
- _importRoots = roots;
1327
- }
1328
- function fileHash(absPath) {
1329
- const cached = hashCache.get(absPath);
1330
- if (cached) return cached;
1331
- try {
1332
- const content = readFileSync4(absPath);
1333
- const h = createHash3("md5").update(content).digest("hex").slice(0, 8);
1334
- hashCache.set(absPath, h);
1335
- return h;
1336
- } catch {
1337
- return "00000000";
1338
- }
1339
- }
1340
- function rewriteImports(code, absPath, mountPath) {
1341
- const prefix = mountPath ? `${mountPath}/__wfw/m` : "/__wfw/m";
1342
- let varCounter = 0;
1343
- return code.replace(
1344
- /^(import|export)\s+(.+?)\s+from\s+['"]([^'"]+)['"];?\s*$/gm,
1345
- (_match, keyword, clause, modPath) => {
1346
- if (!modPath.startsWith(".")) return _match;
1347
- const isReexport = keyword === "export";
1348
- const imports = clause.replace(/^type\s+/, "");
1349
- const resolved = resolve4(dirname3(absPath), modPath);
1350
- for (const root of _importRoots) {
1351
- const rel = relative2(root, resolved);
1352
- if (!rel.startsWith("..") && !rel.startsWith("/")) {
1353
- const v = fileHash(resolved);
1354
- const url = `${prefix}/${rel}?v=${v}`;
1355
- const defaultMatch = imports.match(/^\s*(\w[\w$]*)\s*$/);
1356
- const namedMatch = imports.match(/^\s*\{\s*([\w$,\s]+)\s*\}\s*$/);
1357
- const mixedMatch = imports.match(/^\s*(\w[\w$]*)\s*,\s*\{\s*([\w$,\s]+)\s*\}\s*$/);
1358
- if (defaultMatch) {
1359
- const name = defaultMatch[1];
1360
- if (isReexport) {
1361
- return `const { default: ${name} } = await __wfw.h("${url}");
1362
- export { ${name} as default }`;
1363
- }
1364
- return `const { default: ${name} } = await __wfw.h("${url}");`;
1365
- }
1366
- if (namedMatch) {
1367
- const names = namedMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
1368
- if (isReexport) {
1369
- const tmp = `__wfw$${varCounter++}`;
1370
- const lines = [`const ${tmp} = await __wfw.h("${url}");`];
1371
- for (const n of names) lines.push(`export const ${n} = ${tmp}.${n};`);
1372
- return lines.join("\n");
1373
- }
1374
- const decl = names.map((n) => `${n}`).join(", ");
1375
- return `const { ${decl} } = await __wfw.h("${url}");`;
1376
- }
1377
- if (mixedMatch) {
1378
- const defaultName = mixedMatch[1];
1379
- const namedNames = mixedMatch[2].split(",").map((s) => s.trim()).filter(Boolean);
1380
- const varName = `__wfw$${varCounter++}`;
1381
- const lines = [
1382
- `const ${varName} = await __wfw.h("${url}");`,
1383
- `const ${defaultName} = ${varName}.default;`
1384
- ];
1385
- for (const n of namedNames) lines.push(`const { ${n} } = ${varName};`);
1386
- return lines.join("\n");
1387
- }
1388
- return _match;
1389
- }
1390
- }
1391
- return _match;
1392
- }
1393
- );
1394
- }
1395
- async function transformModule(absPath, root, mountPath) {
1396
- const mp = mountPath || "";
1397
- const cacheKey = mp + absPath;
1398
- const cached = moduleCache.get(cacheKey);
1399
- if (cached) return { url: `${mp}/__wfw/m/${relative2(root, absPath)}`, code: cached };
1400
- const source = readFileSync4(absPath, "utf-8");
1401
- const isTsx = absPath.endsWith(".tsx");
1402
- const result = await esbuild3.transform(source, {
1403
- loader: isTsx ? "tsx" : "ts",
1404
- jsx: isTsx ? "automatic" : void 0,
1405
- jsxImportSource: isTsx ? "react" : void 0,
1406
- sourcemap: false
1407
- });
1408
- let code = result.code;
1409
- code = rewriteImports(code, absPath, mp);
1410
- moduleCache.set(cacheKey, code);
1411
- const url = `${mp}/__wfw/m/${relative2(root, absPath)}`;
1412
- return { url, code };
1413
- }
1414
- function moduleServer(opts) {
1415
- const roots = Array.isArray(opts.root) ? opts.root : [opts.root];
1416
- _setImportRoots(roots);
1417
- const router = new Router();
1418
- router.get("/__wfw/m/*", (async (req, ctx) => {
1419
- const filePath = (ctx.params["*"] || "").split("?")[0];
1420
- const ext = filePath.split(".").pop();
1421
- if (ext !== "tsx" && ext !== "ts") {
1422
- return new Response("Not Found", { status: 404 });
1423
- }
1424
- const mountPath = ctx.mountPath || "";
1425
- for (const root of roots) {
1426
- const absPath = resolve4(root, filePath);
1427
- if (existsSync4(absPath)) {
1428
- try {
1429
- const { code } = await transformModule(absPath, root, mountPath);
1430
- return new Response(code, {
1431
- headers: { "content-type": "application/javascript; charset=utf-8" }
1432
- });
1433
- } catch (err) {
1434
- const msg = err instanceof Error ? err.message : String(err);
1435
- return new Response(`/* Error: ${msg} */`, { status: 500 });
1436
- }
1437
- }
1438
- }
1439
- return new Response("Not Found", { status: 404 });
1440
- }));
1441
- return router;
1442
- }
1443
-
1444
- // src/react/ssr/live.ts
1445
- var clients = /* @__PURE__ */ new Set();
1446
- function broadcastReload() {
1447
- for (const ws of clients) {
1448
- try {
1449
- ws.send("reload");
1450
- } catch {
1451
- clients.delete(ws);
1452
- }
1453
- }
1454
- }
1455
- function broadcastCss(css) {
1456
- const msg = JSON.stringify({ type: "css", css });
1457
- for (const ws of clients) {
1458
- try {
1459
- ws.send(msg);
1460
- } catch {
1461
- clients.delete(ws);
1462
- }
1463
- }
1464
- }
1465
- function liveWs() {
1466
- return {
1467
- open(ws, _ctx2) {
1468
- clients.add(ws);
1469
- ws.on("close", () => clients.delete(ws));
1470
- ws.on("error", () => clients.delete(ws));
1471
- }
1472
- };
1473
- }
1474
- function liveRouter(_dir) {
1475
- const r = new Router();
1476
- compileVendorBundle().catch(() => {
1477
- });
1478
- return r;
1479
- }
1480
- function liveWatcher(dir) {
1481
- const resolved = resolve5(dir);
1482
- const watcher = chokidar.watch(dir, {
1483
- ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
1484
- ignoreInitial: true
1485
- });
1486
- watcher.on("change", async (filePath) => {
1487
- if (/\.tsx?$/i.test(filePath)) {
1488
- if (filePath.endsWith("layout.tsx")) {
1489
- return broadcastReload();
1490
- }
1491
- clearCompileCache();
1492
- clearModuleCache();
1493
- try {
1494
- await compileTsxDev(filePath);
1495
- } catch (e) {
1496
- console.error("server-side recompile failed:", e);
1497
- return broadcastReload();
1498
- }
1499
- let css;
1500
- const cssPath = join3(resolved, "app", "globals.css");
1501
- if (existsSync5(cssPath)) {
1502
- css = await compileTailwindCss(cssPath, resolved);
1503
- }
1504
- try {
1505
- const absPath = resolve5(filePath);
1506
- const { url, code } = await transformModule(absPath, resolved);
1507
- const msg = { type: "update", url, code };
1508
- if (css) msg.css = css;
1509
- const str = JSON.stringify(msg);
1510
- for (const ws of clients) {
1511
- try {
1512
- ws.send(str);
1513
- } catch {
1514
- clients.delete(ws);
1515
- }
1516
- }
1517
- } catch (e) {
1518
- console.error("module transform failed for HMR:", e);
1519
- broadcastReload();
1520
- }
1521
- } else if (/\.css$/i.test(filePath)) {
1522
- const cssPath = join3(resolved, "app", "globals.css");
1523
- if (existsSync5(cssPath)) {
1524
- const css = await compileTailwindCss(cssPath, resolved);
1525
- if (css) broadcastCss(css);
1526
- }
1527
- }
1528
- });
1529
- return {
1530
- close: () => {
1531
- watcher.close();
1532
- clients.clear();
1533
- }
1534
- };
1535
- }
1536
-
1537
- // src/react/ssr/layout.ts
1538
- function layout(path) {
1539
- return async (req, ctx, next) => {
1540
- const mod = await compile(path);
1541
- const Component = mod.default;
1542
- if (!Component) throw new Error(`Layout ${path} has no default export`);
1543
- ctx.layoutStack = [...ctx.layoutStack || [], { path, component: Component }];
1544
- return next(req, ctx);
1545
- };
1546
- }
1547
-
1548
- // src/react/ssr/error-boundary.ts
1549
- import { createElement as createElement2 } from "react";
1550
-
1551
- // src/react/ssr/html-shell.ts
1552
- import { createElement } from "react";
1553
- function buildHtmlShell(title, bodyElement, layoutComponents) {
1554
- if (layoutComponents.length === 0) {
1555
- return createElement(
1556
- "html",
1557
- { lang: "en" },
1558
- createElement(
1559
- "head",
1560
- null,
1561
- createElement("meta", { charSet: "utf-8" }),
1562
- createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
1563
- createElement("title", null, title)
1564
- ),
1565
- createElement("body", null, bodyElement)
1566
- );
1567
- }
1568
- let element = bodyElement;
1569
- for (const L of layoutComponents.toReversed()) {
1570
- element = createElement(L, { children: element });
1571
- }
1572
- return element;
1573
- }
1574
-
1575
- // src/react/ssr/error-boundary.ts
1576
- function errorBoundary(errorPath) {
1577
- return async (req, ctx, next) => {
1578
- try {
1579
- return await next(req, ctx);
1580
- } catch (err) {
1581
- const mod = await compile(errorPath);
1582
- const ErrorComponent = mod.default;
1583
- if (!ErrorComponent) throw err;
1584
- const ctx2 = ctx;
1585
- const layouts = (ctx2.layoutStack || []).map((l) => l.component);
1586
- const base = (ctx2.mountPath || "").replace(/\/$/, "");
1587
- let element = createElement2(ErrorComponent, {
1588
- error: err instanceof Error ? err : new Error(String(err)),
1589
- reset: () => {
1590
- }
1591
- });
1592
- element = buildHtmlShell("500", element, layouts);
1593
- const { renderToReadableStream } = await import("react-dom/server");
1594
- const stream = await renderToReadableStream(element);
1595
- return streamResponse(stream, {
1596
- ctx: ctx2,
1597
- base,
1598
- isDev: isDev(),
1599
- tailwind: ctx2.tailwind,
1600
- status: 500
1601
- });
1602
- }
1603
- };
1604
- }
1605
-
1606
- // src/react/ssr/ssr.ts
1607
- var isDev2 = isDev();
1608
- var als = new AsyncLocalStorage();
1609
- __registerAls(() => als.getStore());
1610
- function hashId(s) {
1611
- return createHash4("md5").update(s).digest("hex").slice(0, 8);
1612
- }
1613
- function serializeLoaderData(ctx) {
1614
- const ld = ctx.loaderData;
1615
- return ld && typeof ld === "object" ? ld : {};
1616
- }
1617
- function errorPage(title, detail, stack) {
1618
- const html = `<!DOCTYPE html>
1619
- <html><head><meta charset="utf-8"><title>${escapeHtml(title)}</title>
1620
- <style>
1621
- body{font-family:system-ui,-apple-system,sans-serif;max-width:900px;margin:40px auto;padding:0 24px;color:#1a1a2e}
1622
- h1{color:#e53e3e;font-size:24px;margin-bottom:8px}
1623
- .info{color:#718096;font-size:14px;margin-bottom:24px}
1624
- pre{background:#1a1a2e;color:#a0ffa0;padding:16px;border-radius:8px;overflow-x:auto;font-size:13px;line-height:1.5;white-space:pre-wrap;word-break:break-word}
1625
- .trace{color:#e0e0e0}
1626
- </style></head><body>
1627
- <h1>${escapeHtml(title)}</h1>
1628
- <p class="info">${escapeHtml(detail)}</p>
1629
- ${stack ? `<pre><span class="trace">${escapeHtml(stack)}</span></pre>` : ""}
1630
- </body></html>`;
1631
- return new Response(html, {
1632
- status: 500,
1633
- headers: { "Content-Type": "text/html; charset=utf-8" }
1634
- });
1635
- }
1636
- function escapeHtml(s) {
1637
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1638
- }
1639
- async function resolveRoute(ssrDir, segments, routeCache) {
1640
- const cacheKey = segments.join("/") || "/";
1641
- if (!isDev2) {
1642
- const cached = routeCache.get(cacheKey);
1643
- if (cached !== void 0) return cached;
1644
- }
1645
- const appDir = join4(ssrDir, "app");
1646
- let dir = appDir;
1647
- let catchAll = null;
1648
- let segIdx = 0;
1649
- for (; segIdx < segments.length; segIdx++) {
1650
- const seg = segments[segIdx];
1651
- const literal = join4(dir, seg);
1652
- try {
1653
- const s = await stat(literal);
1654
- if (s.isDirectory()) {
1655
- dir = literal;
1656
- continue;
1657
- }
1658
- } catch {
1659
- }
1660
- let entries;
1661
- try {
1662
- entries = await readdir(dir, { withFileTypes: true });
1663
- } catch {
1664
- routeCache.set(cacheKey, null);
1665
- return null;
1666
- }
1667
- const paramDir = entries.find(
1668
- (e) => e.isDirectory() && e.name.startsWith("[") && e.name.endsWith("]") && !e.name.startsWith("[...")
1669
- );
1670
- if (paramDir) {
1671
- dir = join4(dir, paramDir.name);
1672
- continue;
1673
- }
1674
- const catchAllDir = entries.find(
1675
- (e) => e.isDirectory() && e.name.startsWith("[...") && e.name.endsWith("]")
1676
- );
1677
- if (catchAllDir) {
1678
- catchAll = segments.slice(segIdx).join("/");
1679
- dir = join4(dir, catchAllDir.name);
1680
- break;
1681
- }
1682
- routeCache.set(cacheKey, null);
1683
- return null;
1684
- }
1685
- const pageFile = join4(dir, "page.tsx");
1686
- if (!existsSync6(pageFile)) {
1687
- routeCache.set(cacheKey, null);
1688
- return null;
1689
- }
1690
- const consumed = catchAll !== null ? segIdx : segments.length;
1691
- const routeParams = [];
1692
- for (let i = 0; i < consumed; i++) routeParams.push(segments[i]);
1693
- const layoutFiles = [];
1694
- let d = dir;
1695
- while (d.startsWith(appDir)) {
1696
- const lf = join4(d, "layout.tsx");
1697
- if (existsSync6(lf)) layoutFiles.unshift(lf);
1698
- if (d === appDir) break;
1699
- d = dirname4(d);
1700
- }
1701
- const errorFiles = [];
1702
- d = dir;
1703
- while (d.startsWith(appDir)) {
1704
- const ef = join4(d, "error.tsx");
1705
- if (existsSync6(ef)) errorFiles.unshift(ef);
1706
- if (d === appDir) break;
1707
- d = dirname4(d);
1708
- }
1709
- let notFoundFile = null;
1710
- d = dir;
1711
- while (d.startsWith(appDir)) {
1712
- const nf = join4(d, "not-found.tsx");
1713
- if (existsSync6(nf)) {
1714
- notFoundFile = nf;
1715
- break;
1716
- }
1717
- if (d === appDir) break;
1718
- d = dirname4(d);
1719
- }
1720
- const result = {
1721
- routePath: "/" + routeParams.join("/"),
1722
- pageFile,
1723
- layoutFiles,
1724
- errorFiles,
1725
- notFoundFile
1726
- };
1727
- routeCache.set(cacheKey, result);
1728
- return result;
1729
- }
1730
- function buildHydrationScript(pageUrl, ctxJson) {
1731
- return `
1732
- <script type="module">
1733
- import { setCtx, TsxContext } from '@weifuwujs/react';
1734
- import { createElement } from 'react';
1735
- import { hydrateRoot, createRoot } from 'react-dom/client';
1736
-
1737
- const _ctx = ${ctxJson};
1738
- setCtx(_ctx);
1739
-
1740
- const _root = document.getElementById('__weifuwu_root');
1741
-
1742
- async function init() {
1743
- const { default: Page } = await import('${pageUrl}');
1744
- ${isDev2 ? `
1745
- window.__WFW_PAGE_URL = '${pageUrl}';
1746
-
1747
- const _pageImpl = { current: Page };
1748
- const _pageProxy = new Proxy(function __wfw_page(){}, {
1749
- apply(_target, _thisArg, args) {
1750
- return Reflect.apply(_pageImpl.current, _thisArg, args);
1751
- },
1752
- });
1753
-
1754
- const reactRoot = createRoot(_root);
1755
- let _tick = 0;
1756
- function renderPage() {
1757
- reactRoot.render(createElement(TsxContext.Provider, { value: _ctx },
1758
- createElement(_pageProxy, { __t: _tick })));
1759
- }
1760
- renderPage();
1761
-
1762
- window.__WFW_RERENDER = () => {
1763
- _tick++;
1764
- reactRoot.render(createElement(TsxContext.Provider, { value: _ctx },
1765
- createElement(_pageProxy, { __t: _tick })));
1766
- };
1767
-
1768
- window.__WFW_REFRESH = async (NewComponent) => {
1769
- const store = globalThis.__WEIFUWU_CTX_STORE?._ctx || _ctx;
1770
- _pageImpl.current = NewComponent;
1771
- __WFW_RERENDER();
1772
- };
1773
- ` : `
1774
- const app = createElement(TsxContext.Provider, { value: _ctx },
1775
- createElement(Page));
1776
- hydrateRoot(_root, app);
1777
- `}
1778
- }
1779
-
1780
- init();
1781
- </script>`;
1782
- }
1783
- function renderPage(pageFile, projectDir) {
1784
- const absPath = resolve6(pageFile);
1785
- const entryId = hashId(absPath);
1786
- ssrEntries.set(entryId, { path: absPath });
1787
- return async (req, ctx) => {
1788
- let pageMod;
1789
- try {
1790
- pageMod = await compile(absPath);
1791
- } catch (err) {
1792
- const msg = err instanceof Error ? err.message : String(err);
1793
- console.error(`[ssr] compile failed: ${pageFile} \u2014 ${msg}`);
1794
- return errorPage("Compilation failed", `${pageFile}: ${msg}`);
1795
- }
1796
- const Component = pageMod.default;
1797
- if (!Component) return errorPage("Missing default export", pageFile);
1798
- const layouts = ctx.layoutStack || [];
1799
- const layoutComponents = layouts.map((l) => l.component);
1800
- const base = (ctx.mountPath || "").replace(/\/$/, "");
1801
- const loaderData = serializeLoaderData(ctx);
1802
- const ctxValue = {
1803
- params: ctx.params,
1804
- query: ctx.query,
1805
- user: ctx.user ?? {},
1806
- parsed: ctx.parsed ?? {},
1807
- theme: ctx.theme,
1808
- i18n: ctx.i18n,
1809
- flash: ctx.flash,
1810
- loaderData,
1811
- env: ctx.env ?? {}
1812
- };
1813
- const pageRelative = relative3(projectDir, absPath);
1814
- const pageUrl = `${base}/__wfw/m/${pageRelative}`;
1815
- return als.run(ctxValue, async () => {
1816
- setCtx(ctxValue);
1817
- let element = createElement3(
1818
- "div",
1819
- { id: "__weifuwu_root" },
1820
- createElement3(TsxContext.Provider, { value: ctxValue }, createElement3(Component, null))
1821
- );
1822
- element = buildHtmlShell("weifuwu", element, layoutComponents);
1823
- const { renderToReadableStream } = await import("react-dom/server");
1824
- const stream = await renderToReadableStream(element);
1825
- return streamResponse(
1826
- stream,
1827
- {
1828
- ctx,
1829
- base,
1830
- isDev: isDev2,
1831
- loaderData,
1832
- tailwind: ctx.tailwind
1833
- },
1834
- buildHydrationScript(pageUrl, JSON.stringify(ctxValue))
1835
- );
1836
- });
1837
- };
1838
- }
1839
- function runChain(mws, handler, req, ctx) {
1840
- let idx = 0;
1841
- const dispatch = (r, c) => {
1842
- if (idx < mws.length) return mws[idx++](r, c, dispatch);
1843
- return handler(r, c);
1844
- };
1845
- return Promise.resolve(dispatch(req, ctx));
1846
- }
1847
- function discoverRoutes(dir) {
1848
- const appDir = join4(dir, "app");
1849
- if (!existsSync6(appDir)) return [];
1850
- const result = [];
1851
- function walk(currentDir, routePath) {
1852
- let entries;
1853
- try {
1854
- entries = readdirSync(currentDir, { withFileTypes: true });
1855
- } catch {
1856
- return;
1857
- }
1858
- for (const entry of entries) {
1859
- if (entry.isDirectory()) {
1860
- let segment = entry.name;
1861
- if (entry.name.startsWith("[...") && entry.name.endsWith("]")) {
1862
- segment = "*";
1863
- } else if (entry.name.startsWith("[") && entry.name.endsWith("]")) {
1864
- segment = ":" + entry.name.slice(1, -1);
1865
- }
1866
- walk(join4(currentDir, entry.name), routePath + "/" + segment);
1867
- } else if (entry.name === "page.tsx") {
1868
- result.push({
1869
- path: routePath || "/",
1870
- file: relative3(appDir, join4(currentDir, entry.name))
1871
- });
1872
- }
1873
- }
1874
- }
1875
- walk(appDir, "");
1876
- return result;
1877
- }
1878
- function ssr(opts) {
1879
- const r = new Router();
1880
- const dir = resolve6(opts.dir);
1881
- const routeCache = /* @__PURE__ */ new Map();
1882
- const wfwRoot = resolve6(import.meta.dirname ?? __dirname);
1883
- r.mount("/", moduleServer({ root: [dir, wfwRoot] }));
1884
- compileVendorBundle().catch(() => {
1885
- });
1886
- r.get("/__wfw/v/bundle", async () => {
1887
- const code = await compileVendorBundle();
1888
- return new Response(code, {
1889
- headers: { "content-type": "application/javascript; charset=utf-8" }
1890
- });
1891
- });
1892
- if (existsSync6(join4(dir, "app", "globals.css"))) {
1893
- r.mount("/", tailwindRouter(dir));
1894
- }
1895
- let devWatcher;
1896
- if (isDev2) {
1897
- r.mount("/", liveRouter(dir));
1898
- r.ws("/__weifuwu/livereload", liveWs());
1899
- devWatcher = liveWatcher(dir);
1900
- }
1901
- r.all("/*", async (req, ctx) => {
1902
- const prefix = ctx.mountPath || "";
1903
- const pathname = new URL(req.url).pathname;
1904
- const relativePath = pathname.replace(prefix, "") || "/";
1905
- const segments = relativePath.split("/").filter(Boolean);
1906
- const resolved = await resolveRoute(dir, segments, routeCache);
1907
- if (!resolved) {
1908
- if (isDev2) {
1909
- const pages = discoverRoutes(dir).map((p) => p.path).sort();
1910
- return Response.json(
1911
- {
1912
- error: "Not Found",
1913
- path: "/" + segments.join("/"),
1914
- method: req.method,
1915
- hint: "Available SSR pages",
1916
- pages
1917
- },
1918
- { status: 404 }
1919
- );
1920
- }
1921
- return new Response("Not Found", { status: 404 });
1922
- }
1923
- const mws = [
1924
- ...resolved.errorFiles.map((f) => errorBoundary(f)),
1925
- ...resolved.layoutFiles.map((f) => layout(f)),
1926
- tailwindContext(dir)
1927
- ];
1928
- const handler = (req2, ctx2) => renderPage(resolved.pageFile, dir)(req2, ctx2);
1929
- return runChain(mws, handler, req, ctx);
1930
- });
1931
- const mod = r;
1932
- mod.pages = () => discoverRoutes(dir);
1933
- if (devWatcher) mod.close = devWatcher.close.bind(devWatcher);
1934
- return mod;
1935
- }
1936
-
1937
- // src/react/ssr/head.tsx
1938
- import { createElement as createElement4 } from "react";
1939
- function Head({ children }) {
1940
- return createElement4("template", { id: "__wfw_head" }, children);
1941
- }
1942
-
1943
- // src/react/ssr/use-websocket.ts
1944
- import { useEffect, useRef, useCallback, useState } from "react";
1945
- var RECONNECT_DELAY = 3e3;
1946
- var MAX_RETRIES = 10;
1947
- function resolveUrl(url) {
1948
- return typeof url === "function" ? url() : url;
1949
- }
1950
- function useWebsocket(url, options) {
1951
- const { onMessage, reconnect: reconnectOpt = true, protocols, enabled = true } = options ?? {};
1952
- const [lastMessage, setLastMessage] = useState(null);
1953
- const [readyState, setReadyState] = useState(WebSocket.CLOSED);
1954
- const wsRef = useRef(null);
1955
- const retryRef = useRef(0);
1956
- const timerRef = useRef(void 0);
1957
- const mountedRef = useRef(true);
1958
- const shouldReconnectRef = useRef(true);
1959
- const urlRef = useRef(url);
1960
- const optsRef = useRef({ onMessage, reconnectOpt, protocols });
1961
- urlRef.current = url;
1962
- optsRef.current = { onMessage, reconnectOpt, protocols };
1963
- const cleanup = useCallback(() => {
1964
- clearTimeout(timerRef.current);
1965
- wsRef.current?.close();
1966
- wsRef.current = null;
1967
- }, []);
1968
- const connect = useCallback(() => {
1969
- if (!mountedRef.current || !enabled) return;
1970
- const resolved = resolveUrl(urlRef.current);
1971
- if (!resolved) return;
1972
- wsRef.current?.close();
1973
- const ws = new WebSocket(resolved, optsRef.current.protocols);
1974
- wsRef.current = ws;
1975
- setReadyState(WebSocket.CONNECTING);
1976
- ws.addEventListener("open", () => {
1977
- if (!mountedRef.current) return;
1978
- retryRef.current = 0;
1979
- setReadyState(WebSocket.OPEN);
1980
- });
1981
- ws.addEventListener("message", (e) => {
1982
- if (!mountedRef.current) return;
1983
- const data = typeof e.data === "string" ? e.data : String(e.data);
1984
- setLastMessage(data);
1985
- optsRef.current.onMessage?.(data);
1986
- });
1987
- ws.addEventListener("close", () => {
1988
- if (!mountedRef.current) return;
1989
- setReadyState(WebSocket.CLOSED);
1990
- const ro = optsRef.current.reconnectOpt;
1991
- if (ro && shouldReconnectRef.current && mountedRef.current) {
1992
- const maxRetries = typeof ro === "object" ? ro.maxRetries ?? MAX_RETRIES : MAX_RETRIES;
1993
- const delay = typeof ro === "object" ? ro.delay ?? RECONNECT_DELAY : RECONNECT_DELAY;
1994
- if (retryRef.current < maxRetries) {
1995
- retryRef.current++;
1996
- timerRef.current = setTimeout(() => connect(), delay);
1997
- }
1998
- }
1999
- });
2000
- }, [enabled]);
2001
- useEffect(() => {
2002
- mountedRef.current = true;
2003
- shouldReconnectRef.current = true;
2004
- if (enabled) connect();
2005
- return () => {
2006
- mountedRef.current = false;
2007
- cleanup();
2008
- };
2009
- }, [enabled, connect, cleanup]);
2010
- const send = useCallback((data) => {
2011
- wsRef.current?.send(data);
2012
- }, []);
2013
- const close = useCallback(() => {
2014
- shouldReconnectRef.current = false;
2015
- cleanup();
2016
- setReadyState(WebSocket.CLOSED);
2017
- }, [cleanup]);
2018
- const reconnectFn = useCallback(() => {
2019
- retryRef.current = 0;
2020
- shouldReconnectRef.current = true;
2021
- cleanup();
2022
- connect();
2023
- }, [cleanup, connect]);
2024
- return { send, close, readyState, lastMessage, reconnect: reconnectFn };
2025
- }
2026
-
2027
- // src/react/ssr/use-action.ts
2028
- import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
2029
- function getCsrfToken() {
2030
- if (typeof document === "undefined") return void 0;
2031
- const match = document.cookie.match(/(?:^|;\s*)_csrf=([^;]+)/);
2032
- return match ? decodeURIComponent(match[1]) : void 0;
2033
- }
2034
- function useAction(url, options) {
2035
- const { method = "POST", headers, onSuccess, onError } = options ?? {};
2036
- const [data, setData] = useState2(null);
2037
- const [error, setError] = useState2(null);
2038
- const [pending, setPending] = useState2(false);
2039
- const mountedRef = useRef2(true);
2040
- const submit = useCallback2(
2041
- async (body) => {
2042
- setPending(true);
2043
- setError(null);
2044
- try {
2045
- const csrfToken = getCsrfToken();
2046
- const hdrs = { ...headers };
2047
- if (csrfToken) hdrs["x-csrf-token"] = csrfToken;
2048
- if (body && typeof body === "object" && !(body instanceof FormData)) {
2049
- hdrs["content-type"] = "application/json";
2050
- }
2051
- const res = await fetch(url, {
2052
- method,
2053
- headers: hdrs,
2054
- body: body instanceof FormData ? body : body !== void 0 ? JSON.stringify(body) : void 0
2055
- });
2056
- if (!res.ok) {
2057
- const text = await res.text();
2058
- throw new Error(text || `HTTP ${res.status}`);
2059
- }
2060
- const result = res.status === 204 ? void 0 : await res.json();
2061
- if (mountedRef.current) {
2062
- setData(result);
2063
- onSuccess?.(result);
2064
- }
2065
- return result;
2066
- } catch (err) {
2067
- const e = err instanceof Error ? err : new Error(String(err));
2068
- if (mountedRef.current) {
2069
- setError(e);
2070
- onError?.(e);
2071
- }
2072
- return void 0;
2073
- } finally {
2074
- if (mountedRef.current) setPending(false);
2075
- }
2076
- },
2077
- [url, method, headers, onSuccess, onError]
2078
- );
2079
- const reset = useCallback2(() => {
2080
- setData(null);
2081
- setError(null);
2082
- }, []);
2083
- return { submit, data, error, pending, reset };
2084
- }
2085
-
2086
- // src/react/ssr/client-router.ts
2087
- import { createElement as createElement5, useCallback as useCallback3, useState as useState3, useEffect as useEffect2 } from "react";
2088
-
2089
- // src/react/ssr/client-pref.ts
2090
- var interceptors = [];
2091
- function addInterceptor(fn) {
2092
- interceptors.push(fn);
2093
- }
2094
- async function runInterceptors(url) {
2095
- for (const fn of interceptors) {
2096
- if (await fn(url)) return true;
2097
- }
2098
- return false;
2099
- }
2100
-
2101
- // src/react/ssr/client-router.ts
2102
- var _navigating = false;
2103
- var _listeners = [];
2104
- function onNavigate(fn) {
2105
- _listeners.push(fn);
2106
- return () => {
2107
- _listeners = _listeners.filter((l) => l !== fn);
2108
- };
2109
- }
2110
- function setNavigating(v) {
2111
- _navigating = v;
2112
- for (const fn of _listeners) fn(v);
2113
- }
2114
- async function navigate(href) {
2115
- if (typeof document === "undefined") return;
2116
- const url = new URL(href, location.origin);
2117
- if (url.origin !== location.origin) {
2118
- location.href = href;
2119
- return;
2120
- }
2121
- if (await runInterceptors(url)) return;
2122
- const scrollPos = [window.scrollX, window.scrollY];
2123
- setNavigating(true);
2124
- try {
2125
- const html = await fetch(url.pathname + url.search, {
2126
- headers: { accept: "text/html" }
2127
- }).then((r) => r.text());
2128
- const doc = new DOMParser().parseFromString(html, "text/html");
2129
- const rootEl = doc.getElementById("__weifuwu_root");
2130
- if (!rootEl) {
2131
- location.href = href;
2132
- return;
2133
- }
2134
- const newHtml = rootEl.innerHTML;
2135
- const bundleMatch = html.match(/src="(\/__ssr\/[^"]+\.js)"/);
2136
- const bundleUrl = bundleMatch ? bundleMatch[1] : null;
2137
- const ctxMatch = html.match(/window\.__WEIFUWU_CTX=(.+?)<\/script>/);
2138
- if (ctxMatch) {
2139
- try {
2140
- const ctx = JSON.parse(ctxMatch[1]);
2141
- window.__WEIFUWU_CTX = ctx;
2142
- setCtx(ctx);
2143
- } catch {
2144
- }
2145
- }
2146
- const currentRoot = document.getElementById("__weifuwu_root");
2147
- if (!currentRoot) {
2148
- location.href = href;
2149
- return;
2150
- }
2151
- history.pushState(null, "", url.pathname + url.search);
2152
- currentRoot.innerHTML = newHtml;
2153
- if (bundleUrl) {
2154
- try {
2155
- await import(
2156
- /* @vite-ignore */
2157
- `${bundleUrl}`
2158
- );
2159
- } catch (e) {
2160
- console.error("[weifuwu/router] hydration failed:", e);
2161
- location.href = href;
2162
- }
2163
- }
2164
- window.scrollTo(scrollPos[0], scrollPos[1]);
2165
- } finally {
2166
- setNavigating(false);
2167
- }
2168
- }
2169
- function useNavigate() {
2170
- return useCallback3((href) => navigate(href), []);
2171
- }
2172
- function useNavigating() {
2173
- const [v, setV] = useState3(false);
2174
- useEffect2(() => onNavigate(setV), []);
2175
- return v;
2176
- }
2177
- var prefetchCache = /* @__PURE__ */ new Map();
2178
- var PREFETCH_TTL = 6e4;
2179
- function Link({ href, children, onClick, prefetch, ...props }) {
2180
- const doNavigate = useNavigate();
2181
- useEffect2(() => {
2182
- if (!prefetch) return;
2183
- let el = document.querySelector(`a[href="${CSS.escape(href)}"]`);
2184
- if (!el) {
2185
- for (const a of document.querySelectorAll("a")) {
2186
- if (a.getAttribute("href") === href) {
2187
- el = a;
2188
- break;
2189
- }
2190
- }
2191
- }
2192
- if (!el) return;
2193
- const observer = new IntersectionObserver(
2194
- ([entry]) => {
2195
- if (entry.isIntersecting) prefetchPage(href);
2196
- },
2197
- { rootMargin: "200px" }
2198
- );
2199
- observer.observe(el);
2200
- return () => observer.disconnect();
2201
- }, [href, prefetch]);
2202
- const handleMouseEnter = useCallback3(() => {
2203
- if (prefetch) prefetchPage(href);
2204
- }, [href, prefetch]);
2205
- const handleClick = useCallback3(
2206
- (e) => {
2207
- if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
2208
- e.preventDefault();
2209
- doNavigate(href);
2210
- onClick?.(e);
2211
- },
2212
- [href, onClick, doNavigate]
2213
- );
2214
- return createElement5(
2215
- "a",
2216
- {
2217
- href,
2218
- onClick: handleClick,
2219
- onMouseEnter: handleMouseEnter,
2220
- ...props
2221
- },
2222
- children
2223
- );
2224
- }
2225
- async function prefetchPage(href) {
2226
- const cached = prefetchCache.get(href);
2227
- if (cached && Date.now() - cached.fetched < PREFETCH_TTL) return;
2228
- try {
2229
- const html = await fetch(href, { headers: { accept: "text/html" } }).then((r) => r.text());
2230
- prefetchCache.set(href, { html, fetched: Date.now() });
2231
- } catch {
2232
- }
2233
- }
2234
-
2235
- // src/react/ssr/client-state.ts
2236
- import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
2237
- function createStore(initial) {
2238
- let state = { ...initial };
2239
- const listeners = /* @__PURE__ */ new Set();
2240
- const getState = () => state;
2241
- const setState = (partial) => {
2242
- const next = typeof partial === "function" ? partial(state) : partial;
2243
- state = { ...state, ...next };
2244
- listeners.forEach((fn) => fn());
2245
- };
2246
- const subscribe2 = (listener) => {
2247
- listeners.add(listener);
2248
- return () => {
2249
- listeners.delete(listener);
2250
- };
2251
- };
2252
- const useStore = ((selector) => useSyncExternalStore2(subscribe2, () => selector ? selector(state) : state));
2253
- useStore.getState = getState;
2254
- useStore.setState = setState;
2255
- useStore.subscribe = subscribe2;
2256
- return useStore;
2257
- }
2258
- var dataCache = /* @__PURE__ */ new Map();
2259
- var inflight = /* @__PURE__ */ new Map();
2260
- var CACHE_TTL = 6e4;
2261
- function useFetch(url, options) {
2262
- const ttl = options?.ttl ?? CACHE_TTL;
2263
- const [state, setState] = useState4({
2264
- data: options?.fallback,
2265
- loading: !options?.fallback && !!url
2266
- });
2267
- const urlRef = useRef3(url);
2268
- urlRef.current = url;
2269
- useEffect3(() => {
2270
- if (!url) {
2271
- setState({ data: void 0, loading: false });
2272
- return;
2273
- }
2274
- if (typeof window === "undefined") return;
2275
- const u = url;
2276
- let cancelled = false;
2277
- const cached = dataCache.get(u);
2278
- if (cached && Date.now() - cached.timestamp < ttl) {
2279
- if (!cancelled)
2280
- setState({
2281
- data: cached.data,
2282
- error: cached.error,
2283
- loading: false
2284
- });
2285
- return;
2286
- }
2287
- async function doFetch() {
2288
- if (!inflight.has(u)) {
2289
- inflight.set(
2290
- u,
2291
- fetch(u).then((r) => {
2292
- if (!r.ok) throw new Error(r.statusText || `HTTP ${r.status}`);
2293
- return r.json();
2294
- })
2295
- );
2296
- }
2297
- const promise = inflight.get(u);
2298
- try {
2299
- const data = await promise;
2300
- dataCache.set(u, { data, error: null, timestamp: Date.now() });
2301
- if (!cancelled) setState({ data, loading: false });
2302
- } catch (err) {
2303
- dataCache.set(u, { data: null, error: err, timestamp: Date.now() });
2304
- if (!cancelled) setState({ error: err, loading: false });
2305
- }
2306
- }
2307
- doFetch();
2308
- return () => {
2309
- cancelled = true;
2310
- };
2311
- }, [url, ttl]);
2312
- const mutate = useCallback4(async (data) => {
2313
- const u = urlRef.current;
2314
- if (!u) return;
2315
- const uStr = u;
2316
- if (data !== void 0) {
2317
- dataCache.set(uStr, { data, error: null, timestamp: Date.now() });
2318
- setState({ data, loading: false, error: void 0 });
2319
- return;
2320
- }
2321
- inflight.delete(uStr);
2322
- try {
2323
- const res = await fetch(uStr);
2324
- if (!res.ok) throw new Error(res.statusText || `HTTP ${res.status}`);
2325
- const newData = await res.json();
2326
- dataCache.set(uStr, { data: newData, error: null, timestamp: Date.now() });
2327
- setState({ data: newData, loading: false, error: void 0 });
2328
- } catch (err) {
2329
- setState({ error: err, loading: false });
2330
- }
2331
- }, []);
2332
- return { data: state.data, error: state.error, loading: state.loading, mutate };
2333
- }
2334
- function useQueryState(key, defaultValue = "") {
2335
- function getSnapshot2() {
2336
- if (typeof window === "undefined") return defaultValue;
2337
- const params = new URLSearchParams(window.location.search);
2338
- return params.get(key) ?? defaultValue;
2339
- }
2340
- const value = useSyncExternalStore2(
2341
- (cb) => {
2342
- if (typeof window === "undefined") return () => {
2343
- };
2344
- window.addEventListener("popstate", cb);
2345
- return () => window.removeEventListener("popstate", cb);
2346
- },
2347
- getSnapshot2,
2348
- () => defaultValue
2349
- );
2350
- const setValue = useCallback4(
2351
- (val) => {
2352
- if (typeof window === "undefined") return;
2353
- const resolved = typeof val === "function" ? val(getSnapshot2()) : val;
2354
- const url = new URL(window.location.href);
2355
- if (resolved === defaultValue || resolved === "") {
2356
- url.searchParams.delete(key);
2357
- } else {
2358
- url.searchParams.set(key, resolved);
2359
- }
2360
- window.history.replaceState(null, "", url.toString());
2361
- window.dispatchEvent(new PopStateEvent("popstate"));
2362
- },
2363
- [key, defaultValue]
2364
- );
2365
- return [value, setValue];
2366
- }
2367
-
2368
- // src/react/ssr/client-locale.ts
2369
- function buildT(messages) {
2370
- if (!messages || Object.keys(messages).length === 0) {
2371
- return (key, _p, fb) => fb ?? key;
2372
- }
2373
- return (key, params, fallback) => {
2374
- const msg = key.split(".").reduce((o, k) => o?.[k], messages);
2375
- if (msg === void 0 || msg === null) return fallback ?? key;
2376
- if (!params) return String(msg);
2377
- let result = String(msg);
2378
- for (const [k, v] of Object.entries(params)) result = result.replace(`{${k}}`, v);
2379
- return result;
2380
- };
2381
- }
2382
- addCtxRebuilder((value) => {
2383
- if (value.i18n?.messages) {
2384
- return { i18n: { ...value.i18n, t: buildT(value.i18n.messages) } };
2385
- }
2386
- return null;
2387
- });
2388
- addInterceptor(async (url) => {
2389
- const m = url.pathname.match(/^\/__lang\/([\w-]+)$/);
2390
- if (!m) return false;
2391
- try {
2392
- const res = await fetch(url.pathname, {
2393
- headers: { accept: "application/json" }
2394
- });
2395
- const data = await res.json();
2396
- setCtx({ i18n: { locale: data.locale, messages: data.messages || {} } });
2397
- } catch {
2398
- location.href = url.href;
2399
- }
2400
- return true;
2401
- });
2402
- function useLocale() {
2403
- const ctx = useCtx();
2404
- return {
2405
- locale: ctx.i18n?.locale,
2406
- setLocale: (locale) => navigate("/__lang/" + locale),
2407
- t: ctx.i18n?.t ?? ((key, _p, fb) => fb ?? key)
2408
- };
2409
- }
2410
-
2411
- // src/react/ssr/client-theme.ts
2412
- import { useEffect as useEffect4 } from "react";
2413
- function resolveTheme(theme) {
2414
- if (theme === "system") {
2415
- if (typeof window === "undefined") return "light";
2416
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
2417
- }
2418
- return theme;
2419
- }
2420
- var _mqListener = null;
2421
- function applyTheme(theme) {
2422
- if (typeof document === "undefined") return;
2423
- const resolved = resolveTheme(theme);
2424
- document.documentElement.dataset.theme = resolved;
2425
- if (theme === "system") {
2426
- if (!_mqListener) {
2427
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
2428
- mq.addEventListener("change", (e) => {
2429
- if (window.__WEIFUWU_CTX?.theme?.value === "system") {
2430
- document.documentElement.dataset.theme = e.matches ? "dark" : "light";
2431
- }
2432
- });
2433
- _mqListener = mq;
2434
- }
2435
- }
2436
- }
2437
- addInterceptor(async (url) => {
2438
- const m = url.pathname.match(/^\/__theme\/([\w-]+)$/);
2439
- if (!m) return false;
2440
- try {
2441
- const res = await fetch(url.pathname, {
2442
- headers: { accept: "application/json" }
2443
- });
2444
- const data = await res.json();
2445
- window.__WEIFUWU_CTX = {
2446
- ...window.__WEIFUWU_CTX,
2447
- theme: { value: data.theme }
2448
- };
2449
- setCtx({ theme: { value: data.theme } });
2450
- applyTheme(data.theme);
2451
- } catch {
2452
- location.href = url.href;
2453
- }
2454
- return true;
2455
- });
2456
- function useTheme() {
2457
- const ctx = useCtx();
2458
- const theme = ctx.theme?.value ?? "system";
2459
- useEffect4(() => {
2460
- applyTheme(theme);
2461
- }, [theme]);
2462
- return {
2463
- theme,
2464
- resolvedTheme: resolveTheme(theme),
2465
- setTheme: (t) => navigate("/__theme/" + t)
2466
- };
2467
- }
2468
-
2469
- // src/react/ssr/use-flash-message.ts
2470
- import { useState as useState5 } from "react";
2471
- function useFlashMessage() {
2472
- const [flash] = useState5(() => {
2473
- if (typeof window === "undefined") return null;
2474
- const raw = window.__WEIFUWU_CTX?.flash?.value;
2475
- if (raw === void 0 || raw === null) return null;
2476
- return raw;
2477
- });
2478
- return flash;
2479
- }
2480
-
2481
- // src/react/ssr/use-agent-stream.ts
2482
- import { useState as useState6, useCallback as useCallback5, useRef as useRef4 } from "react";
2483
- function useAgentStream(opts) {
2484
- const { wsPath, onStreamEnd, onError } = opts;
2485
- const [streams, setStreams] = useState6({});
2486
- const activeRef = useRef4(/* @__PURE__ */ new Set());
2487
- const streamsRef = useRef4({});
2488
- const getAgentText = useCallback5((agentId) => streams[agentId] || "", [streams]);
2489
- const isAgentStreaming = useCallback5((agentId) => activeRef.current.has(agentId), []);
2490
- const streaming = activeRef.current.size > 0;
2491
- useWebsocket(wsPath, {
2492
- onMessage: (raw) => {
2493
- try {
2494
- const msg = JSON.parse(raw);
2495
- if (msg.type !== "agent_stream" && msg.type !== "agent_stream_end" && msg.type !== "agent_error")
2496
- return;
2497
- const agentId = msg.data?.agent_id;
2498
- if (agentId === void 0 || agentId === null) return;
2499
- switch (msg.type) {
2500
- case "agent_stream": {
2501
- activeRef.current.add(agentId);
2502
- const token = msg.data?.token || "";
2503
- streamsRef.current[agentId] = (streamsRef.current[agentId] || "") + token;
2504
- setStreams({ ...streamsRef.current });
2505
- break;
2506
- }
2507
- case "agent_stream_end": {
2508
- activeRef.current.delete(agentId);
2509
- const fullText = streamsRef.current[agentId] || "";
2510
- onStreamEnd?.(agentId, fullText);
2511
- break;
2512
- }
2513
- case "agent_error": {
2514
- activeRef.current.delete(agentId);
2515
- delete streamsRef.current[agentId];
2516
- onError?.(agentId, msg.data?.error || "Unknown error");
2517
- break;
2518
- }
2519
- }
2520
- } catch {
2521
- }
2522
- },
2523
- reconnect: { maxRetries: 10, delay: 3e3 }
2524
- });
2525
- return {
2526
- stream: { streams, streaming, activeAgents: activeRef.current },
2527
- getAgentText,
2528
- isAgentStreaming
2529
- };
2530
- }
2531
- export {
2532
- Head,
2533
- Link,
2534
- TsxContext,
2535
- addCtxRebuilder,
2536
- addInterceptor,
2537
- addTailwindSource,
2538
- applyTheme,
2539
- clearCompileCache,
2540
- clearServerModule,
2541
- compile,
2542
- compileTsx,
2543
- compileTsxDev,
2544
- compileVendorBundle,
2545
- createStore,
2546
- errorBoundary,
2547
- getServerModule,
2548
- layout,
2549
- liveRouter,
2550
- liveWatcher,
2551
- liveWs,
2552
- moduleServer,
2553
- navigate,
2554
- readStream,
2555
- setCtx,
2556
- ssr,
2557
- streamResponse,
2558
- tailwindContext,
2559
- tailwindRouter,
2560
- transformModule,
2561
- useAction,
2562
- useAgentStream,
2563
- useCtx,
2564
- useFetch,
2565
- useFlashMessage,
2566
- useLoaderData,
2567
- useLocale,
2568
- useNavigate,
2569
- useNavigating,
2570
- useQueryState,
2571
- useTheme,
2572
- useWebsocket
2573
- };