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 +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 -130
- 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,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 (!
|
|
5571
|
-
|
|
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 (!
|
|
5582
|
-
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6314
|
+
clients.set(ws, { userId, mountPath });
|
|
6284
6315
|
},
|
|
6285
6316
|
async message(ws, ctx, data) {
|
|
6286
|
-
const client =
|
|
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 =
|
|
6414
|
+
const client = clients.get(ws);
|
|
6384
6415
|
if (client) {
|
|
6385
6416
|
client.abortController?.abort();
|
|
6386
|
-
|
|
6417
|
+
clients.delete(ws);
|
|
6387
6418
|
}
|
|
6388
6419
|
},
|
|
6389
6420
|
error(ws, _ctx, _err) {
|
|
6390
|
-
const client =
|
|
6421
|
+
const client = clients.get(ws);
|
|
6391
6422
|
if (client) {
|
|
6392
6423
|
client.abortController?.abort();
|
|
6393
|
-
|
|
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 (
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
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]?.
|
|
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((
|
|
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:
|
|
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((
|
|
8112
|
-
resolveReady =
|
|
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((
|
|
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:
|
|
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
|
-
|
|
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