weifuwu 0.31.1 → 0.32.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 +238 -120
- package/dist/ai/agent.d.ts +127 -0
- package/dist/ai/index.d.ts +26 -0
- package/dist/ai/types.d.ts +59 -0
- package/dist/core/router.d.ts +11 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +1186 -315
- package/dist/middleware/auth.d.ts +68 -0
- package/dist/middleware/esbuild-dev.d.ts +69 -0
- package/dist/middleware/esbuild-dev.js +335 -0
- package/dist/middleware/sandbox.d.ts +52 -0
- package/dist/middleware/tailwind-dev.d.ts +43 -0
- package/dist/middleware/tailwind-dev.js +199 -0
- package/dist/react/client.d.ts +64 -27
- package/dist/react/client.js +130 -248
- package/dist/react/compile.d.ts +17 -0
- package/dist/react/error-boundary.d.ts +18 -19
- package/dist/react/index.d.ts +80 -16
- package/dist/react/index.js +835 -267
- package/dist/react/types.d.ts +134 -34
- package/dist/types.d.ts +7 -0
- package/package.json +17 -6
- package/dist/react/navigation.d.ts +0 -63
- package/dist/react/navigation.js +0 -154
- package/dist/react/render.d.ts +0 -8
- package/dist/react/route-utils.d.ts +0 -31
package/dist/react/index.js
CHANGED
|
@@ -1,12 +1,116 @@
|
|
|
1
1
|
// src/react/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import { createElement as createElement2 } from "react";
|
|
3
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
3
4
|
|
|
4
|
-
// src/react/
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "
|
|
9
|
-
|
|
5
|
+
// src/react/compile.ts
|
|
6
|
+
import { mkdir, writeFile, readFile } from "node:fs/promises";
|
|
7
|
+
import { resolve, join, dirname } from "node:path";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
var EXTERNAL_PKGS = [
|
|
11
|
+
"react",
|
|
12
|
+
"react/jsx-runtime",
|
|
13
|
+
"react-dom",
|
|
14
|
+
"react-dom/server",
|
|
15
|
+
"react-dom/client",
|
|
16
|
+
"weifuwu",
|
|
17
|
+
"weifuwu/react"
|
|
18
|
+
];
|
|
19
|
+
var FRAMEWORK_IMPORTS = [
|
|
20
|
+
"react",
|
|
21
|
+
"react/jsx-runtime",
|
|
22
|
+
"react-dom",
|
|
23
|
+
"react-dom/server",
|
|
24
|
+
"react-dom/client",
|
|
25
|
+
"weifuwu",
|
|
26
|
+
"weifuwu/react"
|
|
27
|
+
];
|
|
28
|
+
var memCache = /* @__PURE__ */ new Map();
|
|
29
|
+
function buildResolveMap() {
|
|
30
|
+
const map = {};
|
|
31
|
+
for (const spec of FRAMEWORK_IMPORTS) {
|
|
32
|
+
try {
|
|
33
|
+
map[spec] = import.meta.resolve(spec);
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return map;
|
|
38
|
+
}
|
|
39
|
+
var _resolveMap = null;
|
|
40
|
+
function resolveMap() {
|
|
41
|
+
if (!_resolveMap) _resolveMap = buildResolveMap();
|
|
42
|
+
return _resolveMap;
|
|
43
|
+
}
|
|
44
|
+
function rewriteImports(code) {
|
|
45
|
+
const map = resolveMap();
|
|
46
|
+
let result = code;
|
|
47
|
+
for (const [spec, url] of Object.entries(map)) {
|
|
48
|
+
const quoted = JSON.stringify(spec);
|
|
49
|
+
const escaped = quoted.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
+
result = result.replace(new RegExp(`(from\\s+)${escaped}`, "g"), `$1${JSON.stringify(url)}`).replace(new RegExp(`(import\\s+)${escaped}(\\s*;)`, "g"), `$1${JSON.stringify(url)}$2`).replace(new RegExp(`(export\\s+.*?from\\s+)${escaped}`, "g"), `$1${JSON.stringify(url)}`);
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
var _cacheDir = null;
|
|
55
|
+
function setReactCacheDir(dir) {
|
|
56
|
+
_cacheDir = dir;
|
|
57
|
+
}
|
|
58
|
+
function getCacheDir() {
|
|
59
|
+
if (_cacheDir) return _cacheDir;
|
|
60
|
+
_cacheDir = join(process.cwd(), "node_modules", ".weifuwu", "react");
|
|
61
|
+
return _cacheDir;
|
|
62
|
+
}
|
|
63
|
+
async function loadTsxModule(entryPath) {
|
|
64
|
+
const abs = resolve(entryPath);
|
|
65
|
+
let source;
|
|
66
|
+
try {
|
|
67
|
+
source = await readFile(abs, "utf-8");
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error(`Cannot read file: ${entryPath}`);
|
|
70
|
+
}
|
|
71
|
+
const sourceHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
72
|
+
const memCached = memCache.get(abs);
|
|
73
|
+
if (memCached && memCached.sourceHash === sourceHash) {
|
|
74
|
+
return memCached.mod;
|
|
75
|
+
}
|
|
76
|
+
const tmpDir = getCacheDir();
|
|
77
|
+
const tmpFile = join(tmpDir, `${sourceHash}.mjs`);
|
|
78
|
+
if (existsSync(tmpFile)) {
|
|
79
|
+
try {
|
|
80
|
+
const mod2 = await import(tmpFile + "?" + sourceHash);
|
|
81
|
+
memCache.set(abs, { sourceHash, mod: mod2 });
|
|
82
|
+
return mod2;
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const esbuild = await import("esbuild");
|
|
87
|
+
const result = await esbuild.build({
|
|
88
|
+
entryPoints: [abs],
|
|
89
|
+
bundle: true,
|
|
90
|
+
write: false,
|
|
91
|
+
format: "esm",
|
|
92
|
+
platform: "node",
|
|
93
|
+
jsx: "automatic",
|
|
94
|
+
external: EXTERNAL_PKGS,
|
|
95
|
+
logLevel: "silent"
|
|
96
|
+
});
|
|
97
|
+
let code = result.outputFiles[0]?.text ?? "";
|
|
98
|
+
if (!code) throw new Error(`esbuild produced empty output for ${entryPath}`);
|
|
99
|
+
code = rewriteImports(code);
|
|
100
|
+
await mkdir(dirname(tmpFile), { recursive: true });
|
|
101
|
+
await writeFile(tmpFile, code);
|
|
102
|
+
const mod = await import(tmpFile + "?" + sourceHash);
|
|
103
|
+
memCache.set(abs, { sourceHash, mod });
|
|
104
|
+
return mod;
|
|
105
|
+
}
|
|
106
|
+
async function loadTsxComponent(entryPath) {
|
|
107
|
+
const mod = await loadTsxModule(entryPath);
|
|
108
|
+
if (mod.default && typeof mod.default === "function") return mod.default;
|
|
109
|
+
for (const [, val] of Object.entries(mod)) {
|
|
110
|
+
if (typeof val === "function") return val;
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`No component export found in ${entryPath}. Export a default or named component function.`);
|
|
113
|
+
}
|
|
10
114
|
|
|
11
115
|
// src/react/context.ts
|
|
12
116
|
import { createContext } from "react";
|
|
@@ -21,322 +125,786 @@ function getServerDataContext() {
|
|
|
21
125
|
}
|
|
22
126
|
var ServerDataContext = getServerDataContext();
|
|
23
127
|
|
|
24
|
-
// src/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function
|
|
28
|
-
return s.replace(/&/g, "&").replace(/"/g, """);
|
|
128
|
+
// src/middleware/tailwind-dev.ts
|
|
129
|
+
import { resolve as resolve2 } from "node:path";
|
|
130
|
+
import { stat, readFile as readFile2, glob } from "node:fs/promises";
|
|
131
|
+
function escapeHtml(s) {
|
|
132
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
29
133
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
134
|
+
function defaultErrorTemplate(errors) {
|
|
135
|
+
return `<!DOCTYPE html>
|
|
136
|
+
<html>
|
|
137
|
+
<head>
|
|
138
|
+
<meta charset="utf-8">
|
|
139
|
+
<title>Tailwind Build Error</title>
|
|
140
|
+
<style>
|
|
141
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
142
|
+
body { font-family: system-ui; background: #1a1a2e; color: #e0e0e0; padding: 2rem; }
|
|
143
|
+
.overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
144
|
+
.card { background: #16213e; border: 1px solid #06b6d4; border-radius: 12px; padding: 2rem; max-width: 800px; width: 100%; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
|
|
145
|
+
h1 { color: #06b6d4; font-size: 1.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
146
|
+
pre { background: #0f0f23; padding: 1.5rem; border-radius: 8px; overflow: auto; font-size: 0.85rem; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
|
|
147
|
+
</style>
|
|
148
|
+
</head>
|
|
149
|
+
<body>
|
|
150
|
+
<div class="overlay">
|
|
151
|
+
<div class="card">
|
|
152
|
+
<h1>\u{1F3A8} Tailwind CSS Build Error</h1>
|
|
153
|
+
<pre>${escapeHtml(errors)}</pre>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</body>
|
|
157
|
+
</html>`;
|
|
158
|
+
}
|
|
159
|
+
var TW_CANDIDATE_RE = /[a-z][\w-]*(?::[a-z][\w-]*)*(?:-\[[^\]]*\])*(?:\/[0-9]+)?/gi;
|
|
160
|
+
var CLASS_CONTEXT_RE = /(?:class(?:Name)?\s*[=:]\s*["'`]([^"'`]+)["'`]|["'`]([^"'`\n]{2,})["'`])/g;
|
|
161
|
+
async function extractCandidates(patterns, root) {
|
|
162
|
+
const seen = /* @__PURE__ */ new Set();
|
|
163
|
+
for (const pattern of patterns) {
|
|
164
|
+
const resolved = resolve2(root, pattern);
|
|
165
|
+
try {
|
|
166
|
+
for await (const file of glob(resolved)) {
|
|
167
|
+
try {
|
|
168
|
+
const content = await readFile2(file, "utf-8");
|
|
169
|
+
for (const m of content.matchAll(CLASS_CONTEXT_RE)) {
|
|
170
|
+
const str = m[1] ?? m[2];
|
|
171
|
+
if (!str) continue;
|
|
172
|
+
for (const c of str.matchAll(TW_CANDIDATE_RE)) {
|
|
173
|
+
seen.add(c[0]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
37
180
|
}
|
|
38
181
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
182
|
+
return [...seen];
|
|
183
|
+
}
|
|
184
|
+
async function collectFileMtimes(files) {
|
|
185
|
+
const map = /* @__PURE__ */ new Map();
|
|
186
|
+
await Promise.all(
|
|
187
|
+
files.map(async (f) => {
|
|
188
|
+
try {
|
|
189
|
+
const s = await stat(f);
|
|
190
|
+
map.set(f, s.mtimeMs);
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
return map;
|
|
196
|
+
}
|
|
197
|
+
function isCacheValid(cached) {
|
|
198
|
+
return Promise.all(
|
|
199
|
+
[...cached.files].map(async ([file, mtime]) => {
|
|
200
|
+
try {
|
|
201
|
+
const s = await stat(file);
|
|
202
|
+
return s.mtimeMs === mtime;
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
).then((results) => results.every(Boolean));
|
|
208
|
+
}
|
|
209
|
+
function tailwindDev(opts) {
|
|
210
|
+
const cacheMode = opts.cache ?? "memory";
|
|
211
|
+
const errorTemplate = opts.errorTemplate ?? defaultErrorTemplate;
|
|
212
|
+
const entries = Object.entries(opts.entries).map(([p, e]) => {
|
|
213
|
+
const path = p.startsWith("/") ? p : "/" + p;
|
|
214
|
+
const config = typeof e === "string" ? { entry: e } : e;
|
|
215
|
+
return { path, config };
|
|
216
|
+
});
|
|
217
|
+
const cacheStore = /* @__PURE__ */ new Map();
|
|
218
|
+
let compileFn = null;
|
|
219
|
+
let loadError = null;
|
|
220
|
+
async function getCompile() {
|
|
221
|
+
if (compileFn) return compileFn;
|
|
222
|
+
try {
|
|
223
|
+
const mod = await import("@tailwindcss/node");
|
|
224
|
+
compileFn = mod.compile;
|
|
225
|
+
return compileFn;
|
|
226
|
+
} catch (err) {
|
|
227
|
+
loadError = `@tailwindcss/node is not installed. Run: npm install -D tailwindcss @tailwindcss/node
|
|
228
|
+
|
|
229
|
+
${String(err)}`;
|
|
230
|
+
throw new Error(loadError, { cause: err });
|
|
43
231
|
}
|
|
44
232
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
233
|
+
async function compileCss(entry) {
|
|
234
|
+
const compile = await getCompile();
|
|
235
|
+
const entryAbs = resolve2(entry.entry);
|
|
236
|
+
const cssSource = await readFile2(entryAbs, "utf-8");
|
|
237
|
+
const baseDir = resolve2(entry.entry, "..");
|
|
238
|
+
const deps = [entryAbs];
|
|
239
|
+
const result = await compile(cssSource, {
|
|
240
|
+
base: baseDir,
|
|
241
|
+
onDependency: (p) => {
|
|
242
|
+
if (!deps.includes(p)) deps.push(p);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
const contentPatterns = entry.content ?? [];
|
|
246
|
+
for (const src of result.sources) {
|
|
247
|
+
if (!src.negated) {
|
|
248
|
+
contentPatterns.push(resolve2(src.base, src.pattern));
|
|
249
|
+
}
|
|
50
250
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
function injectHeadIntoHtml(html, headTags) {
|
|
55
|
-
if (!headTags) return html;
|
|
56
|
-
let result = html;
|
|
57
|
-
if (headTags.includes("<title>")) {
|
|
58
|
-
const titleMatch = headTags.match(/<title>[^<]*<\/title>/);
|
|
59
|
-
if (titleMatch) {
|
|
60
|
-
result = result.replace(/<title>[^<]*<\/title>/, titleMatch[0]);
|
|
61
|
-
headTags = headTags.replace(titleMatch[0], "");
|
|
251
|
+
let candidates = [];
|
|
252
|
+
if (contentPatterns.length > 0) {
|
|
253
|
+
candidates = await extractCandidates(contentPatterns, baseDir);
|
|
62
254
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
result = result.replace("</head>", headTags + "</head>");
|
|
66
|
-
}
|
|
67
|
-
return result;
|
|
68
|
-
}
|
|
69
|
-
function injectHeadIntoStream(sourceStream, headTags) {
|
|
70
|
-
if (!headTags) return sourceStream;
|
|
71
|
-
let titleTag = "";
|
|
72
|
-
let rest = headTags;
|
|
73
|
-
const titleMatch = headTags.match(/<title>[^<]*<\/title>/);
|
|
74
|
-
if (titleMatch) {
|
|
75
|
-
titleTag = titleMatch[0];
|
|
76
|
-
rest = headTags.replace(titleMatch[0], "");
|
|
77
|
-
}
|
|
78
|
-
let titleInjected = !titleTag;
|
|
79
|
-
let headInjected = !rest;
|
|
80
|
-
return new ReadableStream({
|
|
81
|
-
async start(controller) {
|
|
82
|
-
const reader = sourceStream.getReader();
|
|
83
|
-
let buffer = "";
|
|
255
|
+
const css = result.build(candidates);
|
|
256
|
+
for (const pattern of contentPatterns) {
|
|
84
257
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
headInjected = true;
|
|
109
|
-
continue;
|
|
258
|
+
for await (const file of glob(resolve2(baseDir, pattern))) {
|
|
259
|
+
if (!deps.includes(file)) deps.push(file);
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const etag = `"tw-${hashCode(css)}"`;
|
|
265
|
+
return { css, etag, files: deps };
|
|
266
|
+
}
|
|
267
|
+
return async (req, ctx, next) => {
|
|
268
|
+
const url = new URL(req.url);
|
|
269
|
+
const pathname = url.pathname;
|
|
270
|
+
const matched = entries.find((e) => e.path === pathname);
|
|
271
|
+
if (!matched) return next(req, ctx);
|
|
272
|
+
const { config } = matched;
|
|
273
|
+
try {
|
|
274
|
+
if (cacheMode === "memory") {
|
|
275
|
+
const cached = cacheStore.get(pathname);
|
|
276
|
+
if (cached) {
|
|
277
|
+
const valid = await isCacheValid(cached);
|
|
278
|
+
if (valid) {
|
|
279
|
+
if (req.headers.get("if-none-match") === cached.etag) {
|
|
280
|
+
return new Response(null, { status: 304 });
|
|
110
281
|
}
|
|
282
|
+
return new Response(cached.css, {
|
|
283
|
+
headers: {
|
|
284
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
285
|
+
ETag: cached.etag,
|
|
286
|
+
"Cache-Control": "no-cache"
|
|
287
|
+
}
|
|
288
|
+
});
|
|
111
289
|
}
|
|
112
|
-
|
|
113
|
-
controller.enqueue(encoder.encode(buffer));
|
|
114
|
-
buffer = "";
|
|
115
|
-
}
|
|
290
|
+
cacheStore.delete(pathname);
|
|
116
291
|
}
|
|
117
|
-
if (buffer) controller.enqueue(encoder.encode(buffer));
|
|
118
|
-
controller.close();
|
|
119
|
-
} catch (err) {
|
|
120
|
-
controller.error(err);
|
|
121
292
|
}
|
|
293
|
+
const { css, etag, files } = await compileCss(config);
|
|
294
|
+
if (cacheMode === "memory") {
|
|
295
|
+
const mtimes = await collectFileMtimes(files);
|
|
296
|
+
cacheStore.set(pathname, { css, etag, files: mtimes });
|
|
297
|
+
}
|
|
298
|
+
return new Response(css, {
|
|
299
|
+
headers: {
|
|
300
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
301
|
+
ETag: etag,
|
|
302
|
+
"Cache-Control": "no-cache"
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
} catch (err) {
|
|
306
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
307
|
+
const html = errorTemplate(message);
|
|
308
|
+
return new Response(html, {
|
|
309
|
+
status: 500,
|
|
310
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
311
|
+
});
|
|
122
312
|
}
|
|
123
|
-
}
|
|
313
|
+
};
|
|
124
314
|
}
|
|
125
|
-
function
|
|
126
|
-
let
|
|
127
|
-
for (let i =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
type: "application/json",
|
|
134
|
-
dangerouslySetInnerHTML: { __html: json }
|
|
135
|
-
});
|
|
136
|
-
return createElement(
|
|
137
|
-
ServerDataContext.Provider,
|
|
138
|
-
{ value: data },
|
|
139
|
-
createElement(Fragment, null, wrapped, dataScript)
|
|
140
|
-
);
|
|
315
|
+
function hashCode(s) {
|
|
316
|
+
let hash = 0;
|
|
317
|
+
for (let i = 0; i < s.length; i++) {
|
|
318
|
+
const ch = s.charCodeAt(i);
|
|
319
|
+
hash = (hash << 5) - hash + ch;
|
|
320
|
+
hash |= 0;
|
|
321
|
+
}
|
|
322
|
+
return Math.abs(hash).toString(36);
|
|
141
323
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return new Response("<!DOCTYPE html>\n" + html, {
|
|
149
|
-
status: options.status ?? 200,
|
|
150
|
-
headers: {
|
|
151
|
-
"content-type": "text/html; charset=utf-8",
|
|
152
|
-
...options.headers
|
|
153
|
-
}
|
|
154
|
-
});
|
|
324
|
+
|
|
325
|
+
// src/middleware/esbuild-dev.ts
|
|
326
|
+
import { resolve as resolve3, relative, dirname as dirname2 } from "node:path";
|
|
327
|
+
import { stat as stat2, readFile as readFile3, realpath } from "node:fs/promises";
|
|
328
|
+
function escapeHtml2(s) {
|
|
329
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
155
330
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
331
|
+
function defaultErrorTemplate2(errors) {
|
|
332
|
+
return `<!DOCTYPE html>
|
|
333
|
+
<html>
|
|
334
|
+
<head>
|
|
335
|
+
<meta charset="utf-8">
|
|
336
|
+
<title>Build Error</title>
|
|
337
|
+
<style>
|
|
338
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
339
|
+
body { font-family: system-ui; background: #1a1a2e; color: #e0e0e0; padding: 2rem; }
|
|
340
|
+
.overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
341
|
+
.card { background: #16213e; border: 1px solid #e74c3c; border-radius: 12px; padding: 2rem; max-width: 800px; width: 100%; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
|
|
342
|
+
h1 { color: #e74c3c; font-size: 1.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
343
|
+
pre { background: #0f0f23; padding: 1.5rem; border-radius: 8px; overflow: auto; font-size: 0.85rem; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
|
|
344
|
+
</style>
|
|
345
|
+
</head>
|
|
346
|
+
<body>
|
|
347
|
+
<div class="overlay">
|
|
348
|
+
<div class="card">
|
|
349
|
+
<h1>\u26A0 esbuild Build Error</h1>
|
|
350
|
+
<pre>${escapeHtml2(errors)}</pre>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</body>
|
|
354
|
+
</html>`;
|
|
355
|
+
}
|
|
356
|
+
async function collectDeps(entryPath) {
|
|
357
|
+
const files = /* @__PURE__ */ new Map();
|
|
358
|
+
const visited = /* @__PURE__ */ new Set();
|
|
359
|
+
const queue = [entryPath];
|
|
360
|
+
while (queue.length > 0) {
|
|
361
|
+
const p = queue.shift();
|
|
362
|
+
try {
|
|
363
|
+
const real = await realpath(p);
|
|
364
|
+
if (visited.has(real)) continue;
|
|
365
|
+
visited.add(real);
|
|
366
|
+
const s = await stat2(real);
|
|
367
|
+
files.set(real, s.mtimeMs);
|
|
368
|
+
} catch {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const content = await readFile3(p, "utf-8");
|
|
373
|
+
const importRe = /from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/g;
|
|
374
|
+
let m;
|
|
375
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
376
|
+
const spec = m[1] ?? m[2];
|
|
377
|
+
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
378
|
+
const dir = dirname2(p);
|
|
379
|
+
const resolved = resolve3(dir, spec);
|
|
380
|
+
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", "/index.ts", "/index.tsx", "/index.js"]) {
|
|
381
|
+
const candidate = resolved + ext;
|
|
382
|
+
if (!visited.has(candidate)) {
|
|
383
|
+
queue.push(candidate);
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
172
386
|
break;
|
|
173
387
|
}
|
|
174
|
-
|
|
388
|
+
queue.push(resolved);
|
|
175
389
|
}
|
|
176
|
-
} catch (err) {
|
|
177
|
-
controller.error(err);
|
|
178
390
|
}
|
|
391
|
+
} catch {
|
|
179
392
|
}
|
|
393
|
+
}
|
|
394
|
+
return files;
|
|
395
|
+
}
|
|
396
|
+
function esbuildDev(opts) {
|
|
397
|
+
const cache = opts.cache ?? "memory";
|
|
398
|
+
const importmap = opts.importmap ?? false;
|
|
399
|
+
const importmapOverrides = opts.importmapOverrides ?? {};
|
|
400
|
+
const errorTemplate = opts.errorTemplate ?? defaultErrorTemplate2;
|
|
401
|
+
const entries = Object.entries(opts.entries).map(([p, e]) => {
|
|
402
|
+
const path = p.startsWith("/") ? p : "/" + p;
|
|
403
|
+
const config = typeof e === "string" ? { entry: e } : e;
|
|
404
|
+
return { path, config };
|
|
180
405
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
406
|
+
const cacheStore = /* @__PURE__ */ new Map();
|
|
407
|
+
const chunkMap = /* @__PURE__ */ new Map();
|
|
408
|
+
let esbuild_ = null;
|
|
409
|
+
let esbuildLoadError = null;
|
|
410
|
+
async function getEsbuild() {
|
|
411
|
+
if (esbuild_) return esbuild_;
|
|
412
|
+
try {
|
|
413
|
+
esbuild_ = await import("esbuild");
|
|
414
|
+
return esbuild_;
|
|
415
|
+
} catch (err) {
|
|
416
|
+
esbuildLoadError = `esbuild is not installed. Run: npm install -D esbuild
|
|
417
|
+
|
|
418
|
+
${String(err)}`;
|
|
419
|
+
throw new Error(esbuildLoadError, { cause: err });
|
|
187
420
|
}
|
|
188
|
-
}
|
|
421
|
+
}
|
|
422
|
+
let importmapScript = null;
|
|
423
|
+
function buildImportmap() {
|
|
424
|
+
const mappings = {};
|
|
425
|
+
Object.assign(mappings, importmapOverrides);
|
|
426
|
+
for (const { path, config } of entries) {
|
|
427
|
+
const entryName = path.split("/").pop() ?? "";
|
|
428
|
+
if (entryName.includes("vendor") || config.external && config.external.length === 0 || !config.external && config.bundle !== false) {
|
|
429
|
+
if (!mappings["react"]) mappings["react"] = path;
|
|
430
|
+
if (!mappings["react/jsx-runtime"]) mappings["react/jsx-runtime"] = path;
|
|
431
|
+
if (!mappings["react-dom/client"]) mappings["react-dom/client"] = path;
|
|
432
|
+
if (!mappings["react-dom"]) mappings["react-dom"] = path;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const json = JSON.stringify({ imports: mappings });
|
|
436
|
+
return `<script type="importmap">${json}</script>`;
|
|
437
|
+
}
|
|
438
|
+
async function compile(entry) {
|
|
439
|
+
const esbuild = await getEsbuild();
|
|
440
|
+
const isClientRouter = !!entry.clientRouter;
|
|
441
|
+
let entryAbs;
|
|
442
|
+
let buildOpts;
|
|
443
|
+
if (isClientRouter) {
|
|
444
|
+
const cr = entry.clientRouter;
|
|
445
|
+
const virtualModule = "weifuwu:client-entry";
|
|
446
|
+
entryAbs = virtualModule;
|
|
447
|
+
const layoutImport = `import * as _layout from '${cr.layout}'
|
|
448
|
+
const _lx = Object.entries(_layout).filter(([,v]) => typeof v === 'function')
|
|
449
|
+
const Layout = _layout.default || (_lx[0]?.[1])`;
|
|
450
|
+
let routesCode;
|
|
451
|
+
if (cr.pages) {
|
|
452
|
+
const entries2 = Object.entries(cr.pages).map(
|
|
453
|
+
([path, component]) => ` '${path}': () => import('${component}'),`
|
|
454
|
+
);
|
|
455
|
+
routesCode = `const routes = {
|
|
456
|
+
${entries2.join("\n")}
|
|
457
|
+
}`;
|
|
458
|
+
} else if (cr.routes) {
|
|
459
|
+
routesCode = `import { routes } from '${cr.routes}'`;
|
|
460
|
+
} else {
|
|
461
|
+
routesCode = "const routes = {}";
|
|
462
|
+
}
|
|
463
|
+
const fallbackLine = cr.fallback ? `const fallback = () => import('${cr.fallback}')` : "";
|
|
464
|
+
const fallbackOpt = cr.fallback ? " fallback," : "";
|
|
465
|
+
const generatedCode = [
|
|
466
|
+
`import { createBrowserRouter } from 'weifuwu/react/client'`,
|
|
467
|
+
layoutImport,
|
|
468
|
+
routesCode,
|
|
469
|
+
fallbackLine,
|
|
470
|
+
"",
|
|
471
|
+
"createBrowserRouter({",
|
|
472
|
+
" layout: Layout,",
|
|
473
|
+
" routes,",
|
|
474
|
+
fallbackOpt,
|
|
475
|
+
"})"
|
|
476
|
+
].filter(Boolean).join("\n");
|
|
477
|
+
buildOpts = {
|
|
478
|
+
entryPoints: [virtualModule],
|
|
479
|
+
bundle: entry.bundle ?? true,
|
|
480
|
+
external: entry.external ?? [],
|
|
481
|
+
minify: entry.minify ?? true,
|
|
482
|
+
platform: entry.platform ?? "browser",
|
|
483
|
+
format: entry.format ?? "esm",
|
|
484
|
+
sourcemap: entry.sourcemap ?? false,
|
|
485
|
+
splitting: entry.splitting ?? false,
|
|
486
|
+
...entry.splitting ? { outdir: resolve3(".weifuwu-esbuild-out") } : {},
|
|
487
|
+
define: entry.define,
|
|
488
|
+
loader: entry.loader,
|
|
489
|
+
write: false,
|
|
490
|
+
logLevel: "silent",
|
|
491
|
+
plugins: [{
|
|
492
|
+
name: "weifuwu-client-router",
|
|
493
|
+
setup(build) {
|
|
494
|
+
build.onResolve({ filter: new RegExp(`^${virtualModule.replace(/:/g, "\\:")}$`) }, () => ({
|
|
495
|
+
path: virtualModule,
|
|
496
|
+
namespace: "weifuwu-client"
|
|
497
|
+
}));
|
|
498
|
+
build.onLoad({ filter: /.*/, namespace: "weifuwu-client" }, () => ({
|
|
499
|
+
contents: generatedCode,
|
|
500
|
+
loader: "ts",
|
|
501
|
+
resolveDir: process.cwd()
|
|
502
|
+
}));
|
|
503
|
+
}
|
|
504
|
+
}]
|
|
505
|
+
};
|
|
506
|
+
} else {
|
|
507
|
+
entryAbs = resolve3(entry.entry);
|
|
508
|
+
buildOpts = {
|
|
509
|
+
entryPoints: [entryAbs],
|
|
510
|
+
bundle: entry.bundle ?? true,
|
|
511
|
+
external: entry.external ?? [],
|
|
512
|
+
minify: entry.minify ?? true,
|
|
513
|
+
platform: entry.platform ?? "browser",
|
|
514
|
+
format: entry.format ?? "esm",
|
|
515
|
+
sourcemap: entry.sourcemap ?? false,
|
|
516
|
+
splitting: entry.splitting ?? false,
|
|
517
|
+
...entry.splitting ? { outdir: dirname2(entryAbs) } : {},
|
|
518
|
+
define: entry.define,
|
|
519
|
+
loader: entry.loader,
|
|
520
|
+
write: false,
|
|
521
|
+
logLevel: "silent"
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
const result = await esbuild.build(buildOpts);
|
|
525
|
+
const msgs = [];
|
|
526
|
+
for (const w of result.warnings) {
|
|
527
|
+
const loc = w.location ? ` at ${relative(".", w.location.file ?? "")}:${w.location.line}:${w.location.column}` : "";
|
|
528
|
+
msgs.push(`[warn] ${w.text}${loc}`);
|
|
529
|
+
}
|
|
530
|
+
for (const e of result.errors) {
|
|
531
|
+
const loc = e.location ? ` at ${relative(".", e.location.file ?? "")}:${e.location.line}:${e.location.column}` : "";
|
|
532
|
+
msgs.push(`[error] ${e.text}${loc}`);
|
|
533
|
+
}
|
|
534
|
+
if (result.errors.length > 0) {
|
|
535
|
+
throw new Error(msgs.join("\n"));
|
|
536
|
+
}
|
|
537
|
+
const outputFiles = result.outputFiles;
|
|
538
|
+
const entryFile = outputFiles.find((f) => f.path === entryAbs) ?? outputFiles[0];
|
|
539
|
+
const code = entryFile?.text ?? "";
|
|
540
|
+
let chunks;
|
|
541
|
+
if (outputFiles.length > 1) {
|
|
542
|
+
chunks = /* @__PURE__ */ new Map();
|
|
543
|
+
for (const f of outputFiles) {
|
|
544
|
+
if (f === entryFile) continue;
|
|
545
|
+
const chunkName = f.path.split("/").pop() ?? f.path;
|
|
546
|
+
chunks.set(chunkName, { code: f.text, etag: `"esbuild-${hashCode2(f.text)}"` });
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const etag = `"esbuild-${hashCode2(code)}"`;
|
|
550
|
+
if (msgs.length > 0) {
|
|
551
|
+
console.error("[esbuildDev]", msgs.join("\n"));
|
|
552
|
+
}
|
|
553
|
+
return { code, etag, chunks };
|
|
554
|
+
}
|
|
555
|
+
function isCacheValid2(cached) {
|
|
556
|
+
return Promise.all(
|
|
557
|
+
[...cached.files].map(async ([file, mtime]) => {
|
|
558
|
+
try {
|
|
559
|
+
const s = await stat2(file);
|
|
560
|
+
return s.mtimeMs === mtime;
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
})
|
|
565
|
+
).then((results) => results.every(Boolean));
|
|
566
|
+
}
|
|
567
|
+
return async (req, ctx, next) => {
|
|
568
|
+
const url = new URL(req.url);
|
|
569
|
+
const pathname = url.pathname;
|
|
570
|
+
if (importmap && pathname === "/assets/importmap") {
|
|
571
|
+
if (!importmapScript) importmapScript = buildImportmap();
|
|
572
|
+
return new Response(importmapScript, {
|
|
573
|
+
headers: {
|
|
574
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
575
|
+
"Cache-Control": "no-cache"
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
const matched = entries.find((e) => e.path === pathname);
|
|
580
|
+
const chunkName = pathname.split("/").pop() ?? "";
|
|
581
|
+
const chunkOwner = chunkMap.get(chunkName);
|
|
582
|
+
if (!matched && !chunkOwner) return next(req, ctx);
|
|
583
|
+
if (!matched && chunkOwner) {
|
|
584
|
+
const ownerCache = cacheStore.get(chunkOwner);
|
|
585
|
+
const chunk = ownerCache?.chunks?.get(chunkName);
|
|
586
|
+
if (chunk) {
|
|
587
|
+
if (req.headers.get("if-none-match") === chunk.etag) {
|
|
588
|
+
return new Response(null, { status: 304 });
|
|
589
|
+
}
|
|
590
|
+
return new Response(chunk.code, {
|
|
591
|
+
headers: {
|
|
592
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
593
|
+
ETag: chunk.etag,
|
|
594
|
+
"Cache-Control": "no-cache"
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
return next(req, ctx);
|
|
599
|
+
}
|
|
600
|
+
const { config } = matched;
|
|
601
|
+
try {
|
|
602
|
+
if (cache === "memory") {
|
|
603
|
+
const cached = cacheStore.get(pathname);
|
|
604
|
+
if (cached) {
|
|
605
|
+
const valid = await isCacheValid2(cached);
|
|
606
|
+
if (valid) {
|
|
607
|
+
if (req.headers.get("if-none-match") === cached.etag) {
|
|
608
|
+
return new Response(null, { status: 304 });
|
|
609
|
+
}
|
|
610
|
+
return new Response(cached.code, {
|
|
611
|
+
headers: {
|
|
612
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
613
|
+
ETag: cached.etag,
|
|
614
|
+
"Cache-Control": "no-cache"
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
for (const [cn] of cached.chunks ?? []) chunkMap.delete(cn);
|
|
619
|
+
cacheStore.delete(pathname);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const { code, etag, chunks } = await compile(config);
|
|
623
|
+
const depEntry = config.clientRouter?.routes ?? (config.clientRouter?.pages ? Object.values(config.clientRouter.pages)[0] : void 0) ?? config.entry;
|
|
624
|
+
const deps = await collectDeps(resolve3(depEntry));
|
|
625
|
+
if (cache === "memory") {
|
|
626
|
+
cacheStore.set(pathname, { code, etag, files: deps, chunks });
|
|
627
|
+
if (chunks) {
|
|
628
|
+
for (const [cn] of chunks) chunkMap.set(cn, pathname);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return new Response(code, {
|
|
632
|
+
headers: {
|
|
633
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
634
|
+
ETag: etag,
|
|
635
|
+
"Cache-Control": "no-cache"
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
} catch (err) {
|
|
639
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
640
|
+
const html = errorTemplate(message);
|
|
641
|
+
return new Response(html, {
|
|
642
|
+
status: 500,
|
|
643
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
};
|
|
189
647
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
648
|
+
function hashCode2(s) {
|
|
649
|
+
let hash = 0;
|
|
650
|
+
for (let i = 0; i < s.length; i++) {
|
|
651
|
+
const ch = s.charCodeAt(i);
|
|
652
|
+
hash = (hash << 5) - hash + ch;
|
|
653
|
+
hash |= 0;
|
|
654
|
+
}
|
|
655
|
+
return Math.abs(hash).toString(36);
|
|
195
656
|
}
|
|
196
657
|
|
|
197
|
-
// src/react/navigation.ts
|
|
198
|
-
import {
|
|
199
|
-
createElement as createElement2,
|
|
200
|
-
createContext as createContext2,
|
|
201
|
-
useContext as useContext2
|
|
202
|
-
} from "react";
|
|
203
|
-
|
|
204
658
|
// src/react/error-boundary.ts
|
|
205
|
-
import { Component } from "react";
|
|
659
|
+
import { createElement, Component } from "react";
|
|
206
660
|
var ErrorBoundary = class extends Component {
|
|
207
|
-
state = { error: null };
|
|
661
|
+
state = { hasError: false, error: null };
|
|
208
662
|
static getDerivedStateFromError(error) {
|
|
209
|
-
return { error };
|
|
663
|
+
return { hasError: true, error };
|
|
210
664
|
}
|
|
211
|
-
componentDidCatch(error
|
|
212
|
-
this.props.onError?.(error
|
|
665
|
+
componentDidCatch(error) {
|
|
666
|
+
this.props.onError?.(error);
|
|
213
667
|
}
|
|
214
668
|
render() {
|
|
215
|
-
if (this.state.
|
|
216
|
-
return this.props.fallback;
|
|
669
|
+
if (this.state.hasError) {
|
|
670
|
+
if (this.props.fallback) return this.props.fallback;
|
|
671
|
+
return createElement(
|
|
672
|
+
"div",
|
|
673
|
+
{ style: { padding: "2rem", textAlign: "center" } },
|
|
674
|
+
createElement("h1", null, "Something went wrong"),
|
|
675
|
+
createElement("p", null, this.state.error?.message ?? "Unknown error")
|
|
676
|
+
);
|
|
217
677
|
}
|
|
218
678
|
return this.props.children;
|
|
219
679
|
}
|
|
220
680
|
};
|
|
221
681
|
|
|
222
|
-
// src/react/
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return useContext2(RouterContext)?.params ?? {};
|
|
227
|
-
}
|
|
228
|
-
function useNavigate() {
|
|
229
|
-
const ctx = useContext2(RouterContext);
|
|
230
|
-
if (!ctx) throw new Error("useNavigate() must be used within a client router");
|
|
231
|
-
return ctx.navigate;
|
|
232
|
-
}
|
|
233
|
-
function useNavigation() {
|
|
234
|
-
const ctx = useContext2(RouterContext);
|
|
235
|
-
return { state: ctx?.state ?? "idle" };
|
|
682
|
+
// src/react/hooks.ts
|
|
683
|
+
import { useContext } from "react";
|
|
684
|
+
function useServerData() {
|
|
685
|
+
return useContext(ServerDataContext);
|
|
236
686
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
687
|
+
|
|
688
|
+
// src/react/index.ts
|
|
689
|
+
function HtmlShell({ children, importMap, stylesheets, data }) {
|
|
690
|
+
const headChildren = [
|
|
691
|
+
createElement2("meta", { charSet: "utf-8", key: "charset" }),
|
|
692
|
+
createElement2("meta", { name: "viewport", content: "width=device-width, initial-scale=1", key: "viewport" })
|
|
693
|
+
];
|
|
694
|
+
if (stylesheets) {
|
|
695
|
+
for (const href of stylesheets) {
|
|
696
|
+
headChildren.push(
|
|
697
|
+
createElement2("link", { rel: "stylesheet", href, key: `css-${href}` })
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (importMap) {
|
|
702
|
+
headChildren.push(
|
|
703
|
+
createElement2("script", {
|
|
704
|
+
type: "importmap",
|
|
705
|
+
key: "importmap",
|
|
706
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(importMap) }
|
|
707
|
+
})
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
const bodyChildren = [
|
|
711
|
+
createElement2("div", { id: "root", key: "root" }, children)
|
|
712
|
+
];
|
|
713
|
+
if (data && Object.keys(data).length > 0) {
|
|
714
|
+
bodyChildren.push(
|
|
715
|
+
createElement2("script", {
|
|
716
|
+
id: "__WEIFUWU_DATA__",
|
|
717
|
+
type: "application/json",
|
|
718
|
+
key: "weifuwu-data",
|
|
719
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(data).replace(/</g, "\\u003c") }
|
|
720
|
+
})
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
return createElement2(
|
|
724
|
+
"html",
|
|
725
|
+
{ lang: "en" },
|
|
726
|
+
createElement2("head", null, ...headChildren),
|
|
727
|
+
createElement2("body", null, ...bodyChildren)
|
|
728
|
+
);
|
|
250
729
|
}
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
730
|
+
async function renderComponent(Component2, data, layout, renderOpts) {
|
|
731
|
+
let element = createElement2(Component2, renderOpts.props ?? {});
|
|
732
|
+
if (layout) {
|
|
733
|
+
element = createElement2(layout, { children: element });
|
|
734
|
+
}
|
|
735
|
+
element = createElement2(ServerDataContext.Provider, { value: data }, element);
|
|
736
|
+
const page = createElement2(HtmlShell, {
|
|
737
|
+
children: element,
|
|
738
|
+
importMap: renderOpts.importMap,
|
|
739
|
+
stylesheets: renderOpts.stylesheets,
|
|
740
|
+
data: Object.keys(data).length > 0 ? data : void 0
|
|
741
|
+
});
|
|
742
|
+
const rstream = await renderToReadableStream(page, {
|
|
743
|
+
bootstrapScripts: renderOpts.bootstrapScripts,
|
|
744
|
+
bootstrapModules: renderOpts.bootstrapModules
|
|
745
|
+
});
|
|
746
|
+
if (renderOpts.stream === false) {
|
|
747
|
+
await rstream.allReady;
|
|
748
|
+
}
|
|
749
|
+
return new Response(rstream, {
|
|
750
|
+
status: renderOpts.status ?? 200,
|
|
751
|
+
headers: { "content-type": "text/html; charset=utf-8", ...renderOpts.headers }
|
|
752
|
+
});
|
|
255
753
|
}
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
754
|
+
function react(opts) {
|
|
755
|
+
if (opts && "pages" in opts) {
|
|
756
|
+
return (app) => createFullReactApp(app, opts);
|
|
757
|
+
}
|
|
758
|
+
if (opts?.cacheDir) setReactCacheDir(opts.cacheDir);
|
|
759
|
+
let LayoutComponent = null;
|
|
760
|
+
let layoutLoaded = false;
|
|
761
|
+
let layoutLoadError = null;
|
|
762
|
+
async function getLayout() {
|
|
763
|
+
if (!opts?.layout) return null;
|
|
764
|
+
if (layoutLoaded) {
|
|
765
|
+
if (layoutLoadError) throw layoutLoadError;
|
|
766
|
+
return LayoutComponent;
|
|
767
|
+
}
|
|
267
768
|
try {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
redirect: "manual"
|
|
272
|
-
});
|
|
273
|
-
if (res.status >= 300 && res.status < 400) {
|
|
274
|
-
const loc = res.headers.get("Location");
|
|
275
|
-
if (loc) {
|
|
276
|
-
router.navigate(loc);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
await router.revalidate();
|
|
769
|
+
LayoutComponent = await loadTsxComponent(opts.layout);
|
|
770
|
+
layoutLoaded = true;
|
|
771
|
+
return LayoutComponent;
|
|
281
772
|
} catch (err) {
|
|
282
|
-
|
|
773
|
+
layoutLoadError = err instanceof Error ? err : new Error(String(err));
|
|
774
|
+
layoutLoaded = true;
|
|
775
|
+
throw layoutLoadError;
|
|
283
776
|
}
|
|
777
|
+
}
|
|
778
|
+
return async (_req, ctx, next) => {
|
|
779
|
+
ctx.render = async (path, renderOpts) => {
|
|
780
|
+
let data = renderOpts?.data ?? {};
|
|
781
|
+
if (renderOpts?.loader) {
|
|
782
|
+
const loaderData = await renderOpts.loader(ctx);
|
|
783
|
+
data = { ...data, ...loaderData };
|
|
784
|
+
}
|
|
785
|
+
const Component2 = await loadTsxComponent(path);
|
|
786
|
+
const layout = await getLayout();
|
|
787
|
+
return renderComponent(Component2, data, layout, renderOpts ?? {});
|
|
788
|
+
};
|
|
789
|
+
return next(_req, ctx);
|
|
284
790
|
};
|
|
285
|
-
return createElement2(
|
|
286
|
-
"form",
|
|
287
|
-
{ method, action, ...props, onSubmit: handleSubmit },
|
|
288
|
-
children
|
|
289
|
-
);
|
|
290
791
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
var SETUP_KEY = /* @__PURE__ */ Symbol.for("weifuwu:react:setup");
|
|
295
|
-
function bag(ctx) {
|
|
296
|
-
return ctx;
|
|
792
|
+
async function resolveLoader(modulePath) {
|
|
793
|
+
const mod = await loadTsxModule(modulePath);
|
|
794
|
+
return typeof mod.loader === "function" ? mod.loader : null;
|
|
297
795
|
}
|
|
298
|
-
function
|
|
796
|
+
async function callLoader(loader, ctx, data, rethrow = true) {
|
|
797
|
+
if (!loader) return data;
|
|
299
798
|
try {
|
|
300
|
-
return
|
|
301
|
-
} catch {
|
|
302
|
-
|
|
799
|
+
return { ...data, ...await loader(ctx) };
|
|
800
|
+
} catch (err) {
|
|
801
|
+
if (rethrow) throw err;
|
|
802
|
+
return data;
|
|
303
803
|
}
|
|
304
804
|
}
|
|
305
|
-
function
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
805
|
+
function createFullReactApp(app, opts) {
|
|
806
|
+
const stylesheets = [...opts.stylesheets ?? []];
|
|
807
|
+
if (opts.tailwind) {
|
|
808
|
+
const twPath = opts.tailwind.path ?? "/assets/tailwind.css";
|
|
809
|
+
const twEntry = opts.tailwind.entry ?? "./styles/input.css";
|
|
810
|
+
app.use(tailwindDev({ entries: { [twPath]: { entry: twEntry } } }));
|
|
811
|
+
if (!stylesheets.includes(twPath)) stylesheets.push(twPath);
|
|
812
|
+
}
|
|
813
|
+
app.use(react({ layout: opts.layout, cacheDir: opts.cacheDir }));
|
|
814
|
+
const layoutLoaderP = resolveLoader(opts.layout);
|
|
815
|
+
const notFoundLoaderP = opts.notFound ? resolveLoader(opts.notFound) : Promise.resolve(null);
|
|
816
|
+
const clientCfg = typeof opts.client === "object" ? opts.client : {};
|
|
817
|
+
const clientPath = clientCfg.path ?? "/assets/client.js";
|
|
818
|
+
const bootstrapModules = [...opts.bootstrapModules ?? []];
|
|
819
|
+
if (opts.client !== false && !bootstrapModules.includes(clientPath)) {
|
|
820
|
+
bootstrapModules.push(clientPath);
|
|
821
|
+
}
|
|
822
|
+
const renderOpts = {
|
|
823
|
+
stylesheets: stylesheets.length > 0 ? stylesheets : void 0,
|
|
824
|
+
bootstrapModules: bootstrapModules.length > 0 ? bootstrapModules : void 0,
|
|
825
|
+
stream: opts.stream
|
|
826
|
+
};
|
|
827
|
+
for (const [path, component] of Object.entries(opts.pages)) {
|
|
828
|
+
app.get(path, async (_req, ctx) => {
|
|
829
|
+
let data = {};
|
|
830
|
+
data = await callLoader(await layoutLoaderP, ctx, data);
|
|
831
|
+
const pageLoader = opts.loaders?.[path] ?? await resolveLoader(component);
|
|
832
|
+
data = await callLoader(pageLoader, ctx, data);
|
|
833
|
+
return ctx.render(component, { ...renderOpts, data });
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
if (opts.notFound) {
|
|
837
|
+
const notFoundPath = opts.notFound;
|
|
838
|
+
app.onError(async (err, _req, ctx) => {
|
|
839
|
+
const status = typeof err === "object" && err !== null && "status" in err ? err.status : 500;
|
|
840
|
+
if (ctx.render) {
|
|
841
|
+
let data = { error: String(err) };
|
|
842
|
+
data = await callLoader(await layoutLoaderP, ctx, data, false);
|
|
843
|
+
data = await callLoader(await notFoundLoaderP, ctx, data, false);
|
|
844
|
+
return ctx.render(notFoundPath, { ...renderOpts, status, data });
|
|
845
|
+
}
|
|
846
|
+
return new Response("Internal Server Error", { status });
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
if (opts.client !== false) {
|
|
850
|
+
app.use(esbuildDev({
|
|
851
|
+
entries: {
|
|
852
|
+
[clientPath]: {
|
|
853
|
+
clientRouter: {
|
|
854
|
+
pages: opts.pages,
|
|
855
|
+
layout: opts.layout,
|
|
856
|
+
fallback: opts.notFound
|
|
857
|
+
},
|
|
858
|
+
bundle: true,
|
|
859
|
+
splitting: clientCfg.splitting ?? true,
|
|
860
|
+
minify: clientCfg.minify ?? false
|
|
323
861
|
}
|
|
324
|
-
|
|
325
|
-
|
|
862
|
+
}
|
|
863
|
+
}));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function extractImportPath(fn) {
|
|
867
|
+
const src = fn.toString();
|
|
868
|
+
const m = src.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
869
|
+
if (!m) throw new Error(`Cannot extract import path from: ${src}`);
|
|
870
|
+
return m[1];
|
|
871
|
+
}
|
|
872
|
+
function reactRouter(app, routes, opts = {}) {
|
|
873
|
+
let LayoutComponent = null;
|
|
874
|
+
let layoutPromise = null;
|
|
875
|
+
async function getLayout() {
|
|
876
|
+
if (!opts.layout) return null;
|
|
877
|
+
if (LayoutComponent) return LayoutComponent;
|
|
878
|
+
if (!layoutPromise) {
|
|
879
|
+
layoutPromise = loadTsxComponent(opts.layout).then((c) => {
|
|
880
|
+
LayoutComponent = c;
|
|
881
|
+
return c;
|
|
882
|
+
});
|
|
326
883
|
}
|
|
327
|
-
return
|
|
328
|
-
}
|
|
329
|
-
|
|
884
|
+
return layoutPromise;
|
|
885
|
+
}
|
|
886
|
+
for (const [path, importer] of Object.entries(routes)) {
|
|
887
|
+
app.get(path, async (_req, ctx) => {
|
|
888
|
+
const cmpPath = extractImportPath(importer);
|
|
889
|
+
const Component2 = await loadTsxComponent(cmpPath);
|
|
890
|
+
const layout = await getLayout();
|
|
891
|
+
let data = {};
|
|
892
|
+
const loader = opts.loaders?.[path];
|
|
893
|
+
if (loader) {
|
|
894
|
+
data = await loader(ctx);
|
|
895
|
+
}
|
|
896
|
+
return renderComponent(Component2, data, layout, opts);
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function Link({ href, children, ...props }) {
|
|
901
|
+
return createElement2("a", { href, ...props }, children);
|
|
330
902
|
}
|
|
331
903
|
export {
|
|
332
904
|
ErrorBoundary,
|
|
333
|
-
Form,
|
|
334
905
|
Link,
|
|
335
906
|
ServerDataContext,
|
|
336
907
|
react,
|
|
337
|
-
|
|
338
|
-
useNavigation,
|
|
339
|
-
useParams,
|
|
340
|
-
useRevalidate,
|
|
908
|
+
reactRouter,
|
|
341
909
|
useServerData
|
|
342
910
|
};
|