mokup 0.1.0 → 0.2.1
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/dist/cli-bin.cjs +110 -0
- package/dist/cli-bin.d.cts +5 -0
- package/dist/cli-bin.d.mts +5 -0
- package/dist/cli-bin.d.ts +5 -0
- package/dist/cli-bin.mjs +108 -0
- package/dist/cli.cjs +16 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +1 -0
- package/dist/index.d.cts +12 -1
- package/dist/index.d.mts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/runtime.cjs +16 -0
- package/dist/runtime.d.cts +1 -0
- package/dist/runtime.d.mts +1 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.mjs +1 -0
- package/dist/server/worker.cjs +16 -0
- package/dist/server/worker.d.cts +1 -0
- package/dist/server/worker.d.mts +1 -0
- package/dist/server/worker.d.ts +1 -0
- package/dist/server/worker.mjs +1 -0
- package/dist/server.cjs +16 -0
- package/dist/server.d.cts +1 -0
- package/dist/server.d.mts +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.mjs +1 -0
- package/dist/sw.cjs +90 -0
- package/dist/sw.d.cts +14 -0
- package/dist/sw.d.mts +14 -0
- package/dist/sw.d.ts +14 -0
- package/dist/sw.mjs +87 -0
- package/dist/vite.cjs +613 -19
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.mts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.mjs +613 -19
- package/package.json +33 -3
package/dist/vite.cjs
CHANGED
|
@@ -142,6 +142,18 @@ function toHonoPath(route) {
|
|
|
142
142
|
});
|
|
143
143
|
return `/${segments.join("/")}`;
|
|
144
144
|
}
|
|
145
|
+
function isValidStatus(status) {
|
|
146
|
+
return typeof status === "number" && Number.isFinite(status) && status >= 200 && status <= 599;
|
|
147
|
+
}
|
|
148
|
+
function resolveStatus(routeStatus, responseStatus) {
|
|
149
|
+
if (isValidStatus(routeStatus)) {
|
|
150
|
+
return routeStatus;
|
|
151
|
+
}
|
|
152
|
+
if (isValidStatus(responseStatus)) {
|
|
153
|
+
return responseStatus;
|
|
154
|
+
}
|
|
155
|
+
return 200;
|
|
156
|
+
}
|
|
145
157
|
function applyRouteOverrides(response, route) {
|
|
146
158
|
const headers = new Headers(response.headers);
|
|
147
159
|
const hasHeaders = !!route.headers && Object.keys(route.headers).length > 0;
|
|
@@ -150,7 +162,7 @@ function applyRouteOverrides(response, route) {
|
|
|
150
162
|
headers.set(key, value);
|
|
151
163
|
}
|
|
152
164
|
}
|
|
153
|
-
const status = route.status
|
|
165
|
+
const status = resolveStatus(route.status, response.status);
|
|
154
166
|
if (status === response.status && !hasHeaders) {
|
|
155
167
|
return response;
|
|
156
168
|
}
|
|
@@ -336,17 +348,28 @@ function normalizePlaygroundPath(value) {
|
|
|
336
348
|
const normalized = value.startsWith("/") ? value : `/${value}`;
|
|
337
349
|
return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
338
350
|
}
|
|
339
|
-
function normalizeBase(base) {
|
|
351
|
+
function normalizeBase$1(base) {
|
|
340
352
|
if (!base || base === "/") {
|
|
341
353
|
return "";
|
|
342
354
|
}
|
|
343
355
|
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
344
356
|
}
|
|
357
|
+
function resolvePlaygroundRequestPath(base, playgroundPath) {
|
|
358
|
+
const normalizedBase = normalizeBase$1(base);
|
|
359
|
+
const normalizedPath = normalizePlaygroundPath(playgroundPath);
|
|
360
|
+
if (!normalizedBase) {
|
|
361
|
+
return normalizedPath;
|
|
362
|
+
}
|
|
363
|
+
if (normalizedPath.startsWith(normalizedBase)) {
|
|
364
|
+
return normalizedPath;
|
|
365
|
+
}
|
|
366
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
367
|
+
}
|
|
345
368
|
function injectPlaygroundHmr(html, base) {
|
|
346
369
|
if (html.includes("mokup-playground-hmr")) {
|
|
347
370
|
return html;
|
|
348
371
|
}
|
|
349
|
-
const normalizedBase = normalizeBase(base);
|
|
372
|
+
const normalizedBase = normalizeBase$1(base);
|
|
350
373
|
const clientPath = `${normalizedBase}/@vite/client`;
|
|
351
374
|
const snippet = [
|
|
352
375
|
'<script type="module" id="mokup-playground-hmr">',
|
|
@@ -370,6 +393,29 @@ function injectPlaygroundHmr(html, base) {
|
|
|
370
393
|
return `${html}
|
|
371
394
|
${snippet}`;
|
|
372
395
|
}
|
|
396
|
+
function injectPlaygroundSw(html, script) {
|
|
397
|
+
if (!script) {
|
|
398
|
+
return html;
|
|
399
|
+
}
|
|
400
|
+
if (html.includes("mokup-playground-sw")) {
|
|
401
|
+
return html;
|
|
402
|
+
}
|
|
403
|
+
const snippet = [
|
|
404
|
+
'<script type="module" id="mokup-playground-sw">',
|
|
405
|
+
script,
|
|
406
|
+
"<\/script>"
|
|
407
|
+
].join("\n");
|
|
408
|
+
if (html.includes("</head>")) {
|
|
409
|
+
return html.replace("</head>", `${snippet}
|
|
410
|
+
</head>`);
|
|
411
|
+
}
|
|
412
|
+
if (html.includes("</body>")) {
|
|
413
|
+
return html.replace("</body>", `${snippet}
|
|
414
|
+
</body>`);
|
|
415
|
+
}
|
|
416
|
+
return `${html}
|
|
417
|
+
${snippet}`;
|
|
418
|
+
}
|
|
373
419
|
function isViteDevServer$1(server) {
|
|
374
420
|
return !!server && "ws" in server;
|
|
375
421
|
}
|
|
@@ -508,25 +554,31 @@ function createPlaygroundMiddleware(params) {
|
|
|
508
554
|
if (!params.config.enabled) {
|
|
509
555
|
return next();
|
|
510
556
|
}
|
|
557
|
+
const server = params.getServer?.();
|
|
558
|
+
const requestPath = resolvePlaygroundRequestPath(server?.config?.base ?? "/", playgroundPath);
|
|
511
559
|
const requestUrl = req.url ?? "/";
|
|
512
560
|
const url = new URL(requestUrl, "http://mokup.local");
|
|
513
561
|
const pathname = url.pathname;
|
|
514
|
-
|
|
562
|
+
const matchedPath = pathname.startsWith(requestPath) ? requestPath : pathname.startsWith(playgroundPath) ? playgroundPath : null;
|
|
563
|
+
if (!matchedPath) {
|
|
515
564
|
return next();
|
|
516
565
|
}
|
|
517
|
-
const subPath = pathname.slice(
|
|
566
|
+
const subPath = pathname.slice(matchedPath.length);
|
|
518
567
|
if (subPath === "") {
|
|
519
568
|
const suffix = url.search ?? "";
|
|
520
569
|
res.statusCode = 302;
|
|
521
|
-
res.setHeader("Location", `${
|
|
570
|
+
res.setHeader("Location", `${matchedPath}/${suffix}`);
|
|
522
571
|
res.end();
|
|
523
572
|
return;
|
|
524
573
|
}
|
|
525
574
|
if (subPath === "" || subPath === "/" || subPath === "/index.html") {
|
|
526
575
|
try {
|
|
527
576
|
const html = await node_fs.promises.readFile(indexPath, "utf8");
|
|
528
|
-
|
|
529
|
-
|
|
577
|
+
let output = html;
|
|
578
|
+
if (isViteDevServer$1(server)) {
|
|
579
|
+
output = injectPlaygroundHmr(output, server.config.base ?? "/");
|
|
580
|
+
output = injectPlaygroundSw(output, params.getSwScript?.());
|
|
581
|
+
}
|
|
530
582
|
const contentType = mimeTypes[".html"] ?? "text/html; charset=utf-8";
|
|
531
583
|
sendFile(res, output, contentType);
|
|
532
584
|
} catch (error) {
|
|
@@ -537,13 +589,12 @@ function createPlaygroundMiddleware(params) {
|
|
|
537
589
|
return;
|
|
538
590
|
}
|
|
539
591
|
if (subPath === "/routes") {
|
|
540
|
-
const server = params.getServer?.();
|
|
541
592
|
const dirs = params.getDirs?.() ?? [];
|
|
542
593
|
const baseRoot = resolveGroupRoot(dirs, server?.config?.root);
|
|
543
594
|
const groups = resolveGroups(dirs, baseRoot);
|
|
544
595
|
const routes = params.getRoutes();
|
|
545
596
|
sendJson(res, {
|
|
546
|
-
basePath:
|
|
597
|
+
basePath: matchedPath,
|
|
547
598
|
count: routes.length,
|
|
548
599
|
groups: groups.map((group) => ({ key: group.key, label: group.label })),
|
|
549
600
|
routes: routes.map((route) => toPlaygroundRoute(route, baseRoot, groups))
|
|
@@ -768,12 +819,12 @@ function normalizeMiddlewares(value, source, logger) {
|
|
|
768
819
|
}
|
|
769
820
|
const list = Array.isArray(value) ? value : [value];
|
|
770
821
|
const middlewares = [];
|
|
771
|
-
for (const entry of list) {
|
|
822
|
+
for (const [index, entry] of list.entries()) {
|
|
772
823
|
if (typeof entry !== "function") {
|
|
773
824
|
logger.warn(`Invalid middleware in ${source}`);
|
|
774
825
|
continue;
|
|
775
826
|
}
|
|
776
|
-
middlewares.push({ handle: entry, source });
|
|
827
|
+
middlewares.push({ handle: entry, source, index });
|
|
777
828
|
}
|
|
778
829
|
return middlewares;
|
|
779
830
|
}
|
|
@@ -996,7 +1047,7 @@ async function scanRoutes(params) {
|
|
|
996
1047
|
continue;
|
|
997
1048
|
}
|
|
998
1049
|
const rules = await loadRules(fileInfo.file, params.server, params.logger);
|
|
999
|
-
for (const rule of rules) {
|
|
1050
|
+
for (const [index, rule] of rules.entries()) {
|
|
1000
1051
|
if (!rule || typeof rule !== "object") {
|
|
1001
1052
|
continue;
|
|
1002
1053
|
}
|
|
@@ -1025,6 +1076,7 @@ async function scanRoutes(params) {
|
|
|
1025
1076
|
if (!resolved) {
|
|
1026
1077
|
continue;
|
|
1027
1078
|
}
|
|
1079
|
+
resolved.ruleIndex = index;
|
|
1028
1080
|
if (config.headers) {
|
|
1029
1081
|
resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
|
|
1030
1082
|
}
|
|
@@ -1048,6 +1100,330 @@ async function scanRoutes(params) {
|
|
|
1048
1100
|
return sortRoutes(routes);
|
|
1049
1101
|
}
|
|
1050
1102
|
|
|
1103
|
+
const defaultSwPath = "/mokup-sw.js";
|
|
1104
|
+
const defaultSwScope = "/";
|
|
1105
|
+
function normalizeSwPath(path) {
|
|
1106
|
+
if (!path) {
|
|
1107
|
+
return defaultSwPath;
|
|
1108
|
+
}
|
|
1109
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeSwScope(scope) {
|
|
1112
|
+
if (!scope) {
|
|
1113
|
+
return defaultSwScope;
|
|
1114
|
+
}
|
|
1115
|
+
return scope.startsWith("/") ? scope : `/${scope}`;
|
|
1116
|
+
}
|
|
1117
|
+
function normalizeBasePath(value) {
|
|
1118
|
+
if (!value) {
|
|
1119
|
+
return "/";
|
|
1120
|
+
}
|
|
1121
|
+
const normalized = value.startsWith("/") ? value : `/${value}`;
|
|
1122
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
1123
|
+
return normalized.slice(0, -1);
|
|
1124
|
+
}
|
|
1125
|
+
return normalized;
|
|
1126
|
+
}
|
|
1127
|
+
function resolveSwConfigFromEntries(entries, logger) {
|
|
1128
|
+
let path = defaultSwPath;
|
|
1129
|
+
let scope = defaultSwScope;
|
|
1130
|
+
let register = true;
|
|
1131
|
+
let unregister = false;
|
|
1132
|
+
const basePaths = [];
|
|
1133
|
+
let hasPath = false;
|
|
1134
|
+
let hasScope = false;
|
|
1135
|
+
let hasRegister = false;
|
|
1136
|
+
let hasUnregister = false;
|
|
1137
|
+
for (const entry of entries) {
|
|
1138
|
+
const config = entry.sw;
|
|
1139
|
+
if (config?.path) {
|
|
1140
|
+
const next = normalizeSwPath(config.path);
|
|
1141
|
+
if (!hasPath) {
|
|
1142
|
+
path = next;
|
|
1143
|
+
hasPath = true;
|
|
1144
|
+
} else if (path !== next) {
|
|
1145
|
+
logger.warn(`SW path "${next}" ignored; using "${path}".`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (config?.scope) {
|
|
1149
|
+
const next = normalizeSwScope(config.scope);
|
|
1150
|
+
if (!hasScope) {
|
|
1151
|
+
scope = next;
|
|
1152
|
+
hasScope = true;
|
|
1153
|
+
} else if (scope !== next) {
|
|
1154
|
+
logger.warn(`SW scope "${next}" ignored; using "${scope}".`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (typeof config?.register === "boolean") {
|
|
1158
|
+
if (!hasRegister) {
|
|
1159
|
+
register = config.register;
|
|
1160
|
+
hasRegister = true;
|
|
1161
|
+
} else if (register !== config.register) {
|
|
1162
|
+
logger.warn(
|
|
1163
|
+
`SW register="${String(config.register)}" ignored; using "${String(register)}".`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (typeof config?.unregister === "boolean") {
|
|
1168
|
+
if (!hasUnregister) {
|
|
1169
|
+
unregister = config.unregister;
|
|
1170
|
+
hasUnregister = true;
|
|
1171
|
+
} else if (unregister !== config.unregister) {
|
|
1172
|
+
logger.warn(
|
|
1173
|
+
`SW unregister="${String(config.unregister)}" ignored; using "${String(unregister)}".`
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (typeof config?.basePath !== "undefined") {
|
|
1178
|
+
const values = Array.isArray(config.basePath) ? config.basePath : [config.basePath];
|
|
1179
|
+
for (const value of values) {
|
|
1180
|
+
basePaths.push(normalizeBasePath(value));
|
|
1181
|
+
}
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
const normalizedPrefix = normalizePrefix(entry.prefix ?? "");
|
|
1185
|
+
if (normalizedPrefix) {
|
|
1186
|
+
basePaths.push(normalizedPrefix);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return {
|
|
1190
|
+
path,
|
|
1191
|
+
scope,
|
|
1192
|
+
register,
|
|
1193
|
+
unregister,
|
|
1194
|
+
basePaths: Array.from(new Set(basePaths))
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
function resolveSwConfig(options, logger) {
|
|
1198
|
+
const swEntries = options.filter((entry) => entry.mode === "sw");
|
|
1199
|
+
if (swEntries.length === 0) {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
return resolveSwConfigFromEntries(swEntries, logger);
|
|
1203
|
+
}
|
|
1204
|
+
function resolveSwUnregisterConfig(options, logger) {
|
|
1205
|
+
return resolveSwConfigFromEntries(options, logger);
|
|
1206
|
+
}
|
|
1207
|
+
function toViteImportPath(file, root) {
|
|
1208
|
+
const absolute = pathe.isAbsolute(file) ? file : pathe.resolve(root, file);
|
|
1209
|
+
const rel = pathe.relative(root, absolute);
|
|
1210
|
+
if (!rel.startsWith("..") && !pathe.isAbsolute(rel)) {
|
|
1211
|
+
return `/${toPosix(rel)}`;
|
|
1212
|
+
}
|
|
1213
|
+
return `/@fs/${toPosix(absolute)}`;
|
|
1214
|
+
}
|
|
1215
|
+
function shouldModuleize(handler) {
|
|
1216
|
+
if (typeof handler === "function") {
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1219
|
+
if (typeof Response !== "undefined" && handler instanceof Response) {
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
function toBinaryBody(handler) {
|
|
1225
|
+
if (handler instanceof ArrayBuffer) {
|
|
1226
|
+
return node_buffer.Buffer.from(new Uint8Array(handler)).toString("base64");
|
|
1227
|
+
}
|
|
1228
|
+
if (handler instanceof Uint8Array) {
|
|
1229
|
+
return node_buffer.Buffer.from(handler).toString("base64");
|
|
1230
|
+
}
|
|
1231
|
+
if (node_buffer.Buffer.isBuffer(handler)) {
|
|
1232
|
+
return handler.toString("base64");
|
|
1233
|
+
}
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
function buildManifestResponse(route, moduleId) {
|
|
1237
|
+
if (moduleId) {
|
|
1238
|
+
const response = {
|
|
1239
|
+
type: "module",
|
|
1240
|
+
module: moduleId
|
|
1241
|
+
};
|
|
1242
|
+
if (typeof route.ruleIndex === "number") {
|
|
1243
|
+
response.ruleIndex = route.ruleIndex;
|
|
1244
|
+
}
|
|
1245
|
+
return response;
|
|
1246
|
+
}
|
|
1247
|
+
const handler = route.handler;
|
|
1248
|
+
if (typeof handler === "string") {
|
|
1249
|
+
return {
|
|
1250
|
+
type: "text",
|
|
1251
|
+
body: handler
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
const binary = toBinaryBody(handler);
|
|
1255
|
+
if (binary) {
|
|
1256
|
+
return {
|
|
1257
|
+
type: "binary",
|
|
1258
|
+
body: binary,
|
|
1259
|
+
encoding: "base64"
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
type: "json",
|
|
1264
|
+
body: handler
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function buildSwScript(params) {
|
|
1268
|
+
const { routes, root } = params;
|
|
1269
|
+
const runtimeImportPath = params.runtimeImportPath ?? "mokup/runtime";
|
|
1270
|
+
const basePaths = params.basePaths ?? [];
|
|
1271
|
+
const ruleModules = /* @__PURE__ */ new Map();
|
|
1272
|
+
const middlewareModules = /* @__PURE__ */ new Map();
|
|
1273
|
+
const manifestRoutes = routes.map((route) => {
|
|
1274
|
+
const moduleId = shouldModuleize(route.handler) ? toViteImportPath(route.file, root) : null;
|
|
1275
|
+
if (moduleId) {
|
|
1276
|
+
ruleModules.set(moduleId, moduleId);
|
|
1277
|
+
}
|
|
1278
|
+
const middleware = route.middlewares?.map((entry) => {
|
|
1279
|
+
const modulePath = toViteImportPath(entry.source, root);
|
|
1280
|
+
middlewareModules.set(modulePath, modulePath);
|
|
1281
|
+
return {
|
|
1282
|
+
module: modulePath,
|
|
1283
|
+
ruleIndex: entry.index
|
|
1284
|
+
};
|
|
1285
|
+
});
|
|
1286
|
+
const response = buildManifestResponse(route, moduleId);
|
|
1287
|
+
const manifestRoute = {
|
|
1288
|
+
method: route.method,
|
|
1289
|
+
url: route.template,
|
|
1290
|
+
...route.tokens ? { tokens: route.tokens } : {},
|
|
1291
|
+
...route.score ? { score: route.score } : {},
|
|
1292
|
+
...route.status ? { status: route.status } : {},
|
|
1293
|
+
...route.headers ? { headers: route.headers } : {},
|
|
1294
|
+
...route.delay ? { delay: route.delay } : {},
|
|
1295
|
+
...middleware && middleware.length > 0 ? { middleware } : {},
|
|
1296
|
+
response
|
|
1297
|
+
};
|
|
1298
|
+
return manifestRoute;
|
|
1299
|
+
});
|
|
1300
|
+
const manifest = {
|
|
1301
|
+
version: 1,
|
|
1302
|
+
routes: manifestRoutes
|
|
1303
|
+
};
|
|
1304
|
+
const imports = [
|
|
1305
|
+
`import { createRuntimeApp, handle } from ${JSON.stringify(runtimeImportPath)}`
|
|
1306
|
+
];
|
|
1307
|
+
const moduleEntries = [];
|
|
1308
|
+
let moduleIndex = 0;
|
|
1309
|
+
for (const id of ruleModules.keys()) {
|
|
1310
|
+
const name = `module${moduleIndex++}`;
|
|
1311
|
+
imports.push(`import * as ${name} from '${id}'`);
|
|
1312
|
+
moduleEntries.push({ id, name, kind: "rule" });
|
|
1313
|
+
}
|
|
1314
|
+
for (const id of middlewareModules.keys()) {
|
|
1315
|
+
const name = `module${moduleIndex++}`;
|
|
1316
|
+
imports.push(`import * as ${name} from '${id}'`);
|
|
1317
|
+
moduleEntries.push({ id, name, kind: "middleware" });
|
|
1318
|
+
}
|
|
1319
|
+
const lines = [];
|
|
1320
|
+
lines.push(...imports, "");
|
|
1321
|
+
lines.push(
|
|
1322
|
+
"const resolveModuleExport = (mod) => mod?.default ?? mod",
|
|
1323
|
+
"",
|
|
1324
|
+
"const toRuntimeRule = (value) => {",
|
|
1325
|
+
" if (typeof value === 'undefined') {",
|
|
1326
|
+
" return null",
|
|
1327
|
+
" }",
|
|
1328
|
+
" if (typeof value === 'function') {",
|
|
1329
|
+
" return { response: value }",
|
|
1330
|
+
" }",
|
|
1331
|
+
" if (value === null) {",
|
|
1332
|
+
" return { response: null }",
|
|
1333
|
+
" }",
|
|
1334
|
+
" if (typeof value === 'object') {",
|
|
1335
|
+
" if ('response' in value) {",
|
|
1336
|
+
" return value",
|
|
1337
|
+
" }",
|
|
1338
|
+
" if ('handler' in value) {",
|
|
1339
|
+
" const handlerRule = value",
|
|
1340
|
+
" return {",
|
|
1341
|
+
" response: handlerRule.handler,",
|
|
1342
|
+
" ...(typeof handlerRule.status === 'number' ? { status: handlerRule.status } : {}),",
|
|
1343
|
+
" ...(handlerRule.headers ? { headers: handlerRule.headers } : {}),",
|
|
1344
|
+
" ...(typeof handlerRule.delay === 'number' ? { delay: handlerRule.delay } : {}),",
|
|
1345
|
+
" }",
|
|
1346
|
+
" }",
|
|
1347
|
+
" return { response: value }",
|
|
1348
|
+
" }",
|
|
1349
|
+
" return { response: value }",
|
|
1350
|
+
"}",
|
|
1351
|
+
"",
|
|
1352
|
+
"const toRuntimeRules = (value) => {",
|
|
1353
|
+
" if (typeof value === 'undefined') {",
|
|
1354
|
+
" return []",
|
|
1355
|
+
" }",
|
|
1356
|
+
" if (Array.isArray(value)) {",
|
|
1357
|
+
" return value.map(toRuntimeRule).filter(Boolean)",
|
|
1358
|
+
" }",
|
|
1359
|
+
" const rule = toRuntimeRule(value)",
|
|
1360
|
+
" return rule ? [rule] : []",
|
|
1361
|
+
"}",
|
|
1362
|
+
""
|
|
1363
|
+
);
|
|
1364
|
+
lines.push(
|
|
1365
|
+
`const manifest = ${JSON.stringify(manifest, null, 2)}`,
|
|
1366
|
+
""
|
|
1367
|
+
);
|
|
1368
|
+
if (moduleEntries.length > 0) {
|
|
1369
|
+
lines.push("const moduleMap = {");
|
|
1370
|
+
for (const entry of moduleEntries) {
|
|
1371
|
+
if (entry.kind === "rule") {
|
|
1372
|
+
lines.push(
|
|
1373
|
+
` ${JSON.stringify(entry.id)}: { default: toRuntimeRules(resolveModuleExport(${entry.name})) },`
|
|
1374
|
+
);
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
lines.push(
|
|
1378
|
+
` ${JSON.stringify(entry.id)}: ${entry.name},`
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
lines.push("}", "");
|
|
1382
|
+
}
|
|
1383
|
+
const runtimeOptions = moduleEntries.length > 0 ? "{ manifest, moduleMap }" : "{ manifest }";
|
|
1384
|
+
lines.push(
|
|
1385
|
+
`const basePaths = ${JSON.stringify(basePaths)}`,
|
|
1386
|
+
"",
|
|
1387
|
+
"self.addEventListener('install', () => {",
|
|
1388
|
+
" self.skipWaiting()",
|
|
1389
|
+
"})",
|
|
1390
|
+
"",
|
|
1391
|
+
"self.addEventListener('activate', (event) => {",
|
|
1392
|
+
" event.waitUntil(self.clients.claim())",
|
|
1393
|
+
"})",
|
|
1394
|
+
"",
|
|
1395
|
+
"const shouldHandle = (request) => {",
|
|
1396
|
+
" if (!basePaths || basePaths.length === 0) {",
|
|
1397
|
+
" return true",
|
|
1398
|
+
" }",
|
|
1399
|
+
" const pathname = new URL(request.url).pathname",
|
|
1400
|
+
" return basePaths.some((basePath) => {",
|
|
1401
|
+
" if (basePath === '/') {",
|
|
1402
|
+
" return true",
|
|
1403
|
+
" }",
|
|
1404
|
+
" return pathname === basePath || pathname.startsWith(basePath + '/')",
|
|
1405
|
+
" })",
|
|
1406
|
+
"}",
|
|
1407
|
+
"",
|
|
1408
|
+
"const registerHandler = async () => {",
|
|
1409
|
+
` const app = await createRuntimeApp(${runtimeOptions})`,
|
|
1410
|
+
" const handler = handle(app)",
|
|
1411
|
+
" self.addEventListener('fetch', (event) => {",
|
|
1412
|
+
" if (!shouldHandle(event.request)) {",
|
|
1413
|
+
" return",
|
|
1414
|
+
" }",
|
|
1415
|
+
" handler(event)",
|
|
1416
|
+
" })",
|
|
1417
|
+
"}",
|
|
1418
|
+
"",
|
|
1419
|
+
"registerHandler().catch((error) => {",
|
|
1420
|
+
" console.error('[mokup] Failed to build service worker app:', error)",
|
|
1421
|
+
"})",
|
|
1422
|
+
""
|
|
1423
|
+
);
|
|
1424
|
+
return lines.join("\n");
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1051
1427
|
function buildRouteSignature(routes) {
|
|
1052
1428
|
return routes.map(
|
|
1053
1429
|
(route) => [
|
|
@@ -1075,9 +1451,61 @@ function resolvePlaygroundInput(list) {
|
|
|
1075
1451
|
}
|
|
1076
1452
|
return void 0;
|
|
1077
1453
|
}
|
|
1454
|
+
function normalizeBase(base) {
|
|
1455
|
+
if (!base) {
|
|
1456
|
+
return "/";
|
|
1457
|
+
}
|
|
1458
|
+
if (base.startsWith(".")) {
|
|
1459
|
+
return "/";
|
|
1460
|
+
}
|
|
1461
|
+
let normalized = base.startsWith("/") ? base : `/${base}`;
|
|
1462
|
+
if (!normalized.endsWith("/")) {
|
|
1463
|
+
normalized = `${normalized}/`;
|
|
1464
|
+
}
|
|
1465
|
+
return normalized;
|
|
1466
|
+
}
|
|
1467
|
+
function resolveRegisterPath(base, path) {
|
|
1468
|
+
const normalizedBase = normalizeBase(base);
|
|
1469
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
1470
|
+
if (normalizedPath.startsWith(normalizedBase)) {
|
|
1471
|
+
return normalizedPath;
|
|
1472
|
+
}
|
|
1473
|
+
return `${normalizedBase}${normalizedPath.slice(1)}`;
|
|
1474
|
+
}
|
|
1475
|
+
function resolveRegisterScope(base, scope) {
|
|
1476
|
+
const normalizedBase = normalizeBase(base);
|
|
1477
|
+
const normalizedScope = scope.startsWith("/") ? scope : `/${scope}`;
|
|
1478
|
+
if (normalizedScope.startsWith(normalizedBase)) {
|
|
1479
|
+
return normalizedScope;
|
|
1480
|
+
}
|
|
1481
|
+
return `${normalizedBase}${normalizedScope.slice(1)}`;
|
|
1482
|
+
}
|
|
1483
|
+
function resolveSwImportPath(base) {
|
|
1484
|
+
const normalizedBase = normalizeBase(base);
|
|
1485
|
+
return `${normalizedBase}@id/mokup/sw`;
|
|
1486
|
+
}
|
|
1487
|
+
function resolveSwRuntimeImportPath(base) {
|
|
1488
|
+
const normalizedBase = normalizeBase(base);
|
|
1489
|
+
return `${normalizedBase}@id/mokup/runtime`;
|
|
1490
|
+
}
|
|
1491
|
+
function hasMiddlewareStack(middlewares) {
|
|
1492
|
+
const candidate = middlewares;
|
|
1493
|
+
return Array.isArray(candidate.stack);
|
|
1494
|
+
}
|
|
1495
|
+
function addMiddlewareFirst(server, middleware) {
|
|
1496
|
+
if (hasMiddlewareStack(server.middlewares)) {
|
|
1497
|
+
server.middlewares.stack.unshift({ route: "", handle: middleware });
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
server.middlewares.use(middleware);
|
|
1501
|
+
}
|
|
1078
1502
|
function createMokupPlugin(options = {}) {
|
|
1079
1503
|
let root = node_process.cwd();
|
|
1504
|
+
let base = "/";
|
|
1505
|
+
let command = "serve";
|
|
1080
1506
|
let routes = [];
|
|
1507
|
+
let serverRoutes = [];
|
|
1508
|
+
let swRoutes = [];
|
|
1081
1509
|
let app = null;
|
|
1082
1510
|
let previewWatcher = null;
|
|
1083
1511
|
let currentServer = null;
|
|
@@ -1087,6 +1515,9 @@ function createMokupPlugin(options = {}) {
|
|
|
1087
1515
|
const watchEnabled = optionList.every((entry) => entry.watch !== false);
|
|
1088
1516
|
const playgroundConfig = resolvePlaygroundOptions(resolvePlaygroundInput(optionList));
|
|
1089
1517
|
const logger = createLogger(logEnabled);
|
|
1518
|
+
const hasSwEntries = optionList.some((entry) => entry.mode === "sw");
|
|
1519
|
+
const swConfig = resolveSwConfig(optionList, logger);
|
|
1520
|
+
const unregisterConfig = resolveSwUnregisterConfig(optionList, logger);
|
|
1090
1521
|
const resolveAllDirs = () => {
|
|
1091
1522
|
const dirs = [];
|
|
1092
1523
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1101,15 +1532,55 @@ function createMokupPlugin(options = {}) {
|
|
|
1101
1532
|
}
|
|
1102
1533
|
return dirs;
|
|
1103
1534
|
};
|
|
1535
|
+
const hasSwRoutes = () => !!swConfig && swRoutes.length > 0;
|
|
1536
|
+
const resolveSwRequestPath = (path) => resolveRegisterPath(base, path);
|
|
1537
|
+
const resolveSwRegisterScope = (scope) => resolveRegisterScope(base, scope);
|
|
1538
|
+
const swVirtualId = "virtual:mokup-sw";
|
|
1539
|
+
const resolvedSwVirtualId = `\0${swVirtualId}`;
|
|
1540
|
+
function buildSwLifecycleScript(importPath = "mokup/sw") {
|
|
1541
|
+
const shouldUnregister = unregisterConfig.unregister === true || !hasSwEntries;
|
|
1542
|
+
if (shouldUnregister) {
|
|
1543
|
+
const path2 = resolveSwRequestPath(unregisterConfig.path);
|
|
1544
|
+
const scope2 = resolveSwRegisterScope(unregisterConfig.scope);
|
|
1545
|
+
return [
|
|
1546
|
+
`import { unregisterMokupServiceWorker } from ${JSON.stringify(importPath)}`,
|
|
1547
|
+
"(async () => {",
|
|
1548
|
+
` await unregisterMokupServiceWorker({ path: ${JSON.stringify(path2)}, scope: ${JSON.stringify(scope2)} })`,
|
|
1549
|
+
"})()"
|
|
1550
|
+
].join("\n");
|
|
1551
|
+
}
|
|
1552
|
+
if (!swConfig || swConfig.register === false) {
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
if (!hasSwRoutes()) {
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
const path = resolveSwRequestPath(swConfig.path);
|
|
1559
|
+
const scope = resolveSwRegisterScope(swConfig.scope);
|
|
1560
|
+
return [
|
|
1561
|
+
`import { registerMokupServiceWorker } from ${JSON.stringify(importPath)}`,
|
|
1562
|
+
"(async () => {",
|
|
1563
|
+
` const registration = await registerMokupServiceWorker({ path: ${JSON.stringify(path)}, scope: ${JSON.stringify(scope)} })`,
|
|
1564
|
+
" if (import.meta.hot && registration) {",
|
|
1565
|
+
" import.meta.hot.on('mokup:routes-changed', () => {",
|
|
1566
|
+
" registration.update()",
|
|
1567
|
+
" })",
|
|
1568
|
+
" }",
|
|
1569
|
+
"})()"
|
|
1570
|
+
].join("\n");
|
|
1571
|
+
}
|
|
1104
1572
|
const playgroundMiddleware = createPlaygroundMiddleware({
|
|
1105
1573
|
getRoutes: () => routes,
|
|
1106
1574
|
config: playgroundConfig,
|
|
1107
1575
|
logger,
|
|
1108
1576
|
getServer: () => currentServer,
|
|
1109
|
-
getDirs: () => resolveAllDirs()
|
|
1577
|
+
getDirs: () => resolveAllDirs(),
|
|
1578
|
+
getSwScript: () => buildSwLifecycleScript(resolveSwImportPath(base))
|
|
1110
1579
|
});
|
|
1111
1580
|
const refreshRoutes = async (server) => {
|
|
1112
1581
|
const collected = [];
|
|
1582
|
+
const collectedServer = [];
|
|
1583
|
+
const collectedSw = [];
|
|
1113
1584
|
for (const entry of optionList) {
|
|
1114
1585
|
const dirs = resolveDirs(entry.dir, root);
|
|
1115
1586
|
const scanParams = {
|
|
@@ -1128,9 +1599,19 @@ function createMokupPlugin(options = {}) {
|
|
|
1128
1599
|
}
|
|
1129
1600
|
const scanned = await scanRoutes(scanParams);
|
|
1130
1601
|
collected.push(...scanned);
|
|
1602
|
+
if (entry.mode === "sw") {
|
|
1603
|
+
collectedSw.push(...scanned);
|
|
1604
|
+
if (entry.sw?.fallback !== false) {
|
|
1605
|
+
collectedServer.push(...scanned);
|
|
1606
|
+
}
|
|
1607
|
+
} else {
|
|
1608
|
+
collectedServer.push(...scanned);
|
|
1609
|
+
}
|
|
1131
1610
|
}
|
|
1132
1611
|
routes = sortRoutes(collected);
|
|
1133
|
-
|
|
1612
|
+
serverRoutes = sortRoutes(collectedServer);
|
|
1613
|
+
swRoutes = sortRoutes(collectedSw);
|
|
1614
|
+
app = serverRoutes.length > 0 ? createHonoApp(serverRoutes) : null;
|
|
1134
1615
|
const signature = buildRouteSignature(routes);
|
|
1135
1616
|
if (isViteDevServer(server) && server.ws) {
|
|
1136
1617
|
if (lastSignature && signature !== lastSignature) {
|
|
@@ -1146,14 +1627,99 @@ function createMokupPlugin(options = {}) {
|
|
|
1146
1627
|
return {
|
|
1147
1628
|
name: "mokup:vite",
|
|
1148
1629
|
enforce: "pre",
|
|
1630
|
+
resolveId(id) {
|
|
1631
|
+
if (id === swVirtualId) {
|
|
1632
|
+
return resolvedSwVirtualId;
|
|
1633
|
+
}
|
|
1634
|
+
return null;
|
|
1635
|
+
},
|
|
1636
|
+
async load(id) {
|
|
1637
|
+
if (id !== resolvedSwVirtualId) {
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
if (swRoutes.length === 0) {
|
|
1641
|
+
await refreshRoutes();
|
|
1642
|
+
}
|
|
1643
|
+
return buildSwScript({
|
|
1644
|
+
routes: swRoutes,
|
|
1645
|
+
root,
|
|
1646
|
+
basePaths: swConfig?.basePaths ?? []
|
|
1647
|
+
});
|
|
1648
|
+
},
|
|
1649
|
+
async buildStart() {
|
|
1650
|
+
if (!swConfig || command !== "build") {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
await refreshRoutes();
|
|
1654
|
+
if (!hasSwRoutes()) {
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
const fileName = swConfig.path.startsWith("/") ? swConfig.path.slice(1) : swConfig.path;
|
|
1658
|
+
this.emitFile({
|
|
1659
|
+
type: "chunk",
|
|
1660
|
+
id: swVirtualId,
|
|
1661
|
+
fileName
|
|
1662
|
+
});
|
|
1663
|
+
},
|
|
1664
|
+
async transformIndexHtml(html) {
|
|
1665
|
+
if (swRoutes.length === 0) {
|
|
1666
|
+
await refreshRoutes(currentServer ?? void 0);
|
|
1667
|
+
}
|
|
1668
|
+
const script = buildSwLifecycleScript();
|
|
1669
|
+
if (!script) {
|
|
1670
|
+
return html;
|
|
1671
|
+
}
|
|
1672
|
+
return {
|
|
1673
|
+
html,
|
|
1674
|
+
tags: [
|
|
1675
|
+
{
|
|
1676
|
+
tag: "script",
|
|
1677
|
+
attrs: { type: "module" },
|
|
1678
|
+
children: script,
|
|
1679
|
+
injectTo: "head"
|
|
1680
|
+
}
|
|
1681
|
+
]
|
|
1682
|
+
};
|
|
1683
|
+
},
|
|
1149
1684
|
configResolved(config) {
|
|
1150
1685
|
root = config.root;
|
|
1686
|
+
base = config.base ?? "/";
|
|
1687
|
+
command = config.command;
|
|
1151
1688
|
},
|
|
1152
1689
|
async configureServer(server) {
|
|
1153
1690
|
currentServer = server;
|
|
1154
1691
|
await refreshRoutes(server);
|
|
1155
|
-
server
|
|
1156
|
-
|
|
1692
|
+
addMiddlewareFirst(server, playgroundMiddleware);
|
|
1693
|
+
const swPath = swConfig ? resolveSwRequestPath(swConfig.path) : null;
|
|
1694
|
+
if (swPath && hasSwRoutes()) {
|
|
1695
|
+
server.middlewares.use(async (req, res, next) => {
|
|
1696
|
+
const requestUrl = req.url ?? "/";
|
|
1697
|
+
const parsed = new URL(requestUrl, "http://mokup.local");
|
|
1698
|
+
if (parsed.pathname !== swPath) {
|
|
1699
|
+
return next();
|
|
1700
|
+
}
|
|
1701
|
+
try {
|
|
1702
|
+
const code = buildSwScript({
|
|
1703
|
+
routes: swRoutes,
|
|
1704
|
+
root,
|
|
1705
|
+
runtimeImportPath: resolveSwRuntimeImportPath(base),
|
|
1706
|
+
basePaths: swConfig?.basePaths ?? []
|
|
1707
|
+
});
|
|
1708
|
+
res.statusCode = 200;
|
|
1709
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
1710
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1711
|
+
res.end(code);
|
|
1712
|
+
} catch (error) {
|
|
1713
|
+
res.statusCode = 500;
|
|
1714
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
1715
|
+
res.end("Failed to generate mokup service worker.");
|
|
1716
|
+
logger.error("SW generation failed:", error);
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
if (serverRoutes.length > 0) {
|
|
1721
|
+
server.middlewares.use(createMiddleware(() => app, logger));
|
|
1722
|
+
}
|
|
1157
1723
|
if (!watchEnabled) {
|
|
1158
1724
|
return;
|
|
1159
1725
|
}
|
|
@@ -1179,8 +1745,36 @@ function createMokupPlugin(options = {}) {
|
|
|
1179
1745
|
async configurePreviewServer(server) {
|
|
1180
1746
|
currentServer = server;
|
|
1181
1747
|
await refreshRoutes(server);
|
|
1182
|
-
server
|
|
1183
|
-
|
|
1748
|
+
addMiddlewareFirst(server, playgroundMiddleware);
|
|
1749
|
+
const swPath = swConfig ? resolveSwRequestPath(swConfig.path) : null;
|
|
1750
|
+
if (swPath && hasSwRoutes()) {
|
|
1751
|
+
server.middlewares.use(async (req, res, next) => {
|
|
1752
|
+
const requestUrl = req.url ?? "/";
|
|
1753
|
+
const parsed = new URL(requestUrl, "http://mokup.local");
|
|
1754
|
+
if (parsed.pathname !== swPath) {
|
|
1755
|
+
return next();
|
|
1756
|
+
}
|
|
1757
|
+
try {
|
|
1758
|
+
const code = buildSwScript({
|
|
1759
|
+
routes: swRoutes,
|
|
1760
|
+
root,
|
|
1761
|
+
basePaths: swConfig?.basePaths ?? []
|
|
1762
|
+
});
|
|
1763
|
+
res.statusCode = 200;
|
|
1764
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
1765
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1766
|
+
res.end(code);
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
res.statusCode = 500;
|
|
1769
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
1770
|
+
res.end("Failed to generate mokup service worker.");
|
|
1771
|
+
logger.error("SW generation failed:", error);
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
if (serverRoutes.length > 0) {
|
|
1776
|
+
server.middlewares.use(createMiddleware(() => app, logger));
|
|
1777
|
+
}
|
|
1184
1778
|
if (!watchEnabled) {
|
|
1185
1779
|
return;
|
|
1186
1780
|
}
|