weifuwu 0.18.17 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,8 +15,7 @@ serve((req, ctx) => new Response('Hello, World!'), { port: 3000 })
15
15
  ```
16
16
 
17
17
  ```ts
18
- import { serve, Router, preferences } from 'weifuwu'
19
- import { ssr, layout, liveReload } from 'weifuwu/ssr'
18
+ import { serve, Router, preferences, ssr, layout, liveReload } from 'weifuwu'
20
19
  const app = new Router()
21
20
  app.use(preferences({ dir: './locales' }))
22
21
  app.use(layout('./layouts/root.tsx'))
@@ -568,12 +567,12 @@ app.use(requestId({ header: 'X-Request-Id', generator: () => crypto.randomUUID()
568
567
 
569
568
  ---
570
569
 
571
- ## React SSR (weifuwu/ssr)
570
+ ## React SSR (weifuwu)
572
571
 
573
- Import from `'weifuwu/ssr'`:
572
+ Import from `'weifuwu'` (no separate ssr entry):
574
573
 
575
574
  ```ts
576
- import { ssr, layout, liveReload, errorBoundary, notFound, tailwind } from 'weifuwu/ssr'
575
+ import { ssr, layout, liveReload, errorBoundary, notFound, tailwind } from 'weifuwu'
577
576
  ```
578
577
 
579
578
  ### ssr(path) [β]
@@ -588,7 +587,8 @@ app.get('/about', ssr('./pages/about.tsx'))
588
587
  - Reads `ctx.layoutStack` (set by `layout()` middleware) and wraps the component from outer to inner
589
588
  - Injects hydration script pointing to the auto-generated client bundle at `/__ssr/[hash].js`
590
589
  - Serializes middleware-injected `ctx` data to `window.__WEIFUWU_CTX` for client-side hydration
591
- - Dev mode: injects live reload WebSocket script
590
+ - **Dev mode:** uses `createRoot` instead of `hydrateRoot` — all hooks (`useState`, `useEffect`) work correctly; SSR content is still streamed for fast first paint
591
+ - **Prod mode:** uses `hydrateRoot` for full SSR hydration
592
592
 
593
593
  ### layout(path) [β]
594
594
 
@@ -610,7 +610,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
610
610
 
611
611
  ### liveReload(opts) [β]
612
612
 
613
- Returns a `Router` that registers a WebSocket endpoint at `/__weifuwu/livereload` and starts a file watcher on the given directories. When a `.tsx` file changes, it clears the compile cache and broadcasts a reload to all connected browsers.
613
+ Returns a `Router` that registers a WebSocket endpoint at `/__weifuwu/livereload` and starts a file watcher on the given directories. When a `.tsx` file changes, it compiles a hot-update bundle and broadcasts it to all connected browsers.
614
614
 
615
615
  ```ts
616
616
  if (process.env.NODE_ENV !== 'production') {
@@ -618,6 +618,21 @@ if (process.env.NODE_ENV !== 'production') {
618
618
  }
619
619
  ```
620
620
 
621
+ **How HMR works:**
622
+
623
+ 1. File change detected → compiles the entry `page.tsx` (bundles all dependencies) with esbuild
624
+ 2. Browser receives WS message → `import()` the hot bundle
625
+ 3. Hot bundle calls `window.__WFW_REFRESH(newComponent)` → updates a stable proxy component
626
+ 4. Proxy reference unchanged → React reuses fiber tree → `useState` values preserved
627
+ 5. Sets `ctx.tick` to trigger re-render without page navigation
628
+
629
+ **Key properties:**
630
+
631
+ - **State preservation** — input fields, scroll position, and other `useState`-driven state survive edits
632
+ - **Entry hash isolation** — each `ssr()` route has its own entry hash; WS messages carry the hash so only matching tabs update
633
+ - **Lazy hydration cache** — hydration bundle is only rebuilt on actual page load, not on every file change
634
+ - **Compilation fallback** — if esbuild encounters a syntax error, `location.reload()` is called to show the error
635
+
621
636
  Mount without a path — the internal `/__weifuwu/livereload` route is invisible to the user. The `ssr()` function automatically injects the client-side WS script in dev mode.
622
637
 
623
638
  | Option | Type | Default | Description |
@@ -652,15 +667,15 @@ Returns a catch-all handler for 404 pages. Typically registered last:
652
667
  app.all('/*', notFound('./not-found.tsx'))
653
668
  ```
654
669
 
655
- ### tailwind(path) [α]
670
+ ### tailwind(cssPath) [α]
656
671
 
657
- Compiles Tailwind CSS v4 via `@tailwindcss/postcss` and serves it at `/__wfw/style.css`. In dev mode, watches the CSS file for changes.
672
+ Compiles Tailwind CSS v4 via `@tailwindcss/postcss` and serves it at `/__wfw/style.css`. The file is compiled once and cached; on `app.css` changes in dev mode, the style is pushed to connected browsers via liveReload's WebSocket.
658
673
 
659
674
  ```ts
660
- app.use(tailwind('./app.css'))
675
+ app.use(tailwind('./ui'))
661
676
  ```
662
677
 
663
- When `tailwind()` middleware is detected, `ssr()` automatically injects `<link rel="stylesheet" href="/__wfw/style.css" />` into the HTML `<head>`.
678
+ The `dir` argument is the directory containing `app.css`. When `tailwind()` middleware is detected, `ssr()` automatically injects `<link rel="stylesheet" href="/__wfw/style.css" />` into the HTML `<head>`.
664
679
 
665
680
  ### seo [β] + seoMiddleware [α]
666
681
 
@@ -893,14 +908,14 @@ function Toast() {
893
908
  Auto-detected when `NODE_ENV !== 'production'`. File watching + live reload via `liveReload()`:
894
909
 
895
910
  ```ts
896
- import { liveReload } from 'weifuwu/ssr'
911
+ import { liveReload } from 'weifuwu'
897
912
 
898
913
  if (process.env.NODE_ENV !== 'production') {
899
914
  app.use(liveReload({ dirs: ['./pages', './layouts'] }))
900
915
  }
901
916
  ```
902
917
 
903
- When a `.tsx` file changes, `ssr()` clears its compile cache and the browser auto-refreshes. No process restart needed.
918
+ When a `.tsx` file changes, the browser hot-updates without refreshing `useState` values are preserved. See `liveReload` section for details.
904
919
 
905
920
  Tailwind v4 auto-compile via `tailwind()` middleware:
906
921
 
package/cli.ts CHANGED
@@ -35,6 +35,24 @@ async function cmdInit(name: string) {
35
35
  const templateDir = join(pkgRoot, 'cli', 'template')
36
36
  await cp(templateDir, targetDir, { recursive: true })
37
37
 
38
+ // Rewrite local imports → package imports for the copied project
39
+ for (const file of ['app.ts', 'index.ts', 'ui/page.tsx']) {
40
+ const fp = join(targetDir, file)
41
+ let content = await readFile(fp, 'utf-8')
42
+ content = content
43
+ .replace(/from '\.\.\/\.\.\/index\.ts'/g, "from 'weifuwu'")
44
+ .replace(/from '\.\.\/\.\.\/\.\.\/react\.ts'/g, "from 'weifuwu/react'")
45
+ .replace(/import \{ join \} from 'node:path'\n/gm, '')
46
+ .replace(/const _ui = join\(import\.meta\.dirname, 'ui'\)\n/gm, '')
47
+ .replace(/const _loc = join\(import\.meta\.dirname, 'locales'\)\n\n/, '')
48
+ .replace(/preferences\(\{ dir: _loc, locale: \{ default: 'en' \}, theme: \{ default: 'system' \} \}\)/g, "preferences({ dir: './locales' })")
49
+ .replace(/tailwind\(_ui\)/g, "tailwind('./ui')")
50
+ .replace(/layout\(join\(_ui, 'layout\.tsx'\)\)/g, "layout('./ui/layout.tsx')")
51
+ .replace(/ssr\(join\(_ui, 'page\.tsx'\)\)/g, "ssr('./ui/page.tsx')")
52
+ .replace(/join\(import\.meta\.dirname, 'ui'\)/g, "'./ui'")
53
+ await writeFile(fp, content)
54
+ }
55
+
38
56
  // Write config files
39
57
  await writeFile(join(targetDir, 'package.json'), JSON.stringify({
40
58
  name,
package/dist/cli.js CHANGED
@@ -28,6 +28,12 @@ async function cmdInit(name) {
28
28
  await mkdir(targetDir, { recursive: true });
29
29
  const templateDir = join(pkgRoot, "cli", "template");
30
30
  await cp(templateDir, targetDir, { recursive: true });
31
+ for (const file of ["app.ts", "index.ts", "ui/page.tsx"]) {
32
+ const fp = join(targetDir, file);
33
+ let content = await readFile(fp, "utf-8");
34
+ content = content.replace(/from '\.\.\/\.\.\/index\.ts'/g, "from 'weifuwu'").replace(/from '\.\.\/\.\.\/\.\.\/react\.ts'/g, "from 'weifuwu/react'").replace(/import \{ join \} from 'node:path'\n/gm, "").replace(/const _ui = join\(import\.meta\.dirname, 'ui'\)\n/gm, "").replace(/const _loc = join\(import\.meta\.dirname, 'locales'\)\n\n/, "").replace(/preferences\(\{ dir: _loc, locale: \{ default: 'en' \}, theme: \{ default: 'system' \} \}\)/g, "preferences({ dir: './locales' })").replace(/tailwind\(_ui\)/g, "tailwind('./ui')").replace(/layout\(join\(_ui, 'layout\.tsx'\)\)/g, "layout('./ui/layout.tsx')").replace(/ssr\(join\(_ui, 'page\.tsx'\)\)/g, "ssr('./ui/page.tsx')").replace(/join\(import\.meta\.dirname, 'ui'\)/g, "'./ui'");
35
+ await writeFile(fp, content);
36
+ }
31
37
  await writeFile(join(targetDir, "package.json"), JSON.stringify({
32
38
  name,
33
39
  type: "module",
package/dist/compile.d.ts CHANGED
@@ -1,4 +1,12 @@
1
+ export declare function id(s: string): string;
1
2
  export declare function clearCompileCache(): void;
2
3
  export declare function compileTsx(path: string): Promise<any>;
3
4
  /** Dev hot-reload: CJS + in-memory + vm (faster than ESM + disk + import) */
4
5
  export declare function compileTsxDev(path: string): Promise<any>;
6
+ /** Build a single vendor bundle containing all needed vendor modules */
7
+ export declare function compileVendorBundle(): Promise<string>;
8
+ /** Hot-reload: ESM bundle, calls __WFW_REFRESH on import */
9
+ export declare function compileHotComponent(path: string): Promise<{
10
+ hash: string;
11
+ code: string;
12
+ }>;
package/dist/index.js CHANGED
@@ -376,13 +376,13 @@ var Router = class _Router {
376
376
  const mw = match.middlewares[index++];
377
377
  return mw(innerReq, ctx2, dispatch);
378
378
  }
379
- return await new Promise((resolve13) => {
379
+ return await new Promise((resolve14) => {
380
380
  try {
381
381
  upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
382
- resolve13(new Response(null, { status: 101 }));
382
+ resolve14(new Response(null, { status: 101 }));
383
383
  } catch {
384
384
  socket.destroy();
385
- resolve13(new Response("WebSocket upgrade failed", { status: 500 }));
385
+ resolve14(new Response("WebSocket upgrade failed", { status: 500 }));
386
386
  }
387
387
  });
388
388
  };
@@ -572,7 +572,7 @@ function sendHttpResponseOnSocket(socket, response) {
572
572
  }
573
573
 
574
574
  // tsx-context.ts
575
- import { createContext } from "react";
575
+ import { useSyncExternalStore, createContext } from "react";
576
576
  var DEFAULT_CTX = { params: {}, query: {}, parsed: {}, prefs: {}, loaderData: {}, env: {}, user: {} };
577
577
  var KEY = "__WEIFUWU_CTX_STORE";
578
578
  function getStore() {
@@ -2845,7 +2845,7 @@ function createHub(opts) {
2845
2845
  }
2846
2846
  });
2847
2847
  }
2848
- function join7(key, ws) {
2848
+ function join8(key, ws) {
2849
2849
  if (!channels.has(key)) {
2850
2850
  channels.set(key, /* @__PURE__ */ new Set());
2851
2851
  redisSub?.subscribe(`${prefix}${key}`);
@@ -2886,7 +2886,7 @@ function createHub(opts) {
2886
2886
  await redisSub.quit();
2887
2887
  }
2888
2888
  }
2889
- return { join: join7, leave, broadcast, close };
2889
+ return { join: join8, leave, broadcast, close };
2890
2890
  }
2891
2891
 
2892
2892
  // queue/index.ts
@@ -4696,14 +4696,14 @@ function forkApp(opts) {
4696
4696
  return { child, port: opts.port };
4697
4697
  }
4698
4698
  function stopProcess(mp, timeout = 1e4) {
4699
- return new Promise((resolve13) => {
4699
+ return new Promise((resolve14) => {
4700
4700
  const timer = setTimeout(() => {
4701
4701
  mp.child.kill("SIGKILL");
4702
- resolve13();
4702
+ resolve14();
4703
4703
  }, timeout);
4704
4704
  mp.child.on("exit", () => {
4705
4705
  clearTimeout(timer);
4706
- resolve13();
4706
+ resolve14();
4707
4707
  });
4708
4708
  mp.child.kill("SIGTERM");
4709
4709
  });
@@ -5150,6 +5150,7 @@ import { createHash as createHash2 } from "node:crypto";
5150
5150
  import vm from "node:vm";
5151
5151
  import { createRequire } from "node:module";
5152
5152
  var _cjsRequire = createRequire(import.meta.url);
5153
+ var _userRequire = null;
5153
5154
  var OUT_DIR = ".weifuwu/ssr";
5154
5155
  var cache = /* @__PURE__ */ new Map();
5155
5156
  var externals = [
@@ -5247,6 +5248,62 @@ async function compileTsxDev(path2) {
5247
5248
  cache.set(absPath, mod);
5248
5249
  return mod;
5249
5250
  }
5251
+ var vendorBundle = null;
5252
+ async function compileVendorBundle() {
5253
+ if (vendorBundle) return vendorBundle;
5254
+ if (!_userRequire) _userRequire = createRequire(join2(process.cwd(), "package.json"));
5255
+ const modules = {
5256
+ "react": [],
5257
+ "react-dom": ["react"],
5258
+ "react-dom/client": ["react"],
5259
+ "react/jsx-runtime": ["react"]
5260
+ };
5261
+ for (const request of Object.keys(modules)) {
5262
+ const mod = _userRequire(request);
5263
+ const keys = Object.keys(mod).filter((k) => !k.startsWith("_") && k !== "default");
5264
+ modules[request] = keys;
5265
+ }
5266
+ const wfwMod = _userRequire("weifuwu/react");
5267
+ const wfwKeys = Object.keys(wfwMod).filter((k) => !k.startsWith("_") && k !== "default");
5268
+ const used = /* @__PURE__ */ new Set();
5269
+ const stmts = [""];
5270
+ for (const [request, keys] of Object.entries(modules)) {
5271
+ const unique = keys.filter((k) => !used.has(k) && used.add(k));
5272
+ if (unique.length > 0) stmts.push(`export { ${unique.join(", ")} } from ${JSON.stringify(request)};`);
5273
+ }
5274
+ const uidWfw = wfwKeys.filter((k) => !used.has(k) && used.add(k));
5275
+ if (uidWfw.length > 0) stmts.push(`export { ${uidWfw.join(", ")} } from 'weifuwu/react';`);
5276
+ const result = await esbuild.build({
5277
+ stdin: { contents: stmts.join("\n"), resolveDir: process.cwd() },
5278
+ format: "esm",
5279
+ bundle: true,
5280
+ write: false
5281
+ });
5282
+ vendorBundle = new TextDecoder().decode(result.outputFiles[0].contents);
5283
+ return vendorBundle;
5284
+ }
5285
+ async function compileHotComponent(path2) {
5286
+ const absPath = resolve3(path2);
5287
+ const h = id(absPath);
5288
+ const stdin = `import C from ${JSON.stringify(absPath)};
5289
+ (window.__WFW_REFRESH||function(){})(C)`;
5290
+ const result = await esbuild.build({
5291
+ stdin: { contents: stdin, loader: "tsx", resolveDir: dirname(absPath) },
5292
+ format: "esm",
5293
+ platform: "browser",
5294
+ jsx: "automatic",
5295
+ jsxImportSource: "react",
5296
+ bundle: true,
5297
+ external: ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu/react"],
5298
+ write: false
5299
+ });
5300
+ let code = new TextDecoder().decode(result.outputFiles[0].contents);
5301
+ if (code.includes("__require") && (code.includes('"react"') || code.includes("'react'"))) {
5302
+ code = `import __r from '/__wfw/v/bundle';
5303
+ ` + code.replace(/__require\(["']react["']\)/g, "__r");
5304
+ }
5305
+ return { hash: h, code };
5306
+ }
5250
5307
 
5251
5308
  // stream.ts
5252
5309
  import { TextDecoder as TextDecoder2, TextEncoder as TextEncoder2 } from "node:util";
@@ -5262,8 +5319,20 @@ function getPublicEnv() {
5262
5319
  return _publicEnv;
5263
5320
  }
5264
5321
  function buildHeadPayload(opts) {
5265
- const { ctx, base, compiledTailwindCss } = opts;
5322
+ const { ctx, base, compiledTailwindCss, isDev: isDev2 } = opts;
5266
5323
  let result = "";
5324
+ if (isDev2) {
5325
+ result += `<script type="importmap">{
5326
+ "imports": {
5327
+ "react": "/__wfw/v/bundle",
5328
+ "react-dom": "/__wfw/v/bundle",
5329
+ "react-dom/client": "/__wfw/v/bundle",
5330
+ "react/jsx-runtime": "/__wfw/v/bundle",
5331
+ "weifuwu/react": "/__wfw/v/bundle"
5332
+ }
5333
+ }</script>
5334
+ `;
5335
+ }
5267
5336
  if (ctx.prefs?.theme) {
5268
5337
  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>
5269
5338
  `;
@@ -5352,7 +5421,7 @@ function streamResponse(reactStream, opts) {
5352
5421
  if (opts.isDev) {
5353
5422
  controller.enqueue(encoder2.encode(
5354
5423
  `
5355
- <script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'/__weifuwu/livereload');ws.onmessage=function(e){if(e.data==='reload')location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},500)}})()</script>`
5424
+ <script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//'+location.host+'/__weifuwu/livereload');var t=0;ws.onmessage=function(e){try{var m=JSON.parse(e.data);if(m.type==='component'){if(m.entry&&m.entry!==window.__WFW_ENTRY)return;import('/__wfw/h/'+m.hash+'?'+Date.now()).catch(function(){location.reload()});if(m.css){var s=document.querySelector('style[data-lr]')||function(){var x=document.createElement('style');x.setAttribute('data-lr','');document.head.appendChild(x);return x}();s.textContent=m.css}return}if(m.type==='css'){var s=document.querySelector('style[data-lr]')||function(){var x=document.createElement('style');x.setAttribute('data-lr','');document.head.appendChild(x);return x}();s.textContent=m.css;return}}catch(_){}if(e.data==='reload'&&Date.now()-t>1e3){t=Date.now();location.reload()}};ws.onclose=function(){if(Date.now()-t>1e3){t=Date.now();setTimeout(function(){location.reload()},500)}}})()</script>`
5356
5425
  ));
5357
5426
  }
5358
5427
  } catch {
@@ -5374,17 +5443,30 @@ var als = new AsyncLocalStorage();
5374
5443
  __registerAls(() => als.getStore());
5375
5444
  var isDev = process.env.NODE_ENV !== "production";
5376
5445
  var bundleCache = /* @__PURE__ */ new Map();
5446
+ var _bundleDirty = false;
5447
+ function markClientBundleDirty() {
5448
+ _bundleDirty = true;
5449
+ }
5450
+ function getBundle(key) {
5451
+ if (_bundleDirty) {
5452
+ bundleCache.clear();
5453
+ _bundleDirty = false;
5454
+ }
5455
+ return bundleCache.get(key);
5456
+ }
5457
+ function setBundle(key, buf) {
5458
+ if (_bundleDirty) {
5459
+ bundleCache.clear();
5460
+ _bundleDirty = false;
5461
+ }
5462
+ bundleCache.set(key, buf);
5463
+ }
5377
5464
  function id2(s) {
5378
5465
  return createHash3("md5").update(s).digest("hex").slice(0, 8);
5379
5466
  }
5380
5467
  function serializeLoaderData(ctx) {
5381
- const data = {};
5382
- for (const key of Object.keys(ctx)) {
5383
- if (!["params", "query", "mountPath", "layoutStack"].includes(key)) {
5384
- data[key] = ctx[key];
5385
- }
5386
- }
5387
- return data;
5468
+ const ld = ctx.loaderData;
5469
+ return ld && typeof ld === "object" ? ld : {};
5388
5470
  }
5389
5471
  async function buildClientBundle(entryPath, layoutPaths) {
5390
5472
  try {
@@ -5394,26 +5476,24 @@ async function buildClientBundle(entryPath, layoutPaths) {
5394
5476
  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()})}})()`;
5395
5477
  const code = [
5396
5478
  layoutImports,
5397
- `import{hydrateRoot}from'react-dom/client';`,
5398
- `import{createElement,useState,useEffect}from'react';`,
5479
+ `${isDev ? "import{createRoot}from'react-dom/client';" : "import{hydrateRoot}from'react-dom/client';"}`,
5480
+ `import{createElement}from'react';`,
5399
5481
  `import{TsxContext}from'weifuwu/react';`,
5400
5482
  `import P from${JSON.stringify(absEntry)};`,
5401
5483
  `var setCtx=${_sc};`,
5402
5484
  `const c=document.getElementById('__weifuwu_root');`,
5403
5485
  `if(window.__WEIFUWU_PROPS)setCtx({loaderData:window.__WEIFUWU_PROPS});`,
5404
- `if(!window.__WFW_ROOT){`,
5486
+ // Dev: stable proxy chain — _P → _W (stable) → actual component
5487
+ isDev ? `const _W=function(props){return(_W._fn||P)(props)};_W._fn=P;const _P=function(props){return createElement(_W,props)};` : "",
5488
+ // Dev: HMR handler — updates proxy + re-renders root
5489
+ isDev ? `window.__WFW_ENTRY=${JSON.stringify(id2(absEntry))};window.__WFW_REFRESH=function(n){_W._fn=n;window.__WFW_ROOT.render(createElement(App))};` : "",
5405
5490
  `function App(){`,
5406
- `const[p,setP]=useState({C:P});`,
5407
- `useEffect(()=>{window.__WFW_SET_PAGE=(C)=>{setCtx({loaderData:window.__WEIFUWU_PROPS});setP({C})}},[]);`,
5408
5491
  `const ctx=window.__WEIFUWU_CTX||{};`,
5409
5492
  `return createElement(TsxContext.Provider,{value:ctx},`,
5410
- `createElement(p.C,null))`,
5493
+ isDev ? `createElement(_P,null))` : `createElement(P,null))`,
5411
5494
  `}`,
5412
- `window.__WFW_ROOT=hydrateRoot(c,createElement(App));`,
5413
- `}else{`,
5414
- `window.__WFW_SET_PAGE?.(P);`,
5415
- `}`
5416
- ].join("");
5495
+ isDev ? `window.__WFW_ROOT=createRoot(c);window.__WFW_ROOT.render(createElement(App));` : `hydrateRoot(c,createElement(App));`
5496
+ ].filter(Boolean).join("");
5417
5497
  const { default: esbuild2 } = await import("esbuild");
5418
5498
  const result = await esbuild2.build({
5419
5499
  stdin: { contents: code, loader: "tsx", resolveDir: dirname2(absEntry) },
@@ -5423,8 +5503,9 @@ async function buildClientBundle(entryPath, layoutPaths) {
5423
5503
  jsxImportSource: "react",
5424
5504
  banner: { js: "self.process={env:{}};" },
5425
5505
  loader: { ".node": "empty" },
5506
+ external: isDev ? ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "weifuwu/react"] : void 0,
5426
5507
  write: false,
5427
- minify: true
5508
+ minify: !isDev
5428
5509
  });
5429
5510
  return result.outputFiles[0].contents;
5430
5511
  } catch (err) {
@@ -5437,7 +5518,7 @@ function ssr(path2) {
5437
5518
  const bundleKey = `/__ssr/${entryId}.js`;
5438
5519
  const r = new Router();
5439
5520
  r.get("/__ssr/:path", (req, ctx) => {
5440
- const buf = bundleCache.get("/__ssr/" + ctx.params.path);
5521
+ const buf = getBundle("/__ssr/" + ctx.params.path);
5441
5522
  return buf ? new Response(buf, {
5442
5523
  headers: { "content-type": "application/javascript; charset=utf-8" }
5443
5524
  }) : new Response("", { status: 404 });
@@ -5462,12 +5543,16 @@ function ssr(path2) {
5462
5543
  };
5463
5544
  return als.run(ctxValue, async () => {
5464
5545
  setCtx(ctxValue);
5546
+ if (ctxValue.parsed?.__localeData) {
5547
+ ;
5548
+ globalThis.__LOCALE_DATA__ = ctxValue.parsed.__localeData;
5549
+ }
5465
5550
  let element = createElement(
5466
- TsxContext.Provider,
5467
- { value: ctxValue },
5551
+ "div",
5552
+ { id: "__weifuwu_root" },
5468
5553
  createElement(
5469
- "div",
5470
- { id: "__weifuwu_root" },
5554
+ TsxContext.Provider,
5555
+ { value: ctxValue },
5471
5556
  createElement(Component, null)
5472
5557
  )
5473
5558
  );
@@ -5490,11 +5575,11 @@ function ssr(path2) {
5490
5575
  }
5491
5576
  }
5492
5577
  let bundle = null;
5493
- if (!bundleCache.has(bundleKey)) {
5578
+ if (!getBundle(bundleKey)) {
5494
5579
  const buf = await buildClientBundle(path2, layoutPaths);
5495
- if (buf) bundleCache.set(bundleKey, buf);
5580
+ if (buf) setBundle(bundleKey, buf);
5496
5581
  }
5497
- if (bundleCache.has(bundleKey)) {
5582
+ if (getBundle(bundleKey)) {
5498
5583
  bundle = { url: bundleKey };
5499
5584
  }
5500
5585
  const { renderToReadableStream } = await import("react-dom/server");
@@ -5515,77 +5600,30 @@ function ssr(path2) {
5515
5600
  // tailwind.ts
5516
5601
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
5517
5602
  import { join as join3, relative, resolve as resolve5 } from "node:path";
5518
-
5519
- // live.ts
5520
- import chokidar from "chokidar";
5521
- var clients = /* @__PURE__ */ new Set();
5522
- function broadcastReload() {
5523
- for (const ws of clients) {
5524
- try {
5525
- ws.send("reload");
5526
- } catch {
5527
- clients.delete(ws);
5528
- }
5529
- }
5530
- }
5531
- function liveReload(opts) {
5532
- const r = new Router();
5533
- r.ws("/__weifuwu/livereload", {
5534
- open(ws) {
5535
- clients.add(ws);
5536
- ws.on("close", () => clients.delete(ws));
5537
- ws.on("error", () => clients.delete(ws));
5538
- }
5539
- });
5540
- const watcher = chokidar.watch(opts.dirs, {
5541
- ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
5542
- ignoreInitial: true
5543
- });
5544
- watcher.on("change", async (filePath) => {
5545
- if (!/\.tsx?$/.test(filePath)) return;
5546
- clearCompileCache();
5547
- try {
5548
- await compileTsxDev(filePath);
5549
- } catch {
5550
- }
5551
- broadcastReload();
5552
- });
5553
- r.close = () => {
5554
- watcher.close();
5555
- clients.clear();
5556
- };
5557
- return r;
5558
- }
5559
-
5560
- // tailwind.ts
5561
- var isDev2 = process.env.NODE_ENV !== "production";
5562
5603
  var extraSources = /* @__PURE__ */ new Set();
5604
+ var cssCache = /* @__PURE__ */ new Map();
5563
5605
  function tailwind(dir) {
5564
5606
  const cssDir = resolve5(dir);
5565
5607
  const cssPath = join3(cssDir, "app.css");
5566
- let compiledCss = "";
5567
- let twWatcher = null;
5568
5608
  const r = new Router();
5569
5609
  r.use(async (req, ctx, next) => {
5570
- if (!compiledCss) compiledCss = await compile(cssPath, cssDir);
5571
- ctx.compiledTailwindCss = compiledCss;
5572
- if (isDev2 && !twWatcher) {
5573
- twWatcher = watchFile(cssPath, () => {
5574
- compiledCss = "";
5575
- broadcastReload();
5576
- });
5610
+ if (!cssCache.has(cssPath)) {
5611
+ cssCache.set(cssPath, await compileTailwindCss(cssPath, cssDir));
5577
5612
  }
5613
+ ctx.compiledTailwindCss = cssCache.get(cssPath);
5578
5614
  return next(req, ctx);
5579
5615
  });
5580
5616
  r.get("/__wfw/style.css", async (req, ctx) => {
5581
- if (!compiledCss) compiledCss = await compile(cssPath, cssDir);
5582
- return new Response(compiledCss || "", {
5617
+ if (!cssCache.has(cssPath)) {
5618
+ cssCache.set(cssPath, await compileTailwindCss(cssPath, cssDir));
5619
+ }
5620
+ return new Response(cssCache.get(cssPath) || "", {
5583
5621
  headers: { "content-type": "text/css; charset=utf-8" }
5584
5622
  });
5585
5623
  });
5586
5624
  return r;
5587
5625
  }
5588
- async function compile(cssPath, cssDir) {
5626
+ async function compileTailwindCss(cssPath, cssDir) {
5589
5627
  try {
5590
5628
  if (!existsSync3(cssPath)) {
5591
5629
  mkdirSync2(cssDir, { recursive: true });
@@ -5602,20 +5640,13 @@ ${src}`;
5602
5640
  ${src}`;
5603
5641
  }
5604
5642
  const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
5643
+ cssCache.set(cssPath, result.css);
5605
5644
  return result.css;
5606
5645
  } catch (err) {
5607
5646
  console.warn("Tailwind CSS processing failed:", err.message);
5608
5647
  return "";
5609
5648
  }
5610
5649
  }
5611
- function watchFile(path2, onChange) {
5612
- let watcher = null;
5613
- import("chokidar").then((chokidar2) => {
5614
- watcher = chokidar2.default.watch(resolve5(path2), { persistent: false });
5615
- watcher.on("change", onChange);
5616
- });
5617
- return watcher;
5618
- }
5619
5650
 
5620
5651
  // layout.ts
5621
5652
  function layout(path2) {
@@ -5864,10 +5895,10 @@ function createBashTool(ctx) {
5864
5895
  return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
5865
5896
  }
5866
5897
  const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
5867
- return new Promise((resolve13) => {
5898
+ return new Promise((resolve14) => {
5868
5899
  const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
5869
5900
  const truncated = stdout.length > 1e6 || stderr.length > 1e6;
5870
- resolve13({
5901
+ resolve14({
5871
5902
  stdout: stdout.slice(0, 1e6),
5872
5903
  stderr: stderr.slice(0, 1e6),
5873
5904
  exitCode: error?.code ?? 0,
@@ -6108,7 +6139,7 @@ function createQuestionTool(ctx) {
6108
6139
  options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
6109
6140
  }),
6110
6141
  execute: async ({ question, options }, { toolCallId }) => {
6111
- return new Promise((resolve13, reject) => {
6142
+ return new Promise((resolve14, reject) => {
6112
6143
  const timeout = setTimeout(() => {
6113
6144
  ctx.pendingQuestions.delete(toolCallId);
6114
6145
  reject(new Error("Question timed out"));
@@ -6116,7 +6147,7 @@ function createQuestionTool(ctx) {
6116
6147
  ctx.pendingQuestions.set(toolCallId, {
6117
6148
  resolve: (answer) => {
6118
6149
  clearTimeout(timeout);
6119
- resolve13(answer);
6150
+ resolve14(answer);
6120
6151
  },
6121
6152
  reject: (err) => {
6122
6153
  clearTimeout(timeout);
@@ -6273,17 +6304,17 @@ async function buildRouter4(deps) {
6273
6304
  }
6274
6305
 
6275
6306
  // opencode/ws.ts
6276
- var clients2 = /* @__PURE__ */ new WeakMap();
6307
+ var clients = /* @__PURE__ */ new WeakMap();
6277
6308
  function createWSHandler2(deps) {
6278
6309
  const { sql: sql2, model, workspace, systemPrompt, skills, skillsRegistry, permissions, pendingQuestions } = deps;
6279
6310
  return {
6280
6311
  open(ws, ctx) {
6281
6312
  const userId = ctx.user?.id ?? 0;
6282
6313
  const mountPath = ctx.mountPath ?? "";
6283
- clients2.set(ws, { userId, mountPath });
6314
+ clients.set(ws, { userId, mountPath });
6284
6315
  },
6285
6316
  async message(ws, ctx, data) {
6286
- const client = clients2.get(ws);
6317
+ const client = clients.get(ws);
6287
6318
  if (!client) return;
6288
6319
  let msg;
6289
6320
  try {
@@ -6380,17 +6411,17 @@ function createWSHandler2(deps) {
6380
6411
  }
6381
6412
  },
6382
6413
  close(ws) {
6383
- const client = clients2.get(ws);
6414
+ const client = clients.get(ws);
6384
6415
  if (client) {
6385
6416
  client.abortController?.abort();
6386
- clients2.delete(ws);
6417
+ clients.delete(ws);
6387
6418
  }
6388
6419
  },
6389
6420
  error(ws, _ctx, _err) {
6390
- const client = clients2.get(ws);
6421
+ const client = clients.get(ws);
6391
6422
  if (client) {
6392
6423
  client.abortController?.abort();
6393
- clients2.delete(ws);
6424
+ clients.delete(ws);
6394
6425
  }
6395
6426
  }
6396
6427
  };
@@ -6836,23 +6867,33 @@ function preferences(options) {
6836
6867
  const cached = cache2.get(locale);
6837
6868
  if (cached) return cached;
6838
6869
  const filePath = join6(dir, `${locale}.json`);
6839
- if (!existsSync5(filePath)) return {};
6840
- try {
6841
- const content = await readFile2(filePath, "utf-8");
6842
- const data = JSON.parse(content);
6843
- cache2.set(locale, data);
6844
- return data;
6845
- } catch {
6846
- return {};
6870
+ if (existsSync5(filePath)) {
6871
+ try {
6872
+ const content = await readFile2(filePath, "utf-8");
6873
+ const data = JSON.parse(content);
6874
+ cache2.set(locale, data);
6875
+ return data;
6876
+ } catch {
6877
+ return {};
6878
+ }
6879
+ }
6880
+ const short = locale.split("-")[0];
6881
+ if (short !== locale) {
6882
+ const fallback = cache2.get(short) || await load(short);
6883
+ if (fallback && Object.keys(fallback).length > 0) {
6884
+ cache2.set(locale, fallback);
6885
+ return fallback;
6886
+ }
6847
6887
  }
6888
+ return {};
6848
6889
  }
6849
6890
  return async (req, ctx, next) => {
6850
6891
  const url = new URL(req.url);
6851
- const langMatch = url.pathname.match(/^\/__lang\/(\w+)$/);
6892
+ const langMatch = url.pathname.match(/^\/__lang\/([\w-]+)$/);
6852
6893
  if (langMatch && req.method === "GET") {
6853
6894
  return handlePrefSwitch(req, langMatch[1], localeOpts.cookie, load);
6854
6895
  }
6855
- const themeMatch = url.pathname.match(/^\/__theme\/(\w+)$/);
6896
+ const themeMatch = url.pathname.match(/^\/__theme\/([\w-]+)$/);
6856
6897
  if (themeMatch && req.method === "GET") {
6857
6898
  return handlePrefSwitch(req, themeMatch[1], themeOpts.cookie, load);
6858
6899
  }
@@ -6899,7 +6940,7 @@ function detectLocale(req, opts) {
6899
6940
  if (fromCookie) return fromCookie;
6900
6941
  }
6901
6942
  if (opts.fromAcceptLanguage) {
6902
- const fromHeader = req.headers.get("Accept-Language")?.split(",")[0]?.split("-")[0];
6943
+ const fromHeader = req.headers.get("Accept-Language")?.split(",")[0]?.trim();
6903
6944
  if (fromHeader) return fromHeader;
6904
6945
  }
6905
6946
  return opts.default;
@@ -7862,12 +7903,12 @@ function iii(opts = {}) {
7862
7903
  const handler = async (payload) => {
7863
7904
  if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
7864
7905
  const invocationId = crypto6.randomUUID();
7865
- return new Promise((resolve13, reject) => {
7906
+ return new Promise((resolve14, reject) => {
7866
7907
  const timer = setTimeout(() => {
7867
7908
  pending.delete(invocationId);
7868
7909
  reject(new Error(`Invocation timed out for "${id3}"`));
7869
7910
  }, 3e4);
7870
- pending.set(invocationId, { resolve: resolve13, reject, timer });
7911
+ pending.set(invocationId, { resolve: resolve14, reject, timer });
7871
7912
  worker.ws.send(JSON.stringify({
7872
7913
  type: "invoke",
7873
7914
  invocation_id: invocationId,
@@ -8108,8 +8149,8 @@ function registerWorker(url) {
8108
8149
  function connect() {
8109
8150
  if (intentionalClose) return;
8110
8151
  ws = new WebSocket(url);
8111
- ready = new Promise((resolve13) => {
8112
- resolveReady = resolve13;
8152
+ ready = new Promise((resolve14) => {
8153
+ resolveReady = resolve14;
8113
8154
  });
8114
8155
  ws.onopen = () => {
8115
8156
  reconnectAttempt = 0;
@@ -8229,13 +8270,13 @@ function registerWorker(url) {
8229
8270
  }
8230
8271
  return Promise.resolve(fn(request.payload, ctx));
8231
8272
  }
8232
- return new Promise((resolve13, reject) => {
8273
+ return new Promise((resolve14, reject) => {
8233
8274
  const invocationId = genId();
8234
8275
  const timer = setTimeout(() => {
8235
8276
  pendingInvocations.delete(invocationId);
8236
8277
  reject(new Error(`Invocation timed out for "${request.function_id}"`));
8237
8278
  }, request.timeout_ms || 3e4);
8238
- pendingInvocations.set(invocationId, { resolve: resolve13, reject, timer });
8279
+ pendingInvocations.set(invocationId, { resolve: resolve14, reject, timer });
8239
8280
  send({
8240
8281
  type: "invoke",
8241
8282
  invocation_id: invocationId,
@@ -8256,6 +8297,125 @@ function registerWorker(url) {
8256
8297
  };
8257
8298
  }
8258
8299
 
8300
+ // live.ts
8301
+ import chokidar from "chokidar";
8302
+ import { existsSync as existsSync6 } from "node:fs";
8303
+ import { join as join7, resolve as resolve13 } from "node:path";
8304
+ var clients2 = /* @__PURE__ */ new Set();
8305
+ var hotBundleCache = /* @__PURE__ */ new Map();
8306
+ var hotKeys = [];
8307
+ var MAX_HOT = 10;
8308
+ function setHot(hash, code) {
8309
+ if (!hotBundleCache.has(hash)) {
8310
+ hotKeys.push(hash);
8311
+ if (hotKeys.length > MAX_HOT) {
8312
+ const old = hotKeys.shift();
8313
+ hotBundleCache.delete(old);
8314
+ }
8315
+ }
8316
+ hotBundleCache.set(hash, code);
8317
+ }
8318
+ function broadcastReload() {
8319
+ for (const ws of clients2) {
8320
+ try {
8321
+ ws.send("reload");
8322
+ } catch {
8323
+ clients2.delete(ws);
8324
+ }
8325
+ }
8326
+ }
8327
+ function broadcastCss(css) {
8328
+ const msg = JSON.stringify({ type: "css", css });
8329
+ for (const ws of clients2) {
8330
+ try {
8331
+ ws.send(msg);
8332
+ } catch {
8333
+ clients2.delete(ws);
8334
+ }
8335
+ }
8336
+ }
8337
+ function liveReload(opts) {
8338
+ const r = new Router();
8339
+ const entryPath = (() => {
8340
+ for (const dir of opts.dirs) {
8341
+ const p = join7(resolve13(dir), "page.tsx");
8342
+ if (existsSync6(p)) return p;
8343
+ }
8344
+ return "";
8345
+ })();
8346
+ r.get("/__wfw/v/bundle", async (req, ctx) => {
8347
+ const code = await compileVendorBundle();
8348
+ return new Response(code, {
8349
+ headers: { "content-type": "application/javascript; charset=utf-8" }
8350
+ });
8351
+ });
8352
+ r.get("/__wfw/h/:hash", async (req, ctx) => {
8353
+ const hash = ctx.params.hash.replace(/\.js$/i, "");
8354
+ const code = hotBundleCache.get(hash);
8355
+ if (!code) return new Response("", { status: 404 });
8356
+ return new Response(code, {
8357
+ headers: { "content-type": "application/javascript; charset=utf-8" }
8358
+ });
8359
+ });
8360
+ r.ws("/__weifuwu/livereload", {
8361
+ open(ws) {
8362
+ clients2.add(ws);
8363
+ ws.on("close", () => clients2.delete(ws));
8364
+ ws.on("error", () => clients2.delete(ws));
8365
+ }
8366
+ });
8367
+ const watcher = chokidar.watch(opts.dirs, {
8368
+ ignored: /(^|[/\\])\.|node_modules|[/\\]\.weifuwu[/\\]/,
8369
+ ignoreInitial: true
8370
+ });
8371
+ watcher.on("change", async (filePath) => {
8372
+ if (/\.tsx?$/i.test(filePath)) {
8373
+ clearCompileCache();
8374
+ markClientBundleDirty();
8375
+ try {
8376
+ const target = entryPath || filePath;
8377
+ await compileTsxDev(target);
8378
+ const { hash, code } = await compileHotComponent(target);
8379
+ setHot(hash, code);
8380
+ let css;
8381
+ for (const dir of opts.dirs) {
8382
+ const cssPath = join7(resolve13(dir), "app.css");
8383
+ if (existsSync6(cssPath)) {
8384
+ css = await compileTailwindCss(cssPath, resolve13(dir));
8385
+ }
8386
+ }
8387
+ const entry = entryPath ? id(entryPath) : "";
8388
+ const msg = { type: "component", hash, entry };
8389
+ if (css) msg.css = css;
8390
+ const str = JSON.stringify(msg);
8391
+ for (const ws of clients2) {
8392
+ try {
8393
+ ws.send(str);
8394
+ } catch {
8395
+ clients2.delete(ws);
8396
+ }
8397
+ }
8398
+ } catch (e) {
8399
+ console.error("live reload failed, fallback to full reload:", e);
8400
+ broadcastReload();
8401
+ }
8402
+ } else if (/\.css$/i.test(filePath)) {
8403
+ for (const dir of opts.dirs) {
8404
+ const cssPath = join7(resolve13(dir), "app.css");
8405
+ if (existsSync6(cssPath)) {
8406
+ const css = await compileTailwindCss(cssPath, resolve13(dir));
8407
+ if (css) broadcastCss(css);
8408
+ }
8409
+ }
8410
+ }
8411
+ });
8412
+ r.close = () => {
8413
+ watcher.close();
8414
+ clients2.clear();
8415
+ };
8416
+ return r;
8417
+ }
8418
+
8259
8419
  // not-found.ts
8260
8420
  function notFound(path2) {
8261
8421
  return async (req, ctx) => {
package/dist/react.js CHANGED
@@ -331,7 +331,7 @@ async function prefetchPage(href) {
331
331
  }
332
332
 
333
333
  // tsx-context.ts
334
- import { createContext } from "react";
334
+ import { useSyncExternalStore, createContext } from "react";
335
335
  var DEFAULT_CTX = { params: {}, query: {}, parsed: {}, prefs: {}, loaderData: {}, env: {}, user: {} };
336
336
  var KEY = "__WEIFUWU_CTX_STORE";
337
337
  function getStore() {
@@ -350,22 +350,29 @@ function getStore() {
350
350
  return s;
351
351
  }
352
352
  var store = getStore();
353
+ var subscribe = (cb) => {
354
+ store._listeners.add(cb);
355
+ return () => {
356
+ store._listeners.delete(cb);
357
+ };
358
+ };
359
+ var getSnapshot = () => store._snapshot;
353
360
  function setCtx(value) {
354
361
  store._ctx = { ...store._ctx, ...value };
355
362
  store._snapshot = { params: store._ctx.params, query: store._ctx.query, user: store._ctx.user, parsed: store._ctx.parsed, prefs: store._ctx.prefs, env: store._ctx.env };
356
363
  store._listeners.forEach((fn) => fn());
357
364
  }
358
365
  function useCtx() {
366
+ if (typeof window !== "undefined") {
367
+ const snapshot = useSyncExternalStore(subscribe, getSnapshot);
368
+ return { ...snapshot, ...window.__WEIFUWU_CTX };
369
+ }
359
370
  const alsStore = store._alsGetStore?.();
360
- const base = alsStore ?? store._ctx;
361
- const data = typeof window !== "undefined" ? window.__WEIFUWU_CTX : null;
362
- return { ...base, ...data };
371
+ return alsStore ?? store._ctx;
363
372
  }
364
373
  function useLoaderData() {
365
- const alsStore = store._alsGetStore?.();
366
- const base = alsStore ?? store._ctx;
367
- const data = typeof window !== "undefined" ? window.__WEIFUWU_CTX : null;
368
- return { ...base, ...data }.loaderData;
374
+ const ctx = useCtx();
375
+ return ctx.loaderData;
369
376
  }
370
377
  var TsxContext = createContext(DEFAULT_CTX);
371
378
 
@@ -386,19 +393,19 @@ function createStore(initial) {
386
393
  state = { ...state, ...next };
387
394
  listeners.forEach((fn) => fn());
388
395
  };
389
- const subscribe = (listener) => {
396
+ const subscribe2 = (listener) => {
390
397
  listeners.add(listener);
391
398
  return () => {
392
399
  listeners.delete(listener);
393
400
  };
394
401
  };
395
402
  const useStore = ((selector) => useSyncExternalStore2(
396
- subscribe,
403
+ subscribe2,
397
404
  () => selector ? selector(state) : state
398
405
  ));
399
406
  useStore.getState = getState;
400
407
  useStore.setState = setState;
401
- useStore.subscribe = subscribe;
408
+ useStore.subscribe = subscribe2;
402
409
  return useStore;
403
410
  }
404
411
  var dataCache = /* @__PURE__ */ new Map();
@@ -478,7 +485,7 @@ function notifyQueryListeners() {
478
485
  window.dispatchEvent(new PopStateEvent("popstate"));
479
486
  }
480
487
  function useQueryState(key, defaultValue = "") {
481
- function getSnapshot() {
488
+ function getSnapshot2() {
482
489
  if (typeof window === "undefined") return defaultValue;
483
490
  const params = new URLSearchParams(window.location.search);
484
491
  return params.get(key) ?? defaultValue;
@@ -490,12 +497,12 @@ function useQueryState(key, defaultValue = "") {
490
497
  window.addEventListener("popstate", cb);
491
498
  return () => window.removeEventListener("popstate", cb);
492
499
  },
493
- getSnapshot,
500
+ getSnapshot2,
494
501
  () => defaultValue
495
502
  );
496
503
  const setValue = useCallback4((val) => {
497
504
  if (typeof window === "undefined") return;
498
- const resolved = typeof val === "function" ? val(getSnapshot()) : val;
505
+ const resolved = typeof val === "function" ? val(getSnapshot2()) : val;
499
506
  const url = new URL(window.location.href);
500
507
  if (resolved === defaultValue || resolved === "") {
501
508
  url.searchParams.delete(key);
@@ -510,7 +517,7 @@ function useQueryState(key, defaultValue = "") {
510
517
 
511
518
  // client-locale.ts
512
519
  function buildT() {
513
- const messages = typeof window !== "undefined" ? window.__LOCALE_DATA__ : globalThis.__LOCALE_DATA__;
520
+ const messages = globalThis.__LOCALE_DATA__ || (typeof window !== "undefined" ? window.__LOCALE_DATA__ : null);
514
521
  if (!messages) return (key, _p, fb) => fb ?? key;
515
522
  return (key, params, fallback) => {
516
523
  const msg = key.split(".").reduce((o, k) => o?.[k], messages);
@@ -522,7 +529,7 @@ function buildT() {
522
529
  };
523
530
  }
524
531
  addInterceptor(async (url) => {
525
- const m = url.pathname.match(/^\/__lang\/(\w+)$/);
532
+ const m = url.pathname.match(/^\/__lang\/([\w-]+)$/);
526
533
  if (!m) return false;
527
534
  try {
528
535
  const res = await fetch(url.pathname, {
@@ -575,7 +582,7 @@ function applyTheme(theme) {
575
582
  }
576
583
  }
577
584
  addInterceptor(async (url) => {
578
- const m = url.pathname.match(/^\/__theme\/(\w+)$/);
585
+ const m = url.pathname.match(/^\/__theme\/([\w-]+)$/);
579
586
  if (!m) return false;
580
587
  try {
581
588
  const res = await fetch(url.pathname, {
package/dist/ssr.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { Router } from './router.ts';
2
+ export declare function markClientBundleDirty(): void;
2
3
  export declare function ssr(path: string): Router;
@@ -1,3 +1,4 @@
1
1
  import { Router } from './router.ts';
2
2
  export declare function addTailwindSource(dir: string): void;
3
3
  export declare function tailwind(dir: string): Router;
4
+ export declare function compileTailwindCss(cssPath: string, cssDir: string): Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weifuwu",
3
- "version": "0.18.17",
3
+ "version": "0.19.0",
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",