weifuwu 0.31.1 → 0.31.2
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 +107 -78
- package/dist/core/router.d.ts +11 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +895 -311
- package/dist/middleware/esbuild-dev.d.ts +69 -0
- package/dist/middleware/esbuild-dev.js +335 -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/package.json +16 -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/index.js
CHANGED
|
@@ -225,14 +225,14 @@ function serve(router, options) {
|
|
|
225
225
|
if (!server.listening) return;
|
|
226
226
|
server.close();
|
|
227
227
|
server.closeIdleConnections();
|
|
228
|
-
return new Promise((
|
|
228
|
+
return new Promise((resolve5) => {
|
|
229
229
|
const timer = setTimeout(() => {
|
|
230
230
|
server.closeAllConnections();
|
|
231
|
-
|
|
231
|
+
resolve5();
|
|
232
232
|
}, timeoutMs);
|
|
233
233
|
server.on("close", () => {
|
|
234
234
|
clearTimeout(timer);
|
|
235
|
-
|
|
235
|
+
resolve5();
|
|
236
236
|
});
|
|
237
237
|
});
|
|
238
238
|
}
|
|
@@ -277,7 +277,7 @@ function createHub(opts) {
|
|
|
277
277
|
}
|
|
278
278
|
});
|
|
279
279
|
}
|
|
280
|
-
function
|
|
280
|
+
function join3(key, ws) {
|
|
281
281
|
if (!channels.has(key)) {
|
|
282
282
|
channels.set(key, /* @__PURE__ */ new Set());
|
|
283
283
|
redisSub?.subscribe(`${prefix}${key}`);
|
|
@@ -339,7 +339,7 @@ function createHub(opts) {
|
|
|
339
339
|
redisPub = void 0;
|
|
340
340
|
redisSub = null;
|
|
341
341
|
}
|
|
342
|
-
return { join:
|
|
342
|
+
return { join: join3, leave, broadcast, close };
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
// src/core/ws.ts
|
|
@@ -503,6 +503,20 @@ var Router = class _Router {
|
|
|
503
503
|
this._checkMiddlewareMeta(mw, "global");
|
|
504
504
|
return this;
|
|
505
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Install a plugin — a function that configures the Router with
|
|
508
|
+
* routes, middleware, and error handlers. Use when `.use()` isn't
|
|
509
|
+
* enough because you need to call `app.get()`, `app.onError()`, etc.
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```ts
|
|
513
|
+
* app.plugin(app => createReactApp(app, { pages: {...}, layout: '...', tailwind: {...} }))
|
|
514
|
+
* ```
|
|
515
|
+
*/
|
|
516
|
+
plugin(fn) {
|
|
517
|
+
fn(this);
|
|
518
|
+
return this;
|
|
519
|
+
}
|
|
506
520
|
mount(path, router) {
|
|
507
521
|
this._mountRouter(path, router);
|
|
508
522
|
return this;
|
|
@@ -912,13 +926,13 @@ function serveStatic(root, options) {
|
|
|
912
926
|
let fileHandle;
|
|
913
927
|
try {
|
|
914
928
|
fileHandle = await open(filePath, "r");
|
|
915
|
-
let
|
|
929
|
+
let stat3 = await fileHandle.stat();
|
|
916
930
|
const realPath = await realpath(filePath);
|
|
917
931
|
if (!realPath.startsWith(rootDir + sep) && realPath !== rootDir) {
|
|
918
932
|
await fileHandle.close();
|
|
919
933
|
return new Response("Forbidden", { status: 403 });
|
|
920
934
|
}
|
|
921
|
-
if (
|
|
935
|
+
if (stat3.isDirectory()) {
|
|
922
936
|
await fileHandle.close();
|
|
923
937
|
const indexFile = opts.index ?? "index.html";
|
|
924
938
|
filePath = resolve(filePath, indexFile);
|
|
@@ -926,29 +940,29 @@ function serveStatic(root, options) {
|
|
|
926
940
|
return new Response("Forbidden", { status: 403 });
|
|
927
941
|
}
|
|
928
942
|
fileHandle = await open(filePath, "r");
|
|
929
|
-
|
|
930
|
-
if (!
|
|
943
|
+
stat3 = await fileHandle.stat();
|
|
944
|
+
if (!stat3.isFile()) {
|
|
931
945
|
await fileHandle.close();
|
|
932
946
|
return new Response("Not Found", { status: 404 });
|
|
933
947
|
}
|
|
934
948
|
}
|
|
935
949
|
const mimeType = MIME_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
936
|
-
const etag = `"${
|
|
950
|
+
const etag = `"${stat3.ino}-${stat3.size}-${stat3.mtimeMs}"`;
|
|
937
951
|
const ifNoneMatch = req.headers.get("if-none-match");
|
|
938
952
|
if (ifNoneMatch === etag) {
|
|
939
953
|
await fileHandle.close();
|
|
940
954
|
return new Response(null, { status: 304 });
|
|
941
955
|
}
|
|
942
956
|
const ifModifiedSince = req.headers.get("if-modified-since");
|
|
943
|
-
if (ifModifiedSince &&
|
|
957
|
+
if (ifModifiedSince && stat3.mtimeMs <= new Date(ifModifiedSince).getTime()) {
|
|
944
958
|
await fileHandle.close();
|
|
945
959
|
return new Response(null, { status: 304 });
|
|
946
960
|
}
|
|
947
961
|
const headers = {
|
|
948
962
|
"Content-Type": mimeType,
|
|
949
|
-
"Content-Length": String(
|
|
963
|
+
"Content-Length": String(stat3.size),
|
|
950
964
|
ETag: etag,
|
|
951
|
-
"Last-Modified":
|
|
965
|
+
"Last-Modified": stat3.mtime.toUTCString(),
|
|
952
966
|
"Cache-Control": opts.immutable ? `public, max-age=${opts.maxAge ?? 31536e3}, immutable` : `public, max-age=${opts.maxAge ?? 0}`
|
|
953
967
|
};
|
|
954
968
|
const readStream = fileHandle.createReadStream();
|
|
@@ -1099,6 +1113,536 @@ function upload(options) {
|
|
|
1099
1113
|
return mw;
|
|
1100
1114
|
}
|
|
1101
1115
|
|
|
1116
|
+
// src/middleware/esbuild-dev.ts
|
|
1117
|
+
import { resolve as resolve2, relative, dirname } from "node:path";
|
|
1118
|
+
import { stat, readFile, realpath as realpath2 } from "node:fs/promises";
|
|
1119
|
+
function escapeHtml(s) {
|
|
1120
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1121
|
+
}
|
|
1122
|
+
function defaultErrorTemplate(errors) {
|
|
1123
|
+
return `<!DOCTYPE html>
|
|
1124
|
+
<html>
|
|
1125
|
+
<head>
|
|
1126
|
+
<meta charset="utf-8">
|
|
1127
|
+
<title>Build Error</title>
|
|
1128
|
+
<style>
|
|
1129
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1130
|
+
body { font-family: system-ui; background: #1a1a2e; color: #e0e0e0; padding: 2rem; }
|
|
1131
|
+
.overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
1132
|
+
.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); }
|
|
1133
|
+
h1 { color: #e74c3c; font-size: 1.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
1134
|
+
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; }
|
|
1135
|
+
</style>
|
|
1136
|
+
</head>
|
|
1137
|
+
<body>
|
|
1138
|
+
<div class="overlay">
|
|
1139
|
+
<div class="card">
|
|
1140
|
+
<h1>\u26A0 esbuild Build Error</h1>
|
|
1141
|
+
<pre>${escapeHtml(errors)}</pre>
|
|
1142
|
+
</div>
|
|
1143
|
+
</div>
|
|
1144
|
+
</body>
|
|
1145
|
+
</html>`;
|
|
1146
|
+
}
|
|
1147
|
+
async function collectDeps(entryPath) {
|
|
1148
|
+
const files = /* @__PURE__ */ new Map();
|
|
1149
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1150
|
+
const queue2 = [entryPath];
|
|
1151
|
+
while (queue2.length > 0) {
|
|
1152
|
+
const p = queue2.shift();
|
|
1153
|
+
try {
|
|
1154
|
+
const real = await realpath2(p);
|
|
1155
|
+
if (visited.has(real)) continue;
|
|
1156
|
+
visited.add(real);
|
|
1157
|
+
const s = await stat(real);
|
|
1158
|
+
files.set(real, s.mtimeMs);
|
|
1159
|
+
} catch {
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
const content = await readFile(p, "utf-8");
|
|
1164
|
+
const importRe = /from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/g;
|
|
1165
|
+
let m;
|
|
1166
|
+
while ((m = importRe.exec(content)) !== null) {
|
|
1167
|
+
const spec = m[1] ?? m[2];
|
|
1168
|
+
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
1169
|
+
const dir = dirname(p);
|
|
1170
|
+
const resolved = resolve2(dir, spec);
|
|
1171
|
+
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", "/index.ts", "/index.tsx", "/index.js"]) {
|
|
1172
|
+
const candidate = resolved + ext;
|
|
1173
|
+
if (!visited.has(candidate)) {
|
|
1174
|
+
queue2.push(candidate);
|
|
1175
|
+
break;
|
|
1176
|
+
}
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
queue2.push(resolved);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
} catch {
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return files;
|
|
1186
|
+
}
|
|
1187
|
+
function esbuildDev(opts) {
|
|
1188
|
+
const cache = opts.cache ?? "memory";
|
|
1189
|
+
const importmap = opts.importmap ?? false;
|
|
1190
|
+
const importmapOverrides = opts.importmapOverrides ?? {};
|
|
1191
|
+
const errorTemplate = opts.errorTemplate ?? defaultErrorTemplate;
|
|
1192
|
+
const entries = Object.entries(opts.entries).map(([p, e]) => {
|
|
1193
|
+
const path = p.startsWith("/") ? p : "/" + p;
|
|
1194
|
+
const config = typeof e === "string" ? { entry: e } : e;
|
|
1195
|
+
return { path, config };
|
|
1196
|
+
});
|
|
1197
|
+
const cacheStore = /* @__PURE__ */ new Map();
|
|
1198
|
+
const chunkMap = /* @__PURE__ */ new Map();
|
|
1199
|
+
let esbuild_ = null;
|
|
1200
|
+
let esbuildLoadError = null;
|
|
1201
|
+
async function getEsbuild() {
|
|
1202
|
+
if (esbuild_) return esbuild_;
|
|
1203
|
+
try {
|
|
1204
|
+
esbuild_ = await import("esbuild");
|
|
1205
|
+
return esbuild_;
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
esbuildLoadError = `esbuild is not installed. Run: npm install -D esbuild
|
|
1208
|
+
|
|
1209
|
+
${String(err)}`;
|
|
1210
|
+
throw new Error(esbuildLoadError, { cause: err });
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
let importmapScript = null;
|
|
1214
|
+
function buildImportmap() {
|
|
1215
|
+
const mappings = {};
|
|
1216
|
+
Object.assign(mappings, importmapOverrides);
|
|
1217
|
+
for (const { path, config } of entries) {
|
|
1218
|
+
const entryName = path.split("/").pop() ?? "";
|
|
1219
|
+
if (entryName.includes("vendor") || config.external && config.external.length === 0 || !config.external && config.bundle !== false) {
|
|
1220
|
+
if (!mappings["react"]) mappings["react"] = path;
|
|
1221
|
+
if (!mappings["react/jsx-runtime"]) mappings["react/jsx-runtime"] = path;
|
|
1222
|
+
if (!mappings["react-dom/client"]) mappings["react-dom/client"] = path;
|
|
1223
|
+
if (!mappings["react-dom"]) mappings["react-dom"] = path;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const json = JSON.stringify({ imports: mappings });
|
|
1227
|
+
return `<script type="importmap">${json}</script>`;
|
|
1228
|
+
}
|
|
1229
|
+
async function compile(entry) {
|
|
1230
|
+
const esbuild = await getEsbuild();
|
|
1231
|
+
const isClientRouter = !!entry.clientRouter;
|
|
1232
|
+
let entryAbs;
|
|
1233
|
+
let buildOpts;
|
|
1234
|
+
if (isClientRouter) {
|
|
1235
|
+
const cr = entry.clientRouter;
|
|
1236
|
+
const virtualModule = "weifuwu:client-entry";
|
|
1237
|
+
entryAbs = virtualModule;
|
|
1238
|
+
const layoutImport = `import * as _layout from '${cr.layout}'
|
|
1239
|
+
const _lx = Object.entries(_layout).filter(([,v]) => typeof v === 'function')
|
|
1240
|
+
const Layout = _layout.default || (_lx[0]?.[1])`;
|
|
1241
|
+
let routesCode;
|
|
1242
|
+
if (cr.pages) {
|
|
1243
|
+
const entries2 = Object.entries(cr.pages).map(
|
|
1244
|
+
([path, component]) => ` '${path}': () => import('${component}'),`
|
|
1245
|
+
);
|
|
1246
|
+
routesCode = `const routes = {
|
|
1247
|
+
${entries2.join("\n")}
|
|
1248
|
+
}`;
|
|
1249
|
+
} else if (cr.routes) {
|
|
1250
|
+
routesCode = `import { routes } from '${cr.routes}'`;
|
|
1251
|
+
} else {
|
|
1252
|
+
routesCode = "const routes = {}";
|
|
1253
|
+
}
|
|
1254
|
+
const fallbackLine = cr.fallback ? `const fallback = () => import('${cr.fallback}')` : "";
|
|
1255
|
+
const fallbackOpt = cr.fallback ? " fallback," : "";
|
|
1256
|
+
const generatedCode = [
|
|
1257
|
+
`import { createBrowserRouter } from 'weifuwu/react/client'`,
|
|
1258
|
+
layoutImport,
|
|
1259
|
+
routesCode,
|
|
1260
|
+
fallbackLine,
|
|
1261
|
+
"",
|
|
1262
|
+
"createBrowserRouter({",
|
|
1263
|
+
" layout: Layout,",
|
|
1264
|
+
" routes,",
|
|
1265
|
+
fallbackOpt,
|
|
1266
|
+
"})"
|
|
1267
|
+
].filter(Boolean).join("\n");
|
|
1268
|
+
buildOpts = {
|
|
1269
|
+
entryPoints: [virtualModule],
|
|
1270
|
+
bundle: entry.bundle ?? true,
|
|
1271
|
+
external: entry.external ?? [],
|
|
1272
|
+
minify: entry.minify ?? true,
|
|
1273
|
+
platform: entry.platform ?? "browser",
|
|
1274
|
+
format: entry.format ?? "esm",
|
|
1275
|
+
sourcemap: entry.sourcemap ?? false,
|
|
1276
|
+
splitting: entry.splitting ?? false,
|
|
1277
|
+
...entry.splitting ? { outdir: resolve2(".weifuwu-esbuild-out") } : {},
|
|
1278
|
+
define: entry.define,
|
|
1279
|
+
loader: entry.loader,
|
|
1280
|
+
write: false,
|
|
1281
|
+
logLevel: "silent",
|
|
1282
|
+
plugins: [{
|
|
1283
|
+
name: "weifuwu-client-router",
|
|
1284
|
+
setup(build) {
|
|
1285
|
+
build.onResolve({ filter: new RegExp(`^${virtualModule.replace(/:/g, "\\:")}$`) }, () => ({
|
|
1286
|
+
path: virtualModule,
|
|
1287
|
+
namespace: "weifuwu-client"
|
|
1288
|
+
}));
|
|
1289
|
+
build.onLoad({ filter: /.*/, namespace: "weifuwu-client" }, () => ({
|
|
1290
|
+
contents: generatedCode,
|
|
1291
|
+
loader: "ts",
|
|
1292
|
+
resolveDir: process.cwd()
|
|
1293
|
+
}));
|
|
1294
|
+
}
|
|
1295
|
+
}]
|
|
1296
|
+
};
|
|
1297
|
+
} else {
|
|
1298
|
+
entryAbs = resolve2(entry.entry);
|
|
1299
|
+
buildOpts = {
|
|
1300
|
+
entryPoints: [entryAbs],
|
|
1301
|
+
bundle: entry.bundle ?? true,
|
|
1302
|
+
external: entry.external ?? [],
|
|
1303
|
+
minify: entry.minify ?? true,
|
|
1304
|
+
platform: entry.platform ?? "browser",
|
|
1305
|
+
format: entry.format ?? "esm",
|
|
1306
|
+
sourcemap: entry.sourcemap ?? false,
|
|
1307
|
+
splitting: entry.splitting ?? false,
|
|
1308
|
+
...entry.splitting ? { outdir: dirname(entryAbs) } : {},
|
|
1309
|
+
define: entry.define,
|
|
1310
|
+
loader: entry.loader,
|
|
1311
|
+
write: false,
|
|
1312
|
+
logLevel: "silent"
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
const result = await esbuild.build(buildOpts);
|
|
1316
|
+
const msgs = [];
|
|
1317
|
+
for (const w of result.warnings) {
|
|
1318
|
+
const loc = w.location ? ` at ${relative(".", w.location.file ?? "")}:${w.location.line}:${w.location.column}` : "";
|
|
1319
|
+
msgs.push(`[warn] ${w.text}${loc}`);
|
|
1320
|
+
}
|
|
1321
|
+
for (const e of result.errors) {
|
|
1322
|
+
const loc = e.location ? ` at ${relative(".", e.location.file ?? "")}:${e.location.line}:${e.location.column}` : "";
|
|
1323
|
+
msgs.push(`[error] ${e.text}${loc}`);
|
|
1324
|
+
}
|
|
1325
|
+
if (result.errors.length > 0) {
|
|
1326
|
+
throw new Error(msgs.join("\n"));
|
|
1327
|
+
}
|
|
1328
|
+
const outputFiles = result.outputFiles;
|
|
1329
|
+
const entryFile = outputFiles.find((f) => f.path === entryAbs) ?? outputFiles[0];
|
|
1330
|
+
const code = entryFile?.text ?? "";
|
|
1331
|
+
let chunks;
|
|
1332
|
+
if (outputFiles.length > 1) {
|
|
1333
|
+
chunks = /* @__PURE__ */ new Map();
|
|
1334
|
+
for (const f of outputFiles) {
|
|
1335
|
+
if (f === entryFile) continue;
|
|
1336
|
+
const chunkName = f.path.split("/").pop() ?? f.path;
|
|
1337
|
+
chunks.set(chunkName, { code: f.text, etag: `"esbuild-${hashCode(f.text)}"` });
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
const etag = `"esbuild-${hashCode(code)}"`;
|
|
1341
|
+
if (msgs.length > 0) {
|
|
1342
|
+
console.error("[esbuildDev]", msgs.join("\n"));
|
|
1343
|
+
}
|
|
1344
|
+
return { code, etag, chunks };
|
|
1345
|
+
}
|
|
1346
|
+
function isCacheValid2(cached) {
|
|
1347
|
+
return Promise.all(
|
|
1348
|
+
[...cached.files].map(async ([file, mtime]) => {
|
|
1349
|
+
try {
|
|
1350
|
+
const s = await stat(file);
|
|
1351
|
+
return s.mtimeMs === mtime;
|
|
1352
|
+
} catch {
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
})
|
|
1356
|
+
).then((results) => results.every(Boolean));
|
|
1357
|
+
}
|
|
1358
|
+
return async (req, ctx, next) => {
|
|
1359
|
+
const url = new URL(req.url);
|
|
1360
|
+
const pathname = url.pathname;
|
|
1361
|
+
if (importmap && pathname === "/assets/importmap") {
|
|
1362
|
+
if (!importmapScript) importmapScript = buildImportmap();
|
|
1363
|
+
return new Response(importmapScript, {
|
|
1364
|
+
headers: {
|
|
1365
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
1366
|
+
"Cache-Control": "no-cache"
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
const matched = entries.find((e) => e.path === pathname);
|
|
1371
|
+
const chunkName = pathname.split("/").pop() ?? "";
|
|
1372
|
+
const chunkOwner = chunkMap.get(chunkName);
|
|
1373
|
+
if (!matched && !chunkOwner) return next(req, ctx);
|
|
1374
|
+
if (!matched && chunkOwner) {
|
|
1375
|
+
const ownerCache = cacheStore.get(chunkOwner);
|
|
1376
|
+
const chunk = ownerCache?.chunks?.get(chunkName);
|
|
1377
|
+
if (chunk) {
|
|
1378
|
+
if (req.headers.get("if-none-match") === chunk.etag) {
|
|
1379
|
+
return new Response(null, { status: 304 });
|
|
1380
|
+
}
|
|
1381
|
+
return new Response(chunk.code, {
|
|
1382
|
+
headers: {
|
|
1383
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
1384
|
+
ETag: chunk.etag,
|
|
1385
|
+
"Cache-Control": "no-cache"
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
return next(req, ctx);
|
|
1390
|
+
}
|
|
1391
|
+
const { config } = matched;
|
|
1392
|
+
try {
|
|
1393
|
+
if (cache === "memory") {
|
|
1394
|
+
const cached = cacheStore.get(pathname);
|
|
1395
|
+
if (cached) {
|
|
1396
|
+
const valid = await isCacheValid2(cached);
|
|
1397
|
+
if (valid) {
|
|
1398
|
+
if (req.headers.get("if-none-match") === cached.etag) {
|
|
1399
|
+
return new Response(null, { status: 304 });
|
|
1400
|
+
}
|
|
1401
|
+
return new Response(cached.code, {
|
|
1402
|
+
headers: {
|
|
1403
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
1404
|
+
ETag: cached.etag,
|
|
1405
|
+
"Cache-Control": "no-cache"
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
for (const [cn] of cached.chunks ?? []) chunkMap.delete(cn);
|
|
1410
|
+
cacheStore.delete(pathname);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
const { code, etag, chunks } = await compile(config);
|
|
1414
|
+
const depEntry = config.clientRouter?.routes ?? (config.clientRouter?.pages ? Object.values(config.clientRouter.pages)[0] : void 0) ?? config.entry;
|
|
1415
|
+
const deps = await collectDeps(resolve2(depEntry));
|
|
1416
|
+
if (cache === "memory") {
|
|
1417
|
+
cacheStore.set(pathname, { code, etag, files: deps, chunks });
|
|
1418
|
+
if (chunks) {
|
|
1419
|
+
for (const [cn] of chunks) chunkMap.set(cn, pathname);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return new Response(code, {
|
|
1423
|
+
headers: {
|
|
1424
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
1425
|
+
ETag: etag,
|
|
1426
|
+
"Cache-Control": "no-cache"
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1431
|
+
const html = errorTemplate(message);
|
|
1432
|
+
return new Response(html, {
|
|
1433
|
+
status: 500,
|
|
1434
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function hashCode(s) {
|
|
1440
|
+
let hash = 0;
|
|
1441
|
+
for (let i = 0; i < s.length; i++) {
|
|
1442
|
+
const ch = s.charCodeAt(i);
|
|
1443
|
+
hash = (hash << 5) - hash + ch;
|
|
1444
|
+
hash |= 0;
|
|
1445
|
+
}
|
|
1446
|
+
return Math.abs(hash).toString(36);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/middleware/tailwind-dev.ts
|
|
1450
|
+
import { resolve as resolve3 } from "node:path";
|
|
1451
|
+
import { stat as stat2, readFile as readFile2, glob } from "node:fs/promises";
|
|
1452
|
+
function escapeHtml2(s) {
|
|
1453
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1454
|
+
}
|
|
1455
|
+
function defaultErrorTemplate2(errors) {
|
|
1456
|
+
return `<!DOCTYPE html>
|
|
1457
|
+
<html>
|
|
1458
|
+
<head>
|
|
1459
|
+
<meta charset="utf-8">
|
|
1460
|
+
<title>Tailwind Build Error</title>
|
|
1461
|
+
<style>
|
|
1462
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1463
|
+
body { font-family: system-ui; background: #1a1a2e; color: #e0e0e0; padding: 2rem; }
|
|
1464
|
+
.overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
1465
|
+
.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); }
|
|
1466
|
+
h1 { color: #06b6d4; font-size: 1.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
1467
|
+
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; }
|
|
1468
|
+
</style>
|
|
1469
|
+
</head>
|
|
1470
|
+
<body>
|
|
1471
|
+
<div class="overlay">
|
|
1472
|
+
<div class="card">
|
|
1473
|
+
<h1>\u{1F3A8} Tailwind CSS Build Error</h1>
|
|
1474
|
+
<pre>${escapeHtml2(errors)}</pre>
|
|
1475
|
+
</div>
|
|
1476
|
+
</div>
|
|
1477
|
+
</body>
|
|
1478
|
+
</html>`;
|
|
1479
|
+
}
|
|
1480
|
+
var TW_CANDIDATE_RE = /[a-z][\w-]*(?::[a-z][\w-]*)*(?:-\[[^\]]*\])*(?:\/[0-9]+)?/gi;
|
|
1481
|
+
var CLASS_CONTEXT_RE = /(?:class(?:Name)?\s*[=:]\s*["'`]([^"'`]+)["'`]|["'`]([^"'`\n]{2,})["'`])/g;
|
|
1482
|
+
async function extractCandidates(patterns, root) {
|
|
1483
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1484
|
+
for (const pattern of patterns) {
|
|
1485
|
+
const resolved = resolve3(root, pattern);
|
|
1486
|
+
try {
|
|
1487
|
+
for await (const file of glob(resolved)) {
|
|
1488
|
+
try {
|
|
1489
|
+
const content = await readFile2(file, "utf-8");
|
|
1490
|
+
for (const m of content.matchAll(CLASS_CONTEXT_RE)) {
|
|
1491
|
+
const str = m[1] ?? m[2];
|
|
1492
|
+
if (!str) continue;
|
|
1493
|
+
for (const c of str.matchAll(TW_CANDIDATE_RE)) {
|
|
1494
|
+
seen.add(c[0]);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
} catch {
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
} catch {
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return [...seen];
|
|
1504
|
+
}
|
|
1505
|
+
async function collectFileMtimes(files) {
|
|
1506
|
+
const map = /* @__PURE__ */ new Map();
|
|
1507
|
+
await Promise.all(
|
|
1508
|
+
files.map(async (f) => {
|
|
1509
|
+
try {
|
|
1510
|
+
const s = await stat2(f);
|
|
1511
|
+
map.set(f, s.mtimeMs);
|
|
1512
|
+
} catch {
|
|
1513
|
+
}
|
|
1514
|
+
})
|
|
1515
|
+
);
|
|
1516
|
+
return map;
|
|
1517
|
+
}
|
|
1518
|
+
function isCacheValid(cached) {
|
|
1519
|
+
return Promise.all(
|
|
1520
|
+
[...cached.files].map(async ([file, mtime]) => {
|
|
1521
|
+
try {
|
|
1522
|
+
const s = await stat2(file);
|
|
1523
|
+
return s.mtimeMs === mtime;
|
|
1524
|
+
} catch {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
})
|
|
1528
|
+
).then((results) => results.every(Boolean));
|
|
1529
|
+
}
|
|
1530
|
+
function tailwindDev(opts) {
|
|
1531
|
+
const cacheMode = opts.cache ?? "memory";
|
|
1532
|
+
const errorTemplate = opts.errorTemplate ?? defaultErrorTemplate2;
|
|
1533
|
+
const entries = Object.entries(opts.entries).map(([p, e]) => {
|
|
1534
|
+
const path = p.startsWith("/") ? p : "/" + p;
|
|
1535
|
+
const config = typeof e === "string" ? { entry: e } : e;
|
|
1536
|
+
return { path, config };
|
|
1537
|
+
});
|
|
1538
|
+
const cacheStore = /* @__PURE__ */ new Map();
|
|
1539
|
+
let compileFn = null;
|
|
1540
|
+
let loadError = null;
|
|
1541
|
+
async function getCompile() {
|
|
1542
|
+
if (compileFn) return compileFn;
|
|
1543
|
+
try {
|
|
1544
|
+
const mod = await import("@tailwindcss/node");
|
|
1545
|
+
compileFn = mod.compile;
|
|
1546
|
+
return compileFn;
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
loadError = `@tailwindcss/node is not installed. Run: npm install -D tailwindcss @tailwindcss/node
|
|
1549
|
+
|
|
1550
|
+
${String(err)}`;
|
|
1551
|
+
throw new Error(loadError, { cause: err });
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
async function compileCss(entry) {
|
|
1555
|
+
const compile = await getCompile();
|
|
1556
|
+
const entryAbs = resolve3(entry.entry);
|
|
1557
|
+
const cssSource = await readFile2(entryAbs, "utf-8");
|
|
1558
|
+
const baseDir = resolve3(entry.entry, "..");
|
|
1559
|
+
const deps = [entryAbs];
|
|
1560
|
+
const result = await compile(cssSource, {
|
|
1561
|
+
base: baseDir,
|
|
1562
|
+
onDependency: (p) => {
|
|
1563
|
+
if (!deps.includes(p)) deps.push(p);
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
const contentPatterns = entry.content ?? [];
|
|
1567
|
+
for (const src of result.sources) {
|
|
1568
|
+
if (!src.negated) {
|
|
1569
|
+
contentPatterns.push(resolve3(src.base, src.pattern));
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
let candidates = [];
|
|
1573
|
+
if (contentPatterns.length > 0) {
|
|
1574
|
+
candidates = await extractCandidates(contentPatterns, baseDir);
|
|
1575
|
+
}
|
|
1576
|
+
const css = result.build(candidates);
|
|
1577
|
+
for (const pattern of contentPatterns) {
|
|
1578
|
+
try {
|
|
1579
|
+
for await (const file of glob(resolve3(baseDir, pattern))) {
|
|
1580
|
+
if (!deps.includes(file)) deps.push(file);
|
|
1581
|
+
}
|
|
1582
|
+
} catch {
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
const etag = `"tw-${hashCode2(css)}"`;
|
|
1586
|
+
return { css, etag, files: deps };
|
|
1587
|
+
}
|
|
1588
|
+
return async (req, ctx, next) => {
|
|
1589
|
+
const url = new URL(req.url);
|
|
1590
|
+
const pathname = url.pathname;
|
|
1591
|
+
const matched = entries.find((e) => e.path === pathname);
|
|
1592
|
+
if (!matched) return next(req, ctx);
|
|
1593
|
+
const { config } = matched;
|
|
1594
|
+
try {
|
|
1595
|
+
if (cacheMode === "memory") {
|
|
1596
|
+
const cached = cacheStore.get(pathname);
|
|
1597
|
+
if (cached) {
|
|
1598
|
+
const valid = await isCacheValid(cached);
|
|
1599
|
+
if (valid) {
|
|
1600
|
+
if (req.headers.get("if-none-match") === cached.etag) {
|
|
1601
|
+
return new Response(null, { status: 304 });
|
|
1602
|
+
}
|
|
1603
|
+
return new Response(cached.css, {
|
|
1604
|
+
headers: {
|
|
1605
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
1606
|
+
ETag: cached.etag,
|
|
1607
|
+
"Cache-Control": "no-cache"
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
cacheStore.delete(pathname);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
const { css, etag, files } = await compileCss(config);
|
|
1615
|
+
if (cacheMode === "memory") {
|
|
1616
|
+
const mtimes = await collectFileMtimes(files);
|
|
1617
|
+
cacheStore.set(pathname, { css, etag, files: mtimes });
|
|
1618
|
+
}
|
|
1619
|
+
return new Response(css, {
|
|
1620
|
+
headers: {
|
|
1621
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
1622
|
+
ETag: etag,
|
|
1623
|
+
"Cache-Control": "no-cache"
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
} catch (err) {
|
|
1627
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1628
|
+
const html = errorTemplate(message);
|
|
1629
|
+
return new Response(html, {
|
|
1630
|
+
status: 500,
|
|
1631
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function hashCode2(s) {
|
|
1637
|
+
let hash = 0;
|
|
1638
|
+
for (let i = 0; i < s.length; i++) {
|
|
1639
|
+
const ch = s.charCodeAt(i);
|
|
1640
|
+
hash = (hash << 5) - hash + ch;
|
|
1641
|
+
hash |= 0;
|
|
1642
|
+
}
|
|
1643
|
+
return Math.abs(hash).toString(36);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1102
1646
|
// src/middleware/rate-limit.ts
|
|
1103
1647
|
function defaultKey(_req, _ctx) {
|
|
1104
1648
|
const forwarded = _req.headers.get("x-forwarded-for");
|
|
@@ -1111,7 +1655,7 @@ function defaultKey(_req, _ctx) {
|
|
|
1111
1655
|
}
|
|
1112
1656
|
function rateLimit(options) {
|
|
1113
1657
|
const max = options?.max ?? 100;
|
|
1114
|
-
const
|
|
1658
|
+
const window = options?.window ?? 6e4;
|
|
1115
1659
|
const getKey = options?.key ?? defaultKey;
|
|
1116
1660
|
const message = options?.message ?? "Too Many Requests";
|
|
1117
1661
|
const storeType = options?.store ?? "memory";
|
|
@@ -1138,7 +1682,7 @@ function rateLimit(options) {
|
|
|
1138
1682
|
}
|
|
1139
1683
|
}
|
|
1140
1684
|
},
|
|
1141
|
-
Math.min(
|
|
1685
|
+
Math.min(window, 3e4)
|
|
1142
1686
|
) : null;
|
|
1143
1687
|
if (interval?.unref) interval.unref();
|
|
1144
1688
|
async function checkAndIncrement(key) {
|
|
@@ -1147,16 +1691,16 @@ function rateLimit(options) {
|
|
|
1147
1691
|
const redisKey = `${keyPrefix}${key}`;
|
|
1148
1692
|
const count = await redis2.incr(redisKey);
|
|
1149
1693
|
if (count === 1) {
|
|
1150
|
-
await redis2.pexpire(redisKey,
|
|
1694
|
+
await redis2.pexpire(redisKey, window);
|
|
1151
1695
|
}
|
|
1152
1696
|
const pttl = await redis2.pttl(redisKey);
|
|
1153
|
-
const reset = pttl > 0 ? now + pttl : now +
|
|
1697
|
+
const reset = pttl > 0 ? now + pttl : now + window;
|
|
1154
1698
|
return { count, reset };
|
|
1155
1699
|
}
|
|
1156
1700
|
const entry = hits.get(key);
|
|
1157
1701
|
if (!entry || entry.reset < now) {
|
|
1158
|
-
hits.set(key, { count: 1, reset: now +
|
|
1159
|
-
return { count: 1, reset: now +
|
|
1702
|
+
hits.set(key, { count: 1, reset: now + window });
|
|
1703
|
+
return { count: 1, reset: now + window };
|
|
1160
1704
|
}
|
|
1161
1705
|
entry.count++;
|
|
1162
1706
|
return { count: entry.count, reset: entry.reset };
|
|
@@ -1879,339 +2423,380 @@ function queue(opts) {
|
|
|
1879
2423
|
}
|
|
1880
2424
|
|
|
1881
2425
|
// src/react/index.ts
|
|
1882
|
-
import {
|
|
1883
|
-
|
|
1884
|
-
// src/react/render.ts
|
|
1885
|
-
import {
|
|
1886
|
-
createElement,
|
|
1887
|
-
Fragment
|
|
1888
|
-
} from "react";
|
|
1889
|
-
import { renderToString, renderToReadableStream } from "react-dom/server";
|
|
1890
|
-
|
|
1891
|
-
// src/react/context.ts
|
|
1892
|
-
import { createContext } from "react";
|
|
1893
|
-
var CTX_KEY = /* @__PURE__ */ Symbol.for("weifuwu.react.ServerDataContext");
|
|
1894
|
-
function getServerDataContext() {
|
|
1895
|
-
const globalStore = globalThis;
|
|
1896
|
-
if (globalStore[CTX_KEY]) return globalStore[CTX_KEY];
|
|
1897
|
-
const ctx = createContext({});
|
|
1898
|
-
ctx.displayName = "ServerData";
|
|
1899
|
-
globalStore[CTX_KEY] = ctx;
|
|
1900
|
-
return ctx;
|
|
1901
|
-
}
|
|
1902
|
-
var ServerDataContext = getServerDataContext();
|
|
2426
|
+
import { createElement as createElement2 } from "react";
|
|
2427
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
1903
2428
|
|
|
1904
|
-
// src/react/
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2429
|
+
// src/react/compile.ts
|
|
2430
|
+
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3 } from "node:fs/promises";
|
|
2431
|
+
import { resolve as resolve4, join as join2, dirname as dirname2 } from "node:path";
|
|
2432
|
+
import { createHash } from "node:crypto";
|
|
2433
|
+
import { existsSync } from "node:fs";
|
|
2434
|
+
var EXTERNAL_PKGS = [
|
|
2435
|
+
"react",
|
|
2436
|
+
"react/jsx-runtime",
|
|
2437
|
+
"react-dom",
|
|
2438
|
+
"react-dom/server",
|
|
2439
|
+
"react-dom/client",
|
|
2440
|
+
"weifuwu",
|
|
2441
|
+
"weifuwu/react"
|
|
2442
|
+
];
|
|
2443
|
+
var FRAMEWORK_IMPORTS = [
|
|
2444
|
+
"react",
|
|
2445
|
+
"react/jsx-runtime",
|
|
2446
|
+
"react-dom",
|
|
2447
|
+
"react-dom/server",
|
|
2448
|
+
"react-dom/client",
|
|
2449
|
+
"weifuwu",
|
|
2450
|
+
"weifuwu/react"
|
|
2451
|
+
];
|
|
2452
|
+
var memCache = /* @__PURE__ */ new Map();
|
|
2453
|
+
function buildResolveMap() {
|
|
2454
|
+
const map = {};
|
|
2455
|
+
for (const spec of FRAMEWORK_IMPORTS) {
|
|
2456
|
+
try {
|
|
2457
|
+
map[spec] = import.meta.resolve(spec);
|
|
2458
|
+
} catch {
|
|
1930
2459
|
}
|
|
1931
2460
|
}
|
|
1932
|
-
return
|
|
2461
|
+
return map;
|
|
1933
2462
|
}
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
result = result.replace("
|
|
2463
|
+
var _resolveMap = null;
|
|
2464
|
+
function resolveMap() {
|
|
2465
|
+
if (!_resolveMap) _resolveMap = buildResolveMap();
|
|
2466
|
+
return _resolveMap;
|
|
2467
|
+
}
|
|
2468
|
+
function rewriteImports(code) {
|
|
2469
|
+
const map = resolveMap();
|
|
2470
|
+
let result = code;
|
|
2471
|
+
for (const [spec, url] of Object.entries(map)) {
|
|
2472
|
+
const quoted = JSON.stringify(spec);
|
|
2473
|
+
const escaped = quoted.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2474
|
+
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)}`);
|
|
1946
2475
|
}
|
|
1947
2476
|
return result;
|
|
1948
2477
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
let rest = headTags;
|
|
1953
|
-
const titleMatch = headTags.match(/<title>[^<]*<\/title>/);
|
|
1954
|
-
if (titleMatch) {
|
|
1955
|
-
titleTag = titleMatch[0];
|
|
1956
|
-
rest = headTags.replace(titleMatch[0], "");
|
|
1957
|
-
}
|
|
1958
|
-
let titleInjected = !titleTag;
|
|
1959
|
-
let headInjected = !rest;
|
|
1960
|
-
return new ReadableStream({
|
|
1961
|
-
async start(controller) {
|
|
1962
|
-
const reader = sourceStream.getReader();
|
|
1963
|
-
let buffer = "";
|
|
1964
|
-
try {
|
|
1965
|
-
while (true) {
|
|
1966
|
-
const { done, value } = await reader.read();
|
|
1967
|
-
if (done) break;
|
|
1968
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1969
|
-
if (!titleInjected) {
|
|
1970
|
-
const idx = buffer.indexOf("<title>");
|
|
1971
|
-
const endIdx = buffer.indexOf("</title>", idx);
|
|
1972
|
-
if (idx !== -1 && endIdx !== -1) {
|
|
1973
|
-
controller.enqueue(
|
|
1974
|
-
encoder.encode(buffer.slice(0, idx) + titleTag + buffer.slice(endIdx + 8))
|
|
1975
|
-
);
|
|
1976
|
-
buffer = "";
|
|
1977
|
-
titleInjected = true;
|
|
1978
|
-
continue;
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
if (!headInjected) {
|
|
1982
|
-
const idx = buffer.indexOf("</head>");
|
|
1983
|
-
if (idx !== -1) {
|
|
1984
|
-
controller.enqueue(
|
|
1985
|
-
encoder.encode(buffer.slice(0, idx) + rest + buffer.slice(idx))
|
|
1986
|
-
);
|
|
1987
|
-
buffer = "";
|
|
1988
|
-
headInjected = true;
|
|
1989
|
-
continue;
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
if (titleInjected && headInjected && buffer) {
|
|
1993
|
-
controller.enqueue(encoder.encode(buffer));
|
|
1994
|
-
buffer = "";
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
if (buffer) controller.enqueue(encoder.encode(buffer));
|
|
1998
|
-
controller.close();
|
|
1999
|
-
} catch (err) {
|
|
2000
|
-
controller.error(err);
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
});
|
|
2478
|
+
var _cacheDir = null;
|
|
2479
|
+
function setReactCacheDir(dir) {
|
|
2480
|
+
_cacheDir = dir;
|
|
2004
2481
|
}
|
|
2005
|
-
function
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
}
|
|
2010
|
-
const json = JSON.stringify(data).replace(/</g, "\\u003c");
|
|
2011
|
-
const dataScript = createElement("script", {
|
|
2012
|
-
id: "__WEIFUWU_DATA__",
|
|
2013
|
-
type: "application/json",
|
|
2014
|
-
dangerouslySetInnerHTML: { __html: json }
|
|
2015
|
-
});
|
|
2016
|
-
return createElement(
|
|
2017
|
-
ServerDataContext.Provider,
|
|
2018
|
-
{ value: data },
|
|
2019
|
-
createElement(Fragment, null, wrapped, dataScript)
|
|
2020
|
-
);
|
|
2482
|
+
function getCacheDir() {
|
|
2483
|
+
if (_cacheDir) return _cacheDir;
|
|
2484
|
+
_cacheDir = join2(process.cwd(), "node_modules", ".weifuwu", "react");
|
|
2485
|
+
return _cacheDir;
|
|
2021
2486
|
}
|
|
2022
|
-
function
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2487
|
+
async function loadTsxModule(entryPath) {
|
|
2488
|
+
const abs = resolve4(entryPath);
|
|
2489
|
+
let source;
|
|
2490
|
+
try {
|
|
2491
|
+
source = await readFile3(abs, "utf-8");
|
|
2492
|
+
} catch {
|
|
2493
|
+
throw new Error(`Cannot read file: ${entryPath}`);
|
|
2494
|
+
}
|
|
2495
|
+
const sourceHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
2496
|
+
const memCached = memCache.get(abs);
|
|
2497
|
+
if (memCached && memCached.sourceHash === sourceHash) {
|
|
2498
|
+
return memCached.mod;
|
|
2499
|
+
}
|
|
2500
|
+
const tmpDir = getCacheDir();
|
|
2501
|
+
const tmpFile = join2(tmpDir, `${sourceHash}.mjs`);
|
|
2502
|
+
if (existsSync(tmpFile)) {
|
|
2503
|
+
try {
|
|
2504
|
+
const mod2 = await import(tmpFile + "?" + sourceHash);
|
|
2505
|
+
memCache.set(abs, { sourceHash, mod: mod2 });
|
|
2506
|
+
return mod2;
|
|
2507
|
+
} catch {
|
|
2033
2508
|
}
|
|
2509
|
+
}
|
|
2510
|
+
const esbuild = await import("esbuild");
|
|
2511
|
+
const result = await esbuild.build({
|
|
2512
|
+
entryPoints: [abs],
|
|
2513
|
+
bundle: true,
|
|
2514
|
+
write: false,
|
|
2515
|
+
format: "esm",
|
|
2516
|
+
platform: "node",
|
|
2517
|
+
jsx: "automatic",
|
|
2518
|
+
external: EXTERNAL_PKGS,
|
|
2519
|
+
logLevel: "silent"
|
|
2034
2520
|
});
|
|
2521
|
+
let code = result.outputFiles[0]?.text ?? "";
|
|
2522
|
+
if (!code) throw new Error(`esbuild produced empty output for ${entryPath}`);
|
|
2523
|
+
code = rewriteImports(code);
|
|
2524
|
+
await mkdir2(dirname2(tmpFile), { recursive: true });
|
|
2525
|
+
await writeFile2(tmpFile, code);
|
|
2526
|
+
const mod = await import(tmpFile + "?" + sourceHash);
|
|
2527
|
+
memCache.set(abs, { sourceHash, mod });
|
|
2528
|
+
return mod;
|
|
2035
2529
|
}
|
|
2036
|
-
async function
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
const
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
const combined = new ReadableStream({
|
|
2044
|
-
async start(controller) {
|
|
2045
|
-
controller.enqueue(doctype);
|
|
2046
|
-
try {
|
|
2047
|
-
const reader = stream.getReader();
|
|
2048
|
-
while (true) {
|
|
2049
|
-
const { done, value } = await reader.read();
|
|
2050
|
-
if (done) {
|
|
2051
|
-
controller.close();
|
|
2052
|
-
break;
|
|
2053
|
-
}
|
|
2054
|
-
controller.enqueue(value);
|
|
2055
|
-
}
|
|
2056
|
-
} catch (err) {
|
|
2057
|
-
controller.error(err);
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
});
|
|
2061
|
-
return new Response(combined, {
|
|
2062
|
-
status: options.status ?? 200,
|
|
2063
|
-
headers: {
|
|
2064
|
-
"content-type": "text/html; charset=utf-8",
|
|
2065
|
-
"transfer-encoding": "chunked",
|
|
2066
|
-
...options.headers
|
|
2067
|
-
}
|
|
2068
|
-
});
|
|
2530
|
+
async function loadTsxComponent(entryPath) {
|
|
2531
|
+
const mod = await loadTsxModule(entryPath);
|
|
2532
|
+
if (mod.default && typeof mod.default === "function") return mod.default;
|
|
2533
|
+
for (const [, val] of Object.entries(mod)) {
|
|
2534
|
+
if (typeof val === "function") return val;
|
|
2535
|
+
}
|
|
2536
|
+
throw new Error(`No component export found in ${entryPath}. Export a default or named component function.`);
|
|
2069
2537
|
}
|
|
2070
2538
|
|
|
2071
|
-
// src/react/
|
|
2072
|
-
import {
|
|
2073
|
-
|
|
2074
|
-
|
|
2539
|
+
// src/react/context.ts
|
|
2540
|
+
import { createContext } from "react";
|
|
2541
|
+
var CTX_KEY = /* @__PURE__ */ Symbol.for("weifuwu.react.ServerDataContext");
|
|
2542
|
+
function getServerDataContext() {
|
|
2543
|
+
const globalStore = globalThis;
|
|
2544
|
+
if (globalStore[CTX_KEY]) return globalStore[CTX_KEY];
|
|
2545
|
+
const ctx = createContext({});
|
|
2546
|
+
ctx.displayName = "ServerData";
|
|
2547
|
+
globalStore[CTX_KEY] = ctx;
|
|
2548
|
+
return ctx;
|
|
2075
2549
|
}
|
|
2076
|
-
|
|
2077
|
-
// src/react/navigation.ts
|
|
2078
|
-
import {
|
|
2079
|
-
createElement as createElement2,
|
|
2080
|
-
createContext as createContext2,
|
|
2081
|
-
useContext as useContext2
|
|
2082
|
-
} from "react";
|
|
2550
|
+
var ServerDataContext = getServerDataContext();
|
|
2083
2551
|
|
|
2084
2552
|
// src/react/error-boundary.ts
|
|
2085
|
-
import { Component } from "react";
|
|
2553
|
+
import { createElement, Component } from "react";
|
|
2086
2554
|
var ErrorBoundary = class extends Component {
|
|
2087
|
-
state = { error: null };
|
|
2555
|
+
state = { hasError: false, error: null };
|
|
2088
2556
|
static getDerivedStateFromError(error) {
|
|
2089
|
-
return { error };
|
|
2557
|
+
return { hasError: true, error };
|
|
2090
2558
|
}
|
|
2091
|
-
componentDidCatch(error
|
|
2092
|
-
this.props.onError?.(error
|
|
2559
|
+
componentDidCatch(error) {
|
|
2560
|
+
this.props.onError?.(error);
|
|
2093
2561
|
}
|
|
2094
2562
|
render() {
|
|
2095
|
-
if (this.state.
|
|
2096
|
-
return this.props.fallback;
|
|
2563
|
+
if (this.state.hasError) {
|
|
2564
|
+
if (this.props.fallback) return this.props.fallback;
|
|
2565
|
+
return createElement(
|
|
2566
|
+
"div",
|
|
2567
|
+
{ style: { padding: "2rem", textAlign: "center" } },
|
|
2568
|
+
createElement("h1", null, "Something went wrong"),
|
|
2569
|
+
createElement("p", null, this.state.error?.message ?? "Unknown error")
|
|
2570
|
+
);
|
|
2097
2571
|
}
|
|
2098
2572
|
return this.props.children;
|
|
2099
2573
|
}
|
|
2100
2574
|
};
|
|
2101
2575
|
|
|
2102
|
-
// src/react/
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
return useContext2(RouterContext)?.params ?? {};
|
|
2576
|
+
// src/react/hooks.ts
|
|
2577
|
+
import { useContext } from "react";
|
|
2578
|
+
function useServerData() {
|
|
2579
|
+
return useContext(ServerDataContext);
|
|
2107
2580
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2581
|
+
|
|
2582
|
+
// src/react/index.ts
|
|
2583
|
+
function HtmlShell({ children, importMap, stylesheets, data }) {
|
|
2584
|
+
const headChildren = [
|
|
2585
|
+
createElement2("meta", { charSet: "utf-8", key: "charset" }),
|
|
2586
|
+
createElement2("meta", { name: "viewport", content: "width=device-width, initial-scale=1", key: "viewport" })
|
|
2587
|
+
];
|
|
2588
|
+
if (stylesheets) {
|
|
2589
|
+
for (const href of stylesheets) {
|
|
2590
|
+
headChildren.push(
|
|
2591
|
+
createElement2("link", { rel: "stylesheet", href, key: `css-${href}` })
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
if (importMap) {
|
|
2596
|
+
headChildren.push(
|
|
2597
|
+
createElement2("script", {
|
|
2598
|
+
type: "importmap",
|
|
2599
|
+
key: "importmap",
|
|
2600
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(importMap) }
|
|
2601
|
+
})
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
const bodyChildren = [
|
|
2605
|
+
createElement2("div", { id: "root", key: "root" }, children)
|
|
2606
|
+
];
|
|
2607
|
+
if (data && Object.keys(data).length > 0) {
|
|
2608
|
+
bodyChildren.push(
|
|
2609
|
+
createElement2("script", {
|
|
2610
|
+
id: "__WEIFUWU_DATA__",
|
|
2611
|
+
type: "application/json",
|
|
2612
|
+
key: "weifuwu-data",
|
|
2613
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(data).replace(/</g, "\\u003c") }
|
|
2614
|
+
})
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
return createElement2(
|
|
2618
|
+
"html",
|
|
2619
|
+
{ lang: "en" },
|
|
2620
|
+
createElement2("head", null, ...headChildren),
|
|
2621
|
+
createElement2("body", null, ...bodyChildren)
|
|
2622
|
+
);
|
|
2112
2623
|
}
|
|
2113
|
-
function
|
|
2114
|
-
|
|
2115
|
-
|
|
2624
|
+
async function renderComponent(Component2, data, layout, renderOpts) {
|
|
2625
|
+
let element = createElement2(Component2, renderOpts.props ?? {});
|
|
2626
|
+
if (layout) {
|
|
2627
|
+
element = createElement2(layout, { children: element });
|
|
2628
|
+
}
|
|
2629
|
+
element = createElement2(ServerDataContext.Provider, { value: data }, element);
|
|
2630
|
+
const page = createElement2(HtmlShell, {
|
|
2631
|
+
children: element,
|
|
2632
|
+
importMap: renderOpts.importMap,
|
|
2633
|
+
stylesheets: renderOpts.stylesheets,
|
|
2634
|
+
data: Object.keys(data).length > 0 ? data : void 0
|
|
2635
|
+
});
|
|
2636
|
+
const rstream = await renderToReadableStream(page, {
|
|
2637
|
+
bootstrapScripts: renderOpts.bootstrapScripts,
|
|
2638
|
+
bootstrapModules: renderOpts.bootstrapModules
|
|
2639
|
+
});
|
|
2640
|
+
if (renderOpts.stream === false) {
|
|
2641
|
+
await rstream.allReady;
|
|
2642
|
+
}
|
|
2643
|
+
return new Response(rstream, {
|
|
2644
|
+
status: renderOpts.status ?? 200,
|
|
2645
|
+
headers: { "content-type": "text/html; charset=utf-8", ...renderOpts.headers }
|
|
2646
|
+
});
|
|
2116
2647
|
}
|
|
2117
|
-
function
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
}
|
|
2131
|
-
function useRevalidate() {
|
|
2132
|
-
const ctx = useContext2(RouterContext);
|
|
2133
|
-
if (!ctx) throw new Error("useRevalidate() must be used within a client router");
|
|
2134
|
-
return ctx.revalidate;
|
|
2135
|
-
}
|
|
2136
|
-
function Form({ method = "post", action, children, ...props }) {
|
|
2137
|
-
const router = useContext2(RouterContext);
|
|
2138
|
-
if (!router) {
|
|
2139
|
-
return createElement2("form", { method, action, ...props }, children);
|
|
2140
|
-
}
|
|
2141
|
-
const handleSubmit = async (e) => {
|
|
2142
|
-
e.preventDefault();
|
|
2143
|
-
const form = e.target;
|
|
2144
|
-
const formData = new FormData(form);
|
|
2145
|
-
const url = action || window.location.pathname;
|
|
2146
|
-
const fetchMethod = method === "get" ? "GET" : method.toUpperCase();
|
|
2648
|
+
function react(opts) {
|
|
2649
|
+
if (opts && "pages" in opts) {
|
|
2650
|
+
return (app) => createFullReactApp(app, opts);
|
|
2651
|
+
}
|
|
2652
|
+
if (opts?.cacheDir) setReactCacheDir(opts.cacheDir);
|
|
2653
|
+
let LayoutComponent = null;
|
|
2654
|
+
let layoutLoaded = false;
|
|
2655
|
+
let layoutLoadError = null;
|
|
2656
|
+
async function getLayout() {
|
|
2657
|
+
if (!opts?.layout) return null;
|
|
2658
|
+
if (layoutLoaded) {
|
|
2659
|
+
if (layoutLoadError) throw layoutLoadError;
|
|
2660
|
+
return LayoutComponent;
|
|
2661
|
+
}
|
|
2147
2662
|
try {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
redirect: "manual"
|
|
2152
|
-
});
|
|
2153
|
-
if (res.status >= 300 && res.status < 400) {
|
|
2154
|
-
const loc = res.headers.get("Location");
|
|
2155
|
-
if (loc) {
|
|
2156
|
-
router.navigate(loc);
|
|
2157
|
-
return;
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
await router.revalidate();
|
|
2663
|
+
LayoutComponent = await loadTsxComponent(opts.layout);
|
|
2664
|
+
layoutLoaded = true;
|
|
2665
|
+
return LayoutComponent;
|
|
2161
2666
|
} catch (err) {
|
|
2162
|
-
|
|
2667
|
+
layoutLoadError = err instanceof Error ? err : new Error(String(err));
|
|
2668
|
+
layoutLoaded = true;
|
|
2669
|
+
throw layoutLoadError;
|
|
2163
2670
|
}
|
|
2671
|
+
}
|
|
2672
|
+
return async (_req, ctx, next) => {
|
|
2673
|
+
ctx.render = async (path, renderOpts) => {
|
|
2674
|
+
let data = renderOpts?.data ?? {};
|
|
2675
|
+
if (renderOpts?.loader) {
|
|
2676
|
+
const loaderData = await renderOpts.loader(ctx);
|
|
2677
|
+
data = { ...data, ...loaderData };
|
|
2678
|
+
}
|
|
2679
|
+
const Component2 = await loadTsxComponent(path);
|
|
2680
|
+
const layout = await getLayout();
|
|
2681
|
+
return renderComponent(Component2, data, layout, renderOpts ?? {});
|
|
2682
|
+
};
|
|
2683
|
+
return next(_req, ctx);
|
|
2164
2684
|
};
|
|
2165
|
-
return createElement2(
|
|
2166
|
-
"form",
|
|
2167
|
-
{ method, action, ...props, onSubmit: handleSubmit },
|
|
2168
|
-
children
|
|
2169
|
-
);
|
|
2170
2685
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
var SETUP_KEY = /* @__PURE__ */ Symbol.for("weifuwu:react:setup");
|
|
2175
|
-
function bag(ctx) {
|
|
2176
|
-
return ctx;
|
|
2686
|
+
async function resolveLoader(modulePath) {
|
|
2687
|
+
const mod = await loadTsxModule(modulePath);
|
|
2688
|
+
return typeof mod.loader === "function" ? mod.loader : null;
|
|
2177
2689
|
}
|
|
2178
|
-
function
|
|
2690
|
+
async function callLoader(loader, ctx, data, rethrow = true) {
|
|
2691
|
+
if (!loader) return data;
|
|
2179
2692
|
try {
|
|
2180
|
-
return
|
|
2181
|
-
} catch {
|
|
2182
|
-
|
|
2693
|
+
return { ...data, ...await loader(ctx) };
|
|
2694
|
+
} catch (err) {
|
|
2695
|
+
if (rethrow) throw err;
|
|
2696
|
+
return data;
|
|
2183
2697
|
}
|
|
2184
2698
|
}
|
|
2185
|
-
function
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
const
|
|
2189
|
-
const
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2699
|
+
function createFullReactApp(app, opts) {
|
|
2700
|
+
const stylesheets = [...opts.stylesheets ?? []];
|
|
2701
|
+
if (opts.tailwind) {
|
|
2702
|
+
const twPath = opts.tailwind.path ?? "/assets/tailwind.css";
|
|
2703
|
+
const twEntry = opts.tailwind.entry ?? "./styles/input.css";
|
|
2704
|
+
app.use(tailwindDev({ entries: { [twPath]: { entry: twEntry } } }));
|
|
2705
|
+
if (!stylesheets.includes(twPath)) stylesheets.push(twPath);
|
|
2706
|
+
}
|
|
2707
|
+
app.use(react({ layout: opts.layout, cacheDir: opts.cacheDir }));
|
|
2708
|
+
const layoutLoaderP = resolveLoader(opts.layout);
|
|
2709
|
+
const notFoundLoaderP = opts.notFound ? resolveLoader(opts.notFound) : Promise.resolve(null);
|
|
2710
|
+
const clientCfg = typeof opts.client === "object" ? opts.client : {};
|
|
2711
|
+
const clientPath = clientCfg.path ?? "/assets/client.js";
|
|
2712
|
+
const bootstrapModules = [...opts.bootstrapModules ?? []];
|
|
2713
|
+
if (opts.client !== false && !bootstrapModules.includes(clientPath)) {
|
|
2714
|
+
bootstrapModules.push(clientPath);
|
|
2715
|
+
}
|
|
2716
|
+
const renderOpts = {
|
|
2717
|
+
stylesheets: stylesheets.length > 0 ? stylesheets : void 0,
|
|
2718
|
+
bootstrapModules: bootstrapModules.length > 0 ? bootstrapModules : void 0,
|
|
2719
|
+
stream: opts.stream
|
|
2720
|
+
};
|
|
2721
|
+
for (const [path, component] of Object.entries(opts.pages)) {
|
|
2722
|
+
app.get(path, async (_req, ctx) => {
|
|
2723
|
+
let data = {};
|
|
2724
|
+
data = await callLoader(await layoutLoaderP, ctx, data);
|
|
2725
|
+
const pageLoader = opts.loaders?.[path] ?? await resolveLoader(component);
|
|
2726
|
+
data = await callLoader(pageLoader, ctx, data);
|
|
2727
|
+
return ctx.render(component, { ...renderOpts, data });
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
if (opts.notFound) {
|
|
2731
|
+
const notFoundPath = opts.notFound;
|
|
2732
|
+
app.onError(async (err, _req, ctx) => {
|
|
2733
|
+
const status = typeof err === "object" && err !== null && "status" in err ? err.status : 500;
|
|
2734
|
+
if (ctx.render) {
|
|
2735
|
+
let data = { error: String(err) };
|
|
2736
|
+
data = await callLoader(await layoutLoaderP, ctx, data, false);
|
|
2737
|
+
data = await callLoader(await notFoundLoaderP, ctx, data, false);
|
|
2738
|
+
return ctx.render(notFoundPath, { ...renderOpts, status, data });
|
|
2739
|
+
}
|
|
2740
|
+
return new Response("Internal Server Error", { status });
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
if (opts.client !== false) {
|
|
2744
|
+
app.use(esbuildDev({
|
|
2745
|
+
entries: {
|
|
2746
|
+
[clientPath]: {
|
|
2747
|
+
clientRouter: {
|
|
2748
|
+
pages: opts.pages,
|
|
2749
|
+
layout: opts.layout,
|
|
2750
|
+
fallback: opts.notFound
|
|
2751
|
+
},
|
|
2752
|
+
bundle: true,
|
|
2753
|
+
splitting: clientCfg.splitting ?? true,
|
|
2754
|
+
minify: clientCfg.minify ?? false
|
|
2203
2755
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2756
|
+
}
|
|
2757
|
+
}));
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
function extractImportPath(fn) {
|
|
2761
|
+
const src = fn.toString();
|
|
2762
|
+
const m = src.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
2763
|
+
if (!m) throw new Error(`Cannot extract import path from: ${src}`);
|
|
2764
|
+
return m[1];
|
|
2765
|
+
}
|
|
2766
|
+
function reactRouter(app, routes, opts = {}) {
|
|
2767
|
+
let LayoutComponent = null;
|
|
2768
|
+
let layoutPromise = null;
|
|
2769
|
+
async function getLayout() {
|
|
2770
|
+
if (!opts.layout) return null;
|
|
2771
|
+
if (LayoutComponent) return LayoutComponent;
|
|
2772
|
+
if (!layoutPromise) {
|
|
2773
|
+
layoutPromise = loadTsxComponent(opts.layout).then((c) => {
|
|
2774
|
+
LayoutComponent = c;
|
|
2775
|
+
return c;
|
|
2776
|
+
});
|
|
2206
2777
|
}
|
|
2207
|
-
return
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2778
|
+
return layoutPromise;
|
|
2779
|
+
}
|
|
2780
|
+
for (const [path, importer] of Object.entries(routes)) {
|
|
2781
|
+
app.get(path, async (_req, ctx) => {
|
|
2782
|
+
const cmpPath = extractImportPath(importer);
|
|
2783
|
+
const Component2 = await loadTsxComponent(cmpPath);
|
|
2784
|
+
const layout = await getLayout();
|
|
2785
|
+
let data = {};
|
|
2786
|
+
const loader = opts.loaders?.[path];
|
|
2787
|
+
if (loader) {
|
|
2788
|
+
data = await loader(ctx);
|
|
2789
|
+
}
|
|
2790
|
+
return renderComponent(Component2, data, layout, opts);
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
function Link({ href, children, ...props }) {
|
|
2795
|
+
return createElement2("a", { href, ...props }, children);
|
|
2210
2796
|
}
|
|
2211
2797
|
export {
|
|
2212
2798
|
DEFAULT_MAX_BODY,
|
|
2213
2799
|
ErrorBoundary,
|
|
2214
|
-
Form,
|
|
2215
2800
|
HttpError,
|
|
2216
2801
|
Link,
|
|
2217
2802
|
MIGRATIONS_TABLE,
|
|
@@ -2222,6 +2807,7 @@ export {
|
|
|
2222
2807
|
createHub,
|
|
2223
2808
|
currentTrace,
|
|
2224
2809
|
currentTraceId,
|
|
2810
|
+
esbuildDev,
|
|
2225
2811
|
graphql,
|
|
2226
2812
|
helmet,
|
|
2227
2813
|
logger,
|
|
@@ -2229,16 +2815,14 @@ export {
|
|
|
2229
2815
|
queue,
|
|
2230
2816
|
rateLimit,
|
|
2231
2817
|
react,
|
|
2818
|
+
reactRouter,
|
|
2232
2819
|
redis,
|
|
2233
2820
|
runWithTrace,
|
|
2234
2821
|
serve,
|
|
2235
2822
|
serveStatic,
|
|
2823
|
+
tailwindDev,
|
|
2236
2824
|
trace,
|
|
2237
2825
|
traceElapsed,
|
|
2238
2826
|
upload,
|
|
2239
|
-
useNavigate,
|
|
2240
|
-
useNavigation,
|
|
2241
|
-
useParams,
|
|
2242
|
-
useRevalidate,
|
|
2243
2827
|
useServerData
|
|
2244
2828
|
};
|