weifuwu 0.18.18 → 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 +28 -13
- package/cli.ts +18 -0
- package/dist/cli.js +6 -0
- package/dist/compile.d.ts +8 -0
- package/dist/index.js +290 -131
- package/dist/react.js +24 -17
- package/dist/ssr.d.ts +1 -0
- package/dist/tailwind.d.ts +1 -0
- package/package.json +1 -1
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
|
|
570
|
+
## React SSR (weifuwu)
|
|
572
571
|
|
|
573
|
-
Import from `'weifuwu
|
|
572
|
+
Import from `'weifuwu'` (no separate ssr entry):
|
|
574
573
|
|
|
575
574
|
```ts
|
|
576
|
-
import { ssr, layout, liveReload, errorBoundary, notFound, tailwind } from 'weifuwu
|
|
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
|
|
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
|
|
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(
|
|
670
|
+
### tailwind(cssPath) [α]
|
|
656
671
|
|
|
657
|
-
Compiles Tailwind CSS v4 via `@tailwindcss/postcss` and serves it at `/__wfw/style.css`.
|
|
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('./
|
|
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
|
|
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,
|
|
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((
|
|
379
|
+
return await new Promise((resolve14) => {
|
|
380
380
|
try {
|
|
381
381
|
upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
|
|
382
|
-
|
|
382
|
+
resolve14(new Response(null, { status: 101 }));
|
|
383
383
|
} catch {
|
|
384
384
|
socket.destroy();
|
|
385
|
-
|
|
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
|
|
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:
|
|
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((
|
|
4699
|
+
return new Promise((resolve14) => {
|
|
4700
4700
|
const timer = setTimeout(() => {
|
|
4701
4701
|
mp.child.kill("SIGKILL");
|
|
4702
|
-
|
|
4702
|
+
resolve14();
|
|
4703
4703
|
}, timeout);
|
|
4704
4704
|
mp.child.on("exit", () => {
|
|
4705
4705
|
clearTimeout(timer);
|
|
4706
|
-
|
|
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
|
|
5382
|
-
|
|
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
|
-
|
|
5398
|
-
`import{createElement
|
|
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
|
-
|
|
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(
|
|
5493
|
+
isDev ? `createElement(_P,null))` : `createElement(P,null))`,
|
|
5411
5494
|
`}`,
|
|
5412
|
-
`window.__WFW_ROOT=hydrateRoot(c,createElement(App))
|
|
5413
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
5467
|
-
{
|
|
5551
|
+
"div",
|
|
5552
|
+
{ id: "__weifuwu_root" },
|
|
5468
5553
|
createElement(
|
|
5469
|
-
|
|
5470
|
-
{
|
|
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 (!
|
|
5578
|
+
if (!getBundle(bundleKey)) {
|
|
5494
5579
|
const buf = await buildClientBundle(path2, layoutPaths);
|
|
5495
|
-
if (buf)
|
|
5580
|
+
if (buf) setBundle(bundleKey, buf);
|
|
5496
5581
|
}
|
|
5497
|
-
if (
|
|
5582
|
+
if (getBundle(bundleKey)) {
|
|
5498
5583
|
bundle = { url: bundleKey };
|
|
5499
5584
|
}
|
|
5500
5585
|
const { renderToReadableStream } = await import("react-dom/server");
|
|
@@ -5515,78 +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 (e) {
|
|
5550
|
-
console.error("live reload compile failed:", e);
|
|
5551
|
-
}
|
|
5552
|
-
broadcastReload();
|
|
5553
|
-
});
|
|
5554
|
-
r.close = () => {
|
|
5555
|
-
watcher.close();
|
|
5556
|
-
clients.clear();
|
|
5557
|
-
};
|
|
5558
|
-
return r;
|
|
5559
|
-
}
|
|
5560
|
-
|
|
5561
|
-
// tailwind.ts
|
|
5562
|
-
var isDev2 = process.env.NODE_ENV !== "production";
|
|
5563
5603
|
var extraSources = /* @__PURE__ */ new Set();
|
|
5604
|
+
var cssCache = /* @__PURE__ */ new Map();
|
|
5564
5605
|
function tailwind(dir) {
|
|
5565
5606
|
const cssDir = resolve5(dir);
|
|
5566
5607
|
const cssPath = join3(cssDir, "app.css");
|
|
5567
|
-
let compiledCss = "";
|
|
5568
|
-
let twWatcher = null;
|
|
5569
5608
|
const r = new Router();
|
|
5570
5609
|
r.use(async (req, ctx, next) => {
|
|
5571
|
-
if (!
|
|
5572
|
-
|
|
5573
|
-
if (isDev2 && !twWatcher) {
|
|
5574
|
-
twWatcher = watchFile(cssPath, () => {
|
|
5575
|
-
compiledCss = "";
|
|
5576
|
-
broadcastReload();
|
|
5577
|
-
});
|
|
5610
|
+
if (!cssCache.has(cssPath)) {
|
|
5611
|
+
cssCache.set(cssPath, await compileTailwindCss(cssPath, cssDir));
|
|
5578
5612
|
}
|
|
5613
|
+
ctx.compiledTailwindCss = cssCache.get(cssPath);
|
|
5579
5614
|
return next(req, ctx);
|
|
5580
5615
|
});
|
|
5581
5616
|
r.get("/__wfw/style.css", async (req, ctx) => {
|
|
5582
|
-
if (!
|
|
5583
|
-
|
|
5617
|
+
if (!cssCache.has(cssPath)) {
|
|
5618
|
+
cssCache.set(cssPath, await compileTailwindCss(cssPath, cssDir));
|
|
5619
|
+
}
|
|
5620
|
+
return new Response(cssCache.get(cssPath) || "", {
|
|
5584
5621
|
headers: { "content-type": "text/css; charset=utf-8" }
|
|
5585
5622
|
});
|
|
5586
5623
|
});
|
|
5587
5624
|
return r;
|
|
5588
5625
|
}
|
|
5589
|
-
async function
|
|
5626
|
+
async function compileTailwindCss(cssPath, cssDir) {
|
|
5590
5627
|
try {
|
|
5591
5628
|
if (!existsSync3(cssPath)) {
|
|
5592
5629
|
mkdirSync2(cssDir, { recursive: true });
|
|
@@ -5603,20 +5640,13 @@ ${src}`;
|
|
|
5603
5640
|
${src}`;
|
|
5604
5641
|
}
|
|
5605
5642
|
const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
|
|
5643
|
+
cssCache.set(cssPath, result.css);
|
|
5606
5644
|
return result.css;
|
|
5607
5645
|
} catch (err) {
|
|
5608
5646
|
console.warn("Tailwind CSS processing failed:", err.message);
|
|
5609
5647
|
return "";
|
|
5610
5648
|
}
|
|
5611
5649
|
}
|
|
5612
|
-
function watchFile(path2, onChange) {
|
|
5613
|
-
let watcher = null;
|
|
5614
|
-
import("chokidar").then((chokidar2) => {
|
|
5615
|
-
watcher = chokidar2.default.watch(resolve5(path2), { persistent: false });
|
|
5616
|
-
watcher.on("change", onChange);
|
|
5617
|
-
});
|
|
5618
|
-
return watcher;
|
|
5619
|
-
}
|
|
5620
5650
|
|
|
5621
5651
|
// layout.ts
|
|
5622
5652
|
function layout(path2) {
|
|
@@ -5865,10 +5895,10 @@ function createBashTool(ctx) {
|
|
|
5865
5895
|
return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
|
|
5866
5896
|
}
|
|
5867
5897
|
const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
|
|
5868
|
-
return new Promise((
|
|
5898
|
+
return new Promise((resolve14) => {
|
|
5869
5899
|
const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
5870
5900
|
const truncated = stdout.length > 1e6 || stderr.length > 1e6;
|
|
5871
|
-
|
|
5901
|
+
resolve14({
|
|
5872
5902
|
stdout: stdout.slice(0, 1e6),
|
|
5873
5903
|
stderr: stderr.slice(0, 1e6),
|
|
5874
5904
|
exitCode: error?.code ?? 0,
|
|
@@ -6109,7 +6139,7 @@ function createQuestionTool(ctx) {
|
|
|
6109
6139
|
options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
|
|
6110
6140
|
}),
|
|
6111
6141
|
execute: async ({ question, options }, { toolCallId }) => {
|
|
6112
|
-
return new Promise((
|
|
6142
|
+
return new Promise((resolve14, reject) => {
|
|
6113
6143
|
const timeout = setTimeout(() => {
|
|
6114
6144
|
ctx.pendingQuestions.delete(toolCallId);
|
|
6115
6145
|
reject(new Error("Question timed out"));
|
|
@@ -6117,7 +6147,7 @@ function createQuestionTool(ctx) {
|
|
|
6117
6147
|
ctx.pendingQuestions.set(toolCallId, {
|
|
6118
6148
|
resolve: (answer) => {
|
|
6119
6149
|
clearTimeout(timeout);
|
|
6120
|
-
|
|
6150
|
+
resolve14(answer);
|
|
6121
6151
|
},
|
|
6122
6152
|
reject: (err) => {
|
|
6123
6153
|
clearTimeout(timeout);
|
|
@@ -6274,17 +6304,17 @@ async function buildRouter4(deps) {
|
|
|
6274
6304
|
}
|
|
6275
6305
|
|
|
6276
6306
|
// opencode/ws.ts
|
|
6277
|
-
var
|
|
6307
|
+
var clients = /* @__PURE__ */ new WeakMap();
|
|
6278
6308
|
function createWSHandler2(deps) {
|
|
6279
6309
|
const { sql: sql2, model, workspace, systemPrompt, skills, skillsRegistry, permissions, pendingQuestions } = deps;
|
|
6280
6310
|
return {
|
|
6281
6311
|
open(ws, ctx) {
|
|
6282
6312
|
const userId = ctx.user?.id ?? 0;
|
|
6283
6313
|
const mountPath = ctx.mountPath ?? "";
|
|
6284
|
-
|
|
6314
|
+
clients.set(ws, { userId, mountPath });
|
|
6285
6315
|
},
|
|
6286
6316
|
async message(ws, ctx, data) {
|
|
6287
|
-
const client =
|
|
6317
|
+
const client = clients.get(ws);
|
|
6288
6318
|
if (!client) return;
|
|
6289
6319
|
let msg;
|
|
6290
6320
|
try {
|
|
@@ -6381,17 +6411,17 @@ function createWSHandler2(deps) {
|
|
|
6381
6411
|
}
|
|
6382
6412
|
},
|
|
6383
6413
|
close(ws) {
|
|
6384
|
-
const client =
|
|
6414
|
+
const client = clients.get(ws);
|
|
6385
6415
|
if (client) {
|
|
6386
6416
|
client.abortController?.abort();
|
|
6387
|
-
|
|
6417
|
+
clients.delete(ws);
|
|
6388
6418
|
}
|
|
6389
6419
|
},
|
|
6390
6420
|
error(ws, _ctx, _err) {
|
|
6391
|
-
const client =
|
|
6421
|
+
const client = clients.get(ws);
|
|
6392
6422
|
if (client) {
|
|
6393
6423
|
client.abortController?.abort();
|
|
6394
|
-
|
|
6424
|
+
clients.delete(ws);
|
|
6395
6425
|
}
|
|
6396
6426
|
}
|
|
6397
6427
|
};
|
|
@@ -6837,23 +6867,33 @@ function preferences(options) {
|
|
|
6837
6867
|
const cached = cache2.get(locale);
|
|
6838
6868
|
if (cached) return cached;
|
|
6839
6869
|
const filePath = join6(dir, `${locale}.json`);
|
|
6840
|
-
if (
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
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
|
+
}
|
|
6848
6887
|
}
|
|
6888
|
+
return {};
|
|
6849
6889
|
}
|
|
6850
6890
|
return async (req, ctx, next) => {
|
|
6851
6891
|
const url = new URL(req.url);
|
|
6852
|
-
const langMatch = url.pathname.match(/^\/__lang\/(\w+)$/);
|
|
6892
|
+
const langMatch = url.pathname.match(/^\/__lang\/([\w-]+)$/);
|
|
6853
6893
|
if (langMatch && req.method === "GET") {
|
|
6854
6894
|
return handlePrefSwitch(req, langMatch[1], localeOpts.cookie, load);
|
|
6855
6895
|
}
|
|
6856
|
-
const themeMatch = url.pathname.match(/^\/__theme\/(\w+)$/);
|
|
6896
|
+
const themeMatch = url.pathname.match(/^\/__theme\/([\w-]+)$/);
|
|
6857
6897
|
if (themeMatch && req.method === "GET") {
|
|
6858
6898
|
return handlePrefSwitch(req, themeMatch[1], themeOpts.cookie, load);
|
|
6859
6899
|
}
|
|
@@ -6900,7 +6940,7 @@ function detectLocale(req, opts) {
|
|
|
6900
6940
|
if (fromCookie) return fromCookie;
|
|
6901
6941
|
}
|
|
6902
6942
|
if (opts.fromAcceptLanguage) {
|
|
6903
|
-
const fromHeader = req.headers.get("Accept-Language")?.split(",")[0]?.
|
|
6943
|
+
const fromHeader = req.headers.get("Accept-Language")?.split(",")[0]?.trim();
|
|
6904
6944
|
if (fromHeader) return fromHeader;
|
|
6905
6945
|
}
|
|
6906
6946
|
return opts.default;
|
|
@@ -7863,12 +7903,12 @@ function iii(opts = {}) {
|
|
|
7863
7903
|
const handler = async (payload) => {
|
|
7864
7904
|
if (!worker.ws) throw new Error(`Worker "${worker.name}" disconnected`);
|
|
7865
7905
|
const invocationId = crypto6.randomUUID();
|
|
7866
|
-
return new Promise((
|
|
7906
|
+
return new Promise((resolve14, reject) => {
|
|
7867
7907
|
const timer = setTimeout(() => {
|
|
7868
7908
|
pending.delete(invocationId);
|
|
7869
7909
|
reject(new Error(`Invocation timed out for "${id3}"`));
|
|
7870
7910
|
}, 3e4);
|
|
7871
|
-
pending.set(invocationId, { resolve:
|
|
7911
|
+
pending.set(invocationId, { resolve: resolve14, reject, timer });
|
|
7872
7912
|
worker.ws.send(JSON.stringify({
|
|
7873
7913
|
type: "invoke",
|
|
7874
7914
|
invocation_id: invocationId,
|
|
@@ -8109,8 +8149,8 @@ function registerWorker(url) {
|
|
|
8109
8149
|
function connect() {
|
|
8110
8150
|
if (intentionalClose) return;
|
|
8111
8151
|
ws = new WebSocket(url);
|
|
8112
|
-
ready = new Promise((
|
|
8113
|
-
resolveReady =
|
|
8152
|
+
ready = new Promise((resolve14) => {
|
|
8153
|
+
resolveReady = resolve14;
|
|
8114
8154
|
});
|
|
8115
8155
|
ws.onopen = () => {
|
|
8116
8156
|
reconnectAttempt = 0;
|
|
@@ -8230,13 +8270,13 @@ function registerWorker(url) {
|
|
|
8230
8270
|
}
|
|
8231
8271
|
return Promise.resolve(fn(request.payload, ctx));
|
|
8232
8272
|
}
|
|
8233
|
-
return new Promise((
|
|
8273
|
+
return new Promise((resolve14, reject) => {
|
|
8234
8274
|
const invocationId = genId();
|
|
8235
8275
|
const timer = setTimeout(() => {
|
|
8236
8276
|
pendingInvocations.delete(invocationId);
|
|
8237
8277
|
reject(new Error(`Invocation timed out for "${request.function_id}"`));
|
|
8238
8278
|
}, request.timeout_ms || 3e4);
|
|
8239
|
-
pendingInvocations.set(invocationId, { resolve:
|
|
8279
|
+
pendingInvocations.set(invocationId, { resolve: resolve14, reject, timer });
|
|
8240
8280
|
send({
|
|
8241
8281
|
type: "invoke",
|
|
8242
8282
|
invocation_id: invocationId,
|
|
@@ -8257,6 +8297,125 @@ function registerWorker(url) {
|
|
|
8257
8297
|
};
|
|
8258
8298
|
}
|
|
8259
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
|
+
|
|
8260
8419
|
// not-found.ts
|
|
8261
8420
|
function notFound(path2) {
|
|
8262
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
|
-
|
|
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
|
|
366
|
-
|
|
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
|
|
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
|
-
|
|
403
|
+
subscribe2,
|
|
397
404
|
() => selector ? selector(state) : state
|
|
398
405
|
));
|
|
399
406
|
useStore.getState = getState;
|
|
400
407
|
useStore.setState = setState;
|
|
401
|
-
useStore.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
|
|
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
|
-
|
|
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(
|
|
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__ :
|
|
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
package/dist/tailwind.d.ts
CHANGED