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