weifuwu 0.18.2 → 0.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -306,6 +306,11 @@ var Router = class _Router {
306
306
  return this;
307
307
  }
308
308
  route(method, path2, ...args) {
309
+ const last = args[args.length - 1];
310
+ if (last instanceof _Router) {
311
+ this._mountRouter(path2, last);
312
+ return this;
313
+ }
309
314
  const handler = args.pop();
310
315
  const middlewares = args;
311
316
  const segments = this.splitPath(path2);
@@ -5330,9 +5335,19 @@ function streamResponse(reactStream, opts) {
5330
5335
  var als = new AsyncLocalStorage();
5331
5336
  __registerAls(() => als.getStore());
5332
5337
  var isDev = process.env.NODE_ENV !== "production";
5338
+ var bundleCache = /* @__PURE__ */ new Map();
5333
5339
  function id2(s) {
5334
5340
  return createHash3("md5").update(s).digest("hex").slice(0, 8);
5335
5341
  }
5342
+ function serializeLoaderData(ctx) {
5343
+ const data = {};
5344
+ for (const key of Object.keys(ctx)) {
5345
+ if (!["params", "query", "mountPath", "layoutStack"].includes(key)) {
5346
+ data[key] = ctx[key];
5347
+ }
5348
+ }
5349
+ return data;
5350
+ }
5336
5351
  async function buildClientBundle(entryPath, layoutPaths) {
5337
5352
  try {
5338
5353
  const absEntry = resolve4(entryPath);
@@ -5379,21 +5394,17 @@ async function buildClientBundle(entryPath, layoutPaths) {
5379
5394
  return null;
5380
5395
  }
5381
5396
  }
5382
- var bundleRegistry = /* @__PURE__ */ new Map();
5383
- function serializeLoaderData(ctx) {
5384
- const data = {};
5385
- for (const key of Object.keys(ctx)) {
5386
- if (!["params", "query", "mountPath", "layoutStack"].includes(key)) {
5387
- data[key] = ctx[key];
5388
- }
5389
- }
5390
- return data;
5391
- }
5392
5397
  function ssr(path2) {
5393
5398
  const entryId = id2(resolve4(path2));
5394
5399
  const bundleKey = `/__ssr/${entryId}.js`;
5395
- let bundleBuilt = false;
5396
- return async (req, ctx) => {
5400
+ const r = new Router();
5401
+ r.get("/__ssr/:path", (req, ctx) => {
5402
+ const buf = bundleCache.get("/__ssr/" + ctx.params.path);
5403
+ return buf ? new Response(buf, {
5404
+ headers: { "content-type": "application/javascript; charset=utf-8" }
5405
+ }) : new Response("", { status: 404 });
5406
+ });
5407
+ r.get("/", async (req, ctx) => {
5397
5408
  const pageMod = await compileTsx(path2);
5398
5409
  const Component = pageMod.default;
5399
5410
  if (!Component) return new Response("", { status: 500 });
@@ -5441,14 +5452,11 @@ function ssr(path2) {
5441
5452
  }
5442
5453
  }
5443
5454
  let bundle = null;
5444
- if (!bundleBuilt) {
5455
+ if (!bundleCache.has(bundleKey)) {
5445
5456
  const buf = await buildClientBundle(path2, layoutPaths);
5446
- if (buf) {
5447
- bundleRegistry.set(bundleKey, buf);
5448
- bundleBuilt = true;
5449
- }
5457
+ if (buf) bundleCache.set(bundleKey, buf);
5450
5458
  }
5451
- if (bundleRegistry.has(bundleKey)) {
5459
+ if (bundleCache.has(bundleKey)) {
5452
5460
  bundle = { url: bundleKey };
5453
5461
  }
5454
5462
  const { renderToReadableStream } = await import("react-dom/server");
@@ -5461,7 +5469,8 @@ function ssr(path2) {
5461
5469
  loaderData
5462
5470
  });
5463
5471
  });
5464
- };
5472
+ });
5473
+ return r;
5465
5474
  }
5466
5475
 
5467
5476
  // ssr/layout.ts
package/dist/router.d.ts CHANGED
@@ -19,16 +19,16 @@ export declare class Router {
19
19
  use(router: Router): this;
20
20
  use(path: string, router: Router): this;
21
21
  use(path: string, mw: Middleware): this;
22
- get(path: string, ...args: [...Middleware[], Handler]): this;
23
- post(path: string, ...args: [...Middleware[], Handler]): this;
24
- put(path: string, ...args: [...Middleware[], Handler]): this;
25
- delete(path: string, ...args: [...Middleware[], Handler]): this;
26
- patch(path: string, ...args: [...Middleware[], Handler]): this;
27
- head(path: string, ...args: [...Middleware[], Handler]): this;
28
- options(path: string, ...args: [...Middleware[], Handler]): this;
29
- all(path: string, ...args: [...Middleware[], Handler]): this;
22
+ get(path: string, ...args: [...Middleware[], Handler | Router]): this;
23
+ post(path: string, ...args: [...Middleware[], Handler | Router]): this;
24
+ put(path: string, ...args: [...Middleware[], Handler | Router]): this;
25
+ delete(path: string, ...args: [...Middleware[], Handler | Router]): this;
26
+ patch(path: string, ...args: [...Middleware[], Handler | Router]): this;
27
+ head(path: string, ...args: [...Middleware[], Handler | Router]): this;
28
+ options(path: string, ...args: [...Middleware[], Handler | Router]): this;
29
+ all(path: string, ...args: [...Middleware[], Handler | Router]): this;
30
30
  onError(handler: ErrorHandler): this;
31
- route(method: string, path: string, ...args: [...Middleware[], Handler]): this;
31
+ route(method: string, path: string, ...args: [...Middleware[], Handler | Router]): this;
32
32
  ws(path: string, ...args: [...Middleware[], WebSocketHandler]): this;
33
33
  handler(): Handler;
34
34
  websocketHandler(): WsUpgradeHandler;
@@ -1,4 +1,4 @@
1
- export { ssr, ssrBundleHandler } from './ssr.ts';
1
+ export { ssr } from './ssr.ts';
2
2
  export { layout } from './layout.ts';
3
3
  export { tailwind } from './tailwind.ts';
4
4
  export { notFound } from './not-found.ts';
package/dist/ssr/index.js CHANGED
@@ -231,279 +231,6 @@ function setCtx(value) {
231
231
  }
232
232
  var TsxContext = createContext(DEFAULT_CTX);
233
233
 
234
- // ssr/ssr.ts
235
- var als = new AsyncLocalStorage();
236
- __registerAls(() => als.getStore());
237
- var isDev = process.env.NODE_ENV !== "production";
238
- function id2(s) {
239
- return createHash2("md5").update(s).digest("hex").slice(0, 8);
240
- }
241
- async function buildClientBundle(entryPath, layoutPaths) {
242
- try {
243
- const absEntry = resolve2(entryPath);
244
- const absLayouts = layoutPaths.map((p) => resolve2(p));
245
- const layoutImports = absLayouts.map((p) => `import${JSON.stringify(p)};`).join("");
246
- const _sc = `(function(){var k='__WEIFUWU_CTX_STORE';var s=typeof globalThis!='undefined'&&globalThis[k];if(!s)return function(){};return function(v){s._ctx={...s._ctx,...v};s._snapshot={params:s._ctx.params,query:s._ctx.query,user:s._ctx.user,parsed:s._ctx.parsed,prefs:s._ctx.prefs,env:s._ctx.env};s._listeners.forEach(function(fn){fn()})}})()`;
247
- const code = [
248
- layoutImports,
249
- `import{hydrateRoot}from'react-dom/client';`,
250
- `import{createElement,useState,useEffect}from'react';`,
251
- `import{TsxContext}from'weifuwu/react';`,
252
- `import P from${JSON.stringify(absEntry)};`,
253
- `var setCtx=${_sc};`,
254
- `const c=document.getElementById('__weifuwu_root');`,
255
- `if(window.__WEIFUWU_PROPS)setCtx({loaderData:window.__WEIFUWU_PROPS});`,
256
- `if(!window.__WFW_ROOT){`,
257
- `function App(){`,
258
- `const[p,setP]=useState({C:P});`,
259
- `useEffect(()=>{window.__WFW_SET_PAGE=(C)=>{setCtx({loaderData:window.__WEIFUWU_PROPS});setP({C})}},[]);`,
260
- `const ctx=window.__WEIFUWU_CTX||{};`,
261
- `return createElement(TsxContext.Provider,{value:ctx},`,
262
- `createElement(p.C,null))`,
263
- `}`,
264
- `window.__WFW_ROOT=hydrateRoot(c,createElement(App));`,
265
- `}else{`,
266
- `window.__WFW_SET_PAGE?.(P);`,
267
- `}`
268
- ].join("");
269
- const { default: esbuild2 } = await import("esbuild");
270
- const result = await esbuild2.build({
271
- stdin: { contents: code, loader: "tsx", resolveDir: dirname2(absEntry) },
272
- bundle: true,
273
- format: "esm",
274
- jsx: "automatic",
275
- jsxImportSource: "react",
276
- banner: { js: "self.process={env:{}};" },
277
- loader: { ".node": "empty" },
278
- write: false,
279
- minify: true
280
- });
281
- return result.outputFiles[0].contents;
282
- } catch (err) {
283
- console.error("hydration bundle failed:", err);
284
- return null;
285
- }
286
- }
287
- var bundleRegistry = /* @__PURE__ */ new Map();
288
- function serializeLoaderData(ctx) {
289
- const data = {};
290
- for (const key of Object.keys(ctx)) {
291
- if (!["params", "query", "mountPath", "layoutStack"].includes(key)) {
292
- data[key] = ctx[key];
293
- }
294
- }
295
- return data;
296
- }
297
- function ssr(path) {
298
- const entryId = id2(resolve2(path));
299
- const bundleKey = `/__ssr/${entryId}.js`;
300
- let bundleBuilt = false;
301
- return async (req, ctx) => {
302
- const pageMod = await compileTsx(path);
303
- const Component = pageMod.default;
304
- if (!Component) return new Response("", { status: 500 });
305
- const layouts = ctx.layoutStack || [];
306
- const layoutComponents = layouts.map((l) => l.component);
307
- const layoutPaths = layouts.map((l) => l.path);
308
- const base = (ctx.mountPath || "").replace(/\/$/, "");
309
- const loaderData = serializeLoaderData(ctx);
310
- const ctxValue = {
311
- params: ctx.params,
312
- query: ctx.query,
313
- user: ctx.user ?? {},
314
- parsed: ctx.parsed ?? {},
315
- prefs: ctx.prefs ?? {},
316
- loaderData,
317
- env: ctx.env ?? {}
318
- };
319
- return als.run(ctxValue, async () => {
320
- setCtx(ctxValue);
321
- let element = createElement(
322
- TsxContext.Provider,
323
- { value: ctxValue },
324
- createElement(
325
- "div",
326
- { id: "__weifuwu_root" },
327
- createElement(Component, null)
328
- )
329
- );
330
- if (layoutComponents.length === 0) {
331
- element = createElement(
332
- "html",
333
- { lang: "en" },
334
- createElement(
335
- "head",
336
- null,
337
- createElement("meta", { charSet: "utf-8" }),
338
- createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
339
- createElement("title", null, "weifuwu")
340
- ),
341
- createElement("body", null, element)
342
- );
343
- } else {
344
- for (const L of layoutComponents.toReversed()) {
345
- element = createElement(L, { children: element });
346
- }
347
- }
348
- let bundle = null;
349
- if (!bundleBuilt) {
350
- const buf = await buildClientBundle(path, layoutPaths);
351
- if (buf) {
352
- bundleRegistry.set(bundleKey, buf);
353
- bundleBuilt = true;
354
- }
355
- }
356
- if (bundleRegistry.has(bundleKey)) {
357
- bundle = { url: bundleKey };
358
- }
359
- const { renderToReadableStream } = await import("react-dom/server");
360
- const stream = await renderToReadableStream(element);
361
- return streamResponse(stream, {
362
- ctx,
363
- base,
364
- isDev,
365
- bundle,
366
- loaderData
367
- });
368
- });
369
- };
370
- }
371
- function ssrBundleHandler() {
372
- return (req, ctx) => {
373
- const url = new URL(req.url);
374
- const buf = bundleRegistry.get(url.pathname);
375
- return buf ? new Response(buf, {
376
- headers: { "content-type": "application/javascript; charset=utf-8" }
377
- }) : new Response("", { status: 404 });
378
- };
379
- }
380
-
381
- // ssr/layout.ts
382
- function layout(path) {
383
- return async (req, ctx, next) => {
384
- const mod = await compileTsx(path);
385
- const Component = mod.default;
386
- if (!Component) throw new Error(`Layout ${path} has no default export`);
387
- ctx.layoutStack = [...ctx.layoutStack || [], { path, component: Component }];
388
- return next(req, ctx);
389
- };
390
- }
391
-
392
- // ssr/tailwind.ts
393
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
394
- import { relative, resolve as resolve3 } from "node:path";
395
- var isDev2 = process.env.NODE_ENV !== "production";
396
- function tailwind(cssPath, scanDir) {
397
- let compiledCss = "";
398
- let twWatcher = null;
399
- return async (req, ctx, next) => {
400
- const url = new URL(req.url);
401
- if (url.pathname === "/__wfw/style.css") {
402
- if (!compiledCss) compiledCss = await compile(cssPath, scanDir);
403
- return new Response(compiledCss || "", {
404
- headers: { "content-type": "text/css; charset=utf-8" }
405
- });
406
- }
407
- ctx.compiledTailwindCss = compiledCss;
408
- if (isDev2 && !twWatcher) {
409
- twWatcher = watchFile(cssPath, () => {
410
- compiledCss = "";
411
- });
412
- }
413
- return next(req, ctx);
414
- };
415
- }
416
- async function compile(cssPath, scanDir) {
417
- try {
418
- const inputFile = resolve3(cssPath);
419
- if (!existsSync2(inputFile)) {
420
- mkdirSync2(dirname3(inputFile), { recursive: true });
421
- writeFileSync(inputFile, '@import "tailwindcss"\n', "utf-8");
422
- }
423
- const { default: tailwindPlugin } = await import("@tailwindcss/postcss");
424
- const { default: postcss } = await import("postcss");
425
- let src = readFileSync2(inputFile, "utf-8");
426
- const scanSource = scanDir ? relative(dirname3(inputFile), scanDir) || "." : ".";
427
- const sourcePath = scanSource === "." ? "./" : `./${scanSource}/`;
428
- src = `@source "${sourcePath}";
429
- ${src}`;
430
- const result = await postcss([tailwindPlugin()]).process(src, { from: inputFile });
431
- return result.css;
432
- } catch (err) {
433
- console.warn("Tailwind CSS processing failed:", err.message);
434
- return "";
435
- }
436
- }
437
- function dirname3(p) {
438
- return p.substring(0, p.lastIndexOf("/")) || "/";
439
- }
440
- function watchFile(path, onChange) {
441
- let watcher = null;
442
- import("chokidar").then((chokidar2) => {
443
- watcher = chokidar2.default.watch(resolve3(path), { persistent: false });
444
- watcher.on("change", onChange);
445
- });
446
- return watcher;
447
- }
448
-
449
- // ssr/not-found.ts
450
- function notFound(path) {
451
- return async (req, ctx) => {
452
- if (!path) return new Response("Not Found", { status: 404 });
453
- const mod = await compileTsx(path);
454
- const Component = mod?.default;
455
- const body = Component ? "404 - Not Found" : "404 - Not Found";
456
- return new Response(body, {
457
- status: 404,
458
- headers: { "content-type": "text/html; charset=utf-8" }
459
- });
460
- };
461
- }
462
-
463
- // ssr/error-boundary.ts
464
- import { createElement as createElement2 } from "react";
465
- import { TextEncoder as TextEncoder2 } from "node:util";
466
- function errorBoundary(errorPath) {
467
- return async (req, ctx, next) => {
468
- try {
469
- return await next(req, ctx);
470
- } catch (err) {
471
- const mod = await compileTsx(errorPath);
472
- const ErrorComponent = mod.default;
473
- if (!ErrorComponent) throw err;
474
- const layouts = (ctx.layoutStack || []).map((l) => l.component);
475
- const stream = await import("react-dom/server").then((m) => m.renderToReadableStream(
476
- createElement2(ErrorComponent, {
477
- error: err instanceof Error ? err : new Error(String(err)),
478
- reset: () => {
479
- }
480
- })
481
- ));
482
- const reader = stream.getReader();
483
- const chunks = [];
484
- while (true) {
485
- const { done, value } = await reader.read();
486
- if (done) break;
487
- chunks.push(value);
488
- }
489
- const encoder = new TextEncoder2();
490
- const body = chunks.reduce((acc, c) => {
491
- const merged = new Uint8Array(acc.length + c.length);
492
- merged.set(acc);
493
- merged.set(c, acc.length);
494
- return merged;
495
- }, new Uint8Array(0));
496
- return new Response(body, {
497
- status: 500,
498
- headers: { "content-type": "text/html; charset=utf-8" }
499
- });
500
- }
501
- };
502
- }
503
-
504
- // ssr/live.ts
505
- import chokidar from "chokidar";
506
-
507
234
  // router.ts
508
235
  import { WebSocketServer } from "ws";
509
236
  var createTrieNode = () => ({
@@ -632,6 +359,11 @@ var Router = class _Router {
632
359
  return this;
633
360
  }
634
361
  route(method, path, ...args) {
362
+ const last = args[args.length - 1];
363
+ if (last instanceof _Router) {
364
+ this._mountRouter(path, last);
365
+ return this;
366
+ }
635
367
  const handler = args.pop();
636
368
  const middlewares = args;
637
369
  const segments = this.splitPath(path);
@@ -779,117 +511,383 @@ var Router = class _Router {
779
511
  wildcardIdx = i;
780
512
  }
781
513
  }
782
- const segment = segments[i];
783
- if (!segment) break;
784
- const next = matchTrieNode(node, segment, params);
785
- if (!next) {
786
- if (wildcardHandler) {
787
- params["*"] = segments.slice(wildcardIdx).join("/");
788
- return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
789
- }
790
- return null;
514
+ const segment = segments[i];
515
+ if (!segment) break;
516
+ const next = matchTrieNode(node, segment, params);
517
+ if (!next) {
518
+ if (wildcardHandler) {
519
+ params["*"] = segments.slice(wildcardIdx).join("/");
520
+ return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
521
+ }
522
+ return null;
523
+ }
524
+ node = next;
525
+ }
526
+ pathMws.push(...node.pathMws);
527
+ const handler = node.handlers.get(method) || node.handlers.get("*");
528
+ if (handler) {
529
+ if (node.wildcard) params["*"] = segments.slice(segments.length).join("/");
530
+ return {
531
+ handler,
532
+ middlewares: node.middlewares.get(method) || node.middlewares.get("*") || [],
533
+ pathMws,
534
+ params
535
+ };
536
+ }
537
+ if (wildcardHandler) {
538
+ params["*"] = segments.slice(wildcardIdx).join("/");
539
+ return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
540
+ }
541
+ return null;
542
+ }
543
+ matchWsTrie(root, segments) {
544
+ let node = root;
545
+ const params = {};
546
+ for (const segment of segments) {
547
+ const next = matchWsNode(node, segment, params);
548
+ if (!next) return null;
549
+ node = next;
550
+ }
551
+ return node.handler ? { handler: node.handler, middlewares: node.middlewares, params } : null;
552
+ }
553
+ async handle(req, ctx, segments, query) {
554
+ const match = this.matchTrie(req.method, segments);
555
+ if (match?.handler) {
556
+ const { handler, middlewares: routeMws, pathMws, params } = match;
557
+ const allMws = this.globalMws.length + pathMws.length + routeMws.length === 0 ? [] : [...this.globalMws, ...pathMws, ...routeMws];
558
+ const ctxWithMatch = { ...ctx, params: { ...ctx.params, ...params } };
559
+ try {
560
+ return await this.runChain(allMws, handler, req, ctxWithMatch);
561
+ } catch (e) {
562
+ const err = e instanceof Error ? e : new Error(String(e));
563
+ return this.errorHandler ? this.errorHandler(err, req, ctxWithMatch) : new Response("Internal Server Error", { status: 500 });
564
+ }
565
+ }
566
+ if (this.globalMws.length > 0) {
567
+ try {
568
+ const delegate = () => new Response("Not Found", { status: 404 });
569
+ return await this.runChain(this.globalMws, delegate, req, ctx);
570
+ } catch (e) {
571
+ const err = e instanceof Error ? e : new Error(String(e));
572
+ return this.errorHandler ? this.errorHandler(err, req, ctx) : new Response("Internal Server Error", { status: 500 });
573
+ }
574
+ }
575
+ return new Response("Not Found", { status: 404 });
576
+ }
577
+ async runChain(middlewares, finalHandler, req, ctx) {
578
+ let index = 0;
579
+ const dispatch = async (req2, ctx2) => {
580
+ if (index < middlewares.length) {
581
+ const mw = middlewares[index++];
582
+ return mw ? await mw(req2, ctx2, dispatch) : new Response("Middleware error", { status: 500 });
583
+ }
584
+ return await finalHandler(req2, ctx2);
585
+ };
586
+ return dispatch(req, ctx);
587
+ }
588
+ };
589
+ function upgradeSocket(wss, req, socket, head, handler, ctx) {
590
+ wss.handleUpgrade(req, socket, head, (ws) => {
591
+ if (handler.open) {
592
+ handler.open(ws, ctx);
593
+ }
594
+ ws.on("message", (data) => {
595
+ handler.message?.(ws, ctx, data);
596
+ });
597
+ ws.on("close", () => {
598
+ handler.close?.(ws, ctx);
599
+ });
600
+ ws.on("error", (err) => {
601
+ handler.error?.(ws, ctx, err);
602
+ });
603
+ });
604
+ }
605
+ function sendHttpResponseOnSocket(socket, response) {
606
+ const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}`;
607
+ const headerLines = [statusLine];
608
+ response.headers.forEach((value, key) => {
609
+ headerLines.push(`${key}: ${value}`);
610
+ });
611
+ headerLines.push("Connection: close");
612
+ headerLines.push("");
613
+ const headerStr = headerLines.join("\r\n");
614
+ response.arrayBuffer().then((buf) => {
615
+ const body = Buffer.from(buf);
616
+ socket.write(headerStr + "\r\n" + body.toString());
617
+ socket.end();
618
+ }).catch(() => {
619
+ socket.write(headerStr + "\r\n");
620
+ socket.end();
621
+ });
622
+ }
623
+
624
+ // ssr/ssr.ts
625
+ var als = new AsyncLocalStorage();
626
+ __registerAls(() => als.getStore());
627
+ var isDev = process.env.NODE_ENV !== "production";
628
+ var bundleCache = /* @__PURE__ */ new Map();
629
+ function id2(s) {
630
+ return createHash2("md5").update(s).digest("hex").slice(0, 8);
631
+ }
632
+ function serializeLoaderData(ctx) {
633
+ const data = {};
634
+ for (const key of Object.keys(ctx)) {
635
+ if (!["params", "query", "mountPath", "layoutStack"].includes(key)) {
636
+ data[key] = ctx[key];
637
+ }
638
+ }
639
+ return data;
640
+ }
641
+ async function buildClientBundle(entryPath, layoutPaths) {
642
+ try {
643
+ const absEntry = resolve2(entryPath);
644
+ const absLayouts = layoutPaths.map((p) => resolve2(p));
645
+ const layoutImports = absLayouts.map((p) => `import${JSON.stringify(p)};`).join("");
646
+ const _sc = `(function(){var k='__WEIFUWU_CTX_STORE';var s=typeof globalThis!='undefined'&&globalThis[k];if(!s)return function(){};return function(v){s._ctx={...s._ctx,...v};s._snapshot={params:s._ctx.params,query:s._ctx.query,user:s._ctx.user,parsed:s._ctx.parsed,prefs:s._ctx.prefs,env:s._ctx.env};s._listeners.forEach(function(fn){fn()})}})()`;
647
+ const code = [
648
+ layoutImports,
649
+ `import{hydrateRoot}from'react-dom/client';`,
650
+ `import{createElement,useState,useEffect}from'react';`,
651
+ `import{TsxContext}from'weifuwu/react';`,
652
+ `import P from${JSON.stringify(absEntry)};`,
653
+ `var setCtx=${_sc};`,
654
+ `const c=document.getElementById('__weifuwu_root');`,
655
+ `if(window.__WEIFUWU_PROPS)setCtx({loaderData:window.__WEIFUWU_PROPS});`,
656
+ `if(!window.__WFW_ROOT){`,
657
+ `function App(){`,
658
+ `const[p,setP]=useState({C:P});`,
659
+ `useEffect(()=>{window.__WFW_SET_PAGE=(C)=>{setCtx({loaderData:window.__WEIFUWU_PROPS});setP({C})}},[]);`,
660
+ `const ctx=window.__WEIFUWU_CTX||{};`,
661
+ `return createElement(TsxContext.Provider,{value:ctx},`,
662
+ `createElement(p.C,null))`,
663
+ `}`,
664
+ `window.__WFW_ROOT=hydrateRoot(c,createElement(App));`,
665
+ `}else{`,
666
+ `window.__WFW_SET_PAGE?.(P);`,
667
+ `}`
668
+ ].join("");
669
+ const { default: esbuild2 } = await import("esbuild");
670
+ const result = await esbuild2.build({
671
+ stdin: { contents: code, loader: "tsx", resolveDir: dirname2(absEntry) },
672
+ bundle: true,
673
+ format: "esm",
674
+ jsx: "automatic",
675
+ jsxImportSource: "react",
676
+ banner: { js: "self.process={env:{}};" },
677
+ loader: { ".node": "empty" },
678
+ write: false,
679
+ minify: true
680
+ });
681
+ return result.outputFiles[0].contents;
682
+ } catch (err) {
683
+ console.error("hydration bundle failed:", err);
684
+ return null;
685
+ }
686
+ }
687
+ function ssr(path) {
688
+ const entryId = id2(resolve2(path));
689
+ const bundleKey = `/__ssr/${entryId}.js`;
690
+ const r = new Router();
691
+ r.get("/__ssr/:path", (req, ctx) => {
692
+ const buf = bundleCache.get("/__ssr/" + ctx.params.path);
693
+ return buf ? new Response(buf, {
694
+ headers: { "content-type": "application/javascript; charset=utf-8" }
695
+ }) : new Response("", { status: 404 });
696
+ });
697
+ r.get("/", async (req, ctx) => {
698
+ const pageMod = await compileTsx(path);
699
+ const Component = pageMod.default;
700
+ if (!Component) return new Response("", { status: 500 });
701
+ const layouts = ctx.layoutStack || [];
702
+ const layoutComponents = layouts.map((l) => l.component);
703
+ const layoutPaths = layouts.map((l) => l.path);
704
+ const base = (ctx.mountPath || "").replace(/\/$/, "");
705
+ const loaderData = serializeLoaderData(ctx);
706
+ const ctxValue = {
707
+ params: ctx.params,
708
+ query: ctx.query,
709
+ user: ctx.user ?? {},
710
+ parsed: ctx.parsed ?? {},
711
+ prefs: ctx.prefs ?? {},
712
+ loaderData,
713
+ env: ctx.env ?? {}
714
+ };
715
+ return als.run(ctxValue, async () => {
716
+ setCtx(ctxValue);
717
+ let element = createElement(
718
+ TsxContext.Provider,
719
+ { value: ctxValue },
720
+ createElement(
721
+ "div",
722
+ { id: "__weifuwu_root" },
723
+ createElement(Component, null)
724
+ )
725
+ );
726
+ if (layoutComponents.length === 0) {
727
+ element = createElement(
728
+ "html",
729
+ { lang: "en" },
730
+ createElement(
731
+ "head",
732
+ null,
733
+ createElement("meta", { charSet: "utf-8" }),
734
+ createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
735
+ createElement("title", null, "weifuwu")
736
+ ),
737
+ createElement("body", null, element)
738
+ );
739
+ } else {
740
+ for (const L of layoutComponents.toReversed()) {
741
+ element = createElement(L, { children: element });
742
+ }
743
+ }
744
+ let bundle = null;
745
+ if (!bundleCache.has(bundleKey)) {
746
+ const buf = await buildClientBundle(path, layoutPaths);
747
+ if (buf) bundleCache.set(bundleKey, buf);
791
748
  }
792
- node = next;
793
- }
794
- pathMws.push(...node.pathMws);
795
- const handler = node.handlers.get(method) || node.handlers.get("*");
796
- if (handler) {
797
- if (node.wildcard) params["*"] = segments.slice(segments.length).join("/");
798
- return {
799
- handler,
800
- middlewares: node.middlewares.get(method) || node.middlewares.get("*") || [],
801
- pathMws,
802
- params
803
- };
804
- }
805
- if (wildcardHandler) {
806
- params["*"] = segments.slice(wildcardIdx).join("/");
807
- return { handler: wildcardHandler, middlewares: wildcardMws, pathMws, params };
808
- }
809
- return null;
810
- }
811
- matchWsTrie(root, segments) {
812
- let node = root;
813
- const params = {};
814
- for (const segment of segments) {
815
- const next = matchWsNode(node, segment, params);
816
- if (!next) return null;
817
- node = next;
818
- }
819
- return node.handler ? { handler: node.handler, middlewares: node.middlewares, params } : null;
820
- }
821
- async handle(req, ctx, segments, query) {
822
- const match = this.matchTrie(req.method, segments);
823
- if (match?.handler) {
824
- const { handler, middlewares: routeMws, pathMws, params } = match;
825
- const allMws = this.globalMws.length + pathMws.length + routeMws.length === 0 ? [] : [...this.globalMws, ...pathMws, ...routeMws];
826
- const ctxWithMatch = { ...ctx, params: { ...ctx.params, ...params } };
827
- try {
828
- return await this.runChain(allMws, handler, req, ctxWithMatch);
829
- } catch (e) {
830
- const err = e instanceof Error ? e : new Error(String(e));
831
- return this.errorHandler ? this.errorHandler(err, req, ctxWithMatch) : new Response("Internal Server Error", { status: 500 });
749
+ if (bundleCache.has(bundleKey)) {
750
+ bundle = { url: bundleKey };
832
751
  }
752
+ const { renderToReadableStream } = await import("react-dom/server");
753
+ const stream = await renderToReadableStream(element);
754
+ return streamResponse(stream, {
755
+ ctx,
756
+ base,
757
+ isDev,
758
+ bundle,
759
+ loaderData
760
+ });
761
+ });
762
+ });
763
+ return r;
764
+ }
765
+
766
+ // ssr/layout.ts
767
+ function layout(path) {
768
+ return async (req, ctx, next) => {
769
+ const mod = await compileTsx(path);
770
+ const Component = mod.default;
771
+ if (!Component) throw new Error(`Layout ${path} has no default export`);
772
+ ctx.layoutStack = [...ctx.layoutStack || [], { path, component: Component }];
773
+ return next(req, ctx);
774
+ };
775
+ }
776
+
777
+ // ssr/tailwind.ts
778
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
779
+ import { relative, resolve as resolve3 } from "node:path";
780
+ var isDev2 = process.env.NODE_ENV !== "production";
781
+ function tailwind(cssPath, scanDir) {
782
+ let compiledCss = "";
783
+ let twWatcher = null;
784
+ return async (req, ctx, next) => {
785
+ const url = new URL(req.url);
786
+ if (url.pathname === "/__wfw/style.css") {
787
+ if (!compiledCss) compiledCss = await compile(cssPath, scanDir);
788
+ return new Response(compiledCss || "", {
789
+ headers: { "content-type": "text/css; charset=utf-8" }
790
+ });
833
791
  }
834
- if (this.globalMws.length > 0) {
835
- try {
836
- const delegate = () => new Response("Not Found", { status: 404 });
837
- return await this.runChain(this.globalMws, delegate, req, ctx);
838
- } catch (e) {
839
- const err = e instanceof Error ? e : new Error(String(e));
840
- return this.errorHandler ? this.errorHandler(err, req, ctx) : new Response("Internal Server Error", { status: 500 });
841
- }
792
+ ctx.compiledTailwindCss = compiledCss;
793
+ if (isDev2 && !twWatcher) {
794
+ twWatcher = watchFile(cssPath, () => {
795
+ compiledCss = "";
796
+ });
842
797
  }
843
- return new Response("Not Found", { status: 404 });
844
- }
845
- async runChain(middlewares, finalHandler, req, ctx) {
846
- let index = 0;
847
- const dispatch = async (req2, ctx2) => {
848
- if (index < middlewares.length) {
849
- const mw = middlewares[index++];
850
- return mw ? await mw(req2, ctx2, dispatch) : new Response("Middleware error", { status: 500 });
851
- }
852
- return await finalHandler(req2, ctx2);
853
- };
854
- return dispatch(req, ctx);
855
- }
856
- };
857
- function upgradeSocket(wss, req, socket, head, handler, ctx) {
858
- wss.handleUpgrade(req, socket, head, (ws) => {
859
- if (handler.open) {
860
- handler.open(ws, ctx);
798
+ return next(req, ctx);
799
+ };
800
+ }
801
+ async function compile(cssPath, scanDir) {
802
+ try {
803
+ const inputFile = resolve3(cssPath);
804
+ if (!existsSync2(inputFile)) {
805
+ mkdirSync2(dirname3(inputFile), { recursive: true });
806
+ writeFileSync(inputFile, '@import "tailwindcss"\n', "utf-8");
861
807
  }
862
- ws.on("message", (data) => {
863
- handler.message?.(ws, ctx, data);
864
- });
865
- ws.on("close", () => {
866
- handler.close?.(ws, ctx);
867
- });
868
- ws.on("error", (err) => {
869
- handler.error?.(ws, ctx, err);
870
- });
871
- });
808
+ const { default: tailwindPlugin } = await import("@tailwindcss/postcss");
809
+ const { default: postcss } = await import("postcss");
810
+ let src = readFileSync2(inputFile, "utf-8");
811
+ const scanSource = scanDir ? relative(dirname3(inputFile), scanDir) || "." : ".";
812
+ const sourcePath = scanSource === "." ? "./" : `./${scanSource}/`;
813
+ src = `@source "${sourcePath}";
814
+ ${src}`;
815
+ const result = await postcss([tailwindPlugin()]).process(src, { from: inputFile });
816
+ return result.css;
817
+ } catch (err) {
818
+ console.warn("Tailwind CSS processing failed:", err.message);
819
+ return "";
820
+ }
872
821
  }
873
- function sendHttpResponseOnSocket(socket, response) {
874
- const statusLine = `HTTP/1.1 ${response.status} ${response.statusText}`;
875
- const headerLines = [statusLine];
876
- response.headers.forEach((value, key) => {
877
- headerLines.push(`${key}: ${value}`);
878
- });
879
- headerLines.push("Connection: close");
880
- headerLines.push("");
881
- const headerStr = headerLines.join("\r\n");
882
- response.arrayBuffer().then((buf) => {
883
- const body = Buffer.from(buf);
884
- socket.write(headerStr + "\r\n" + body.toString());
885
- socket.end();
886
- }).catch(() => {
887
- socket.write(headerStr + "\r\n");
888
- socket.end();
822
+ function dirname3(p) {
823
+ return p.substring(0, p.lastIndexOf("/")) || "/";
824
+ }
825
+ function watchFile(path, onChange) {
826
+ let watcher = null;
827
+ import("chokidar").then((chokidar2) => {
828
+ watcher = chokidar2.default.watch(resolve3(path), { persistent: false });
829
+ watcher.on("change", onChange);
889
830
  });
831
+ return watcher;
832
+ }
833
+
834
+ // ssr/not-found.ts
835
+ function notFound(path) {
836
+ return async (req, ctx) => {
837
+ if (!path) return new Response("Not Found", { status: 404 });
838
+ const mod = await compileTsx(path);
839
+ const Component = mod?.default;
840
+ const body = Component ? "404 - Not Found" : "404 - Not Found";
841
+ return new Response(body, {
842
+ status: 404,
843
+ headers: { "content-type": "text/html; charset=utf-8" }
844
+ });
845
+ };
846
+ }
847
+
848
+ // ssr/error-boundary.ts
849
+ import { createElement as createElement2 } from "react";
850
+ import { TextEncoder as TextEncoder2 } from "node:util";
851
+ function errorBoundary(errorPath) {
852
+ return async (req, ctx, next) => {
853
+ try {
854
+ return await next(req, ctx);
855
+ } catch (err) {
856
+ const mod = await compileTsx(errorPath);
857
+ const ErrorComponent = mod.default;
858
+ if (!ErrorComponent) throw err;
859
+ const layouts = (ctx.layoutStack || []).map((l) => l.component);
860
+ const stream = await import("react-dom/server").then((m) => m.renderToReadableStream(
861
+ createElement2(ErrorComponent, {
862
+ error: err instanceof Error ? err : new Error(String(err)),
863
+ reset: () => {
864
+ }
865
+ })
866
+ ));
867
+ const reader = stream.getReader();
868
+ const chunks = [];
869
+ while (true) {
870
+ const { done, value } = await reader.read();
871
+ if (done) break;
872
+ chunks.push(value);
873
+ }
874
+ const encoder = new TextEncoder2();
875
+ const body = chunks.reduce((acc, c) => {
876
+ const merged = new Uint8Array(acc.length + c.length);
877
+ merged.set(acc);
878
+ merged.set(c, acc.length);
879
+ return merged;
880
+ }, new Uint8Array(0));
881
+ return new Response(body, {
882
+ status: 500,
883
+ headers: { "content-type": "text/html; charset=utf-8" }
884
+ });
885
+ }
886
+ };
890
887
  }
891
888
 
892
889
  // ssr/live.ts
890
+ import chokidar from "chokidar";
893
891
  var clients = /* @__PURE__ */ new Set();
894
892
  function broadcastReload() {
895
893
  for (const ws of clients) {
@@ -931,6 +929,5 @@ export {
931
929
  liveReload,
932
930
  notFound,
933
931
  ssr,
934
- ssrBundleHandler,
935
932
  tailwind
936
933
  };
package/dist/ssr/ssr.d.ts CHANGED
@@ -1,3 +1,2 @@
1
- import type { Handler } from '../types.ts';
2
- export declare function ssr(path: string): Handler;
3
- export declare function ssrBundleHandler(): Handler;
1
+ import { Router } from '../router.ts';
2
+ export declare function ssr(path: string): Router;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "Web-standard HTTP framework for Node.js — (req, ctx) => Response",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",