mokup 0.1.0 → 0.2.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/dist/vite.cjs CHANGED
@@ -1,15 +1,15 @@
1
1
  'use strict';
2
2
 
3
+ const node_fs = require('node:fs');
3
4
  const node_process = require('node:process');
5
+ const node_url = require('node:url');
4
6
  const chokidar = require('chokidar');
5
7
  const node_buffer = require('node:buffer');
6
8
  const hono = require('hono');
7
9
  const patternRouter = require('hono/router/pattern-router');
8
10
  const pathe = require('pathe');
9
- const node_fs = require('node:fs');
10
11
  const node_module = require('node:module');
11
12
  const runtime = require('@mokup/runtime');
12
- const node_url = require('node:url');
13
13
  const esbuild = require('esbuild');
14
14
  const jsoncParser = require('jsonc-parser');
15
15
 
@@ -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 ?? response.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
- if (!pathname.startsWith(playgroundPath)) {
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(playgroundPath.length);
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", `${playgroundPath}/${suffix}`);
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
- const server = params.getServer?.();
529
- const output = isViteDevServer$1(server) ? injectPlaygroundHmr(html, server.config.base ?? "/") : html;
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,13 @@ 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: playgroundPath,
597
+ basePath: matchedPath,
598
+ root: baseRoot,
547
599
  count: routes.length,
548
600
  groups: groups.map((group) => ({ key: group.key, label: group.label })),
549
601
  routes: routes.map((route) => toPlaygroundRoute(route, baseRoot, groups))
@@ -768,12 +820,12 @@ function normalizeMiddlewares(value, source, logger) {
768
820
  }
769
821
  const list = Array.isArray(value) ? value : [value];
770
822
  const middlewares = [];
771
- for (const entry of list) {
823
+ for (const [index, entry] of list.entries()) {
772
824
  if (typeof entry !== "function") {
773
825
  logger.warn(`Invalid middleware in ${source}`);
774
826
  continue;
775
827
  }
776
- middlewares.push({ handle: entry, source });
828
+ middlewares.push({ handle: entry, source, index });
777
829
  }
778
830
  return middlewares;
779
831
  }
@@ -996,7 +1048,7 @@ async function scanRoutes(params) {
996
1048
  continue;
997
1049
  }
998
1050
  const rules = await loadRules(fileInfo.file, params.server, params.logger);
999
- for (const rule of rules) {
1051
+ for (const [index, rule] of rules.entries()) {
1000
1052
  if (!rule || typeof rule !== "object") {
1001
1053
  continue;
1002
1054
  }
@@ -1025,6 +1077,7 @@ async function scanRoutes(params) {
1025
1077
  if (!resolved) {
1026
1078
  continue;
1027
1079
  }
1080
+ resolved.ruleIndex = index;
1028
1081
  if (config.headers) {
1029
1082
  resolved.headers = { ...config.headers, ...resolved.headers ?? {} };
1030
1083
  }
@@ -1048,6 +1101,330 @@ async function scanRoutes(params) {
1048
1101
  return sortRoutes(routes);
1049
1102
  }
1050
1103
 
1104
+ const defaultSwPath = "/mokup-sw.js";
1105
+ const defaultSwScope = "/";
1106
+ function normalizeSwPath(path) {
1107
+ if (!path) {
1108
+ return defaultSwPath;
1109
+ }
1110
+ return path.startsWith("/") ? path : `/${path}`;
1111
+ }
1112
+ function normalizeSwScope(scope) {
1113
+ if (!scope) {
1114
+ return defaultSwScope;
1115
+ }
1116
+ return scope.startsWith("/") ? scope : `/${scope}`;
1117
+ }
1118
+ function normalizeBasePath(value) {
1119
+ if (!value) {
1120
+ return "/";
1121
+ }
1122
+ const normalized = value.startsWith("/") ? value : `/${value}`;
1123
+ if (normalized.length > 1 && normalized.endsWith("/")) {
1124
+ return normalized.slice(0, -1);
1125
+ }
1126
+ return normalized;
1127
+ }
1128
+ function resolveSwConfigFromEntries(entries, logger) {
1129
+ let path = defaultSwPath;
1130
+ let scope = defaultSwScope;
1131
+ let register = true;
1132
+ let unregister = false;
1133
+ const basePaths = [];
1134
+ let hasPath = false;
1135
+ let hasScope = false;
1136
+ let hasRegister = false;
1137
+ let hasUnregister = false;
1138
+ for (const entry of entries) {
1139
+ const config = entry.sw;
1140
+ if (config?.path) {
1141
+ const next = normalizeSwPath(config.path);
1142
+ if (!hasPath) {
1143
+ path = next;
1144
+ hasPath = true;
1145
+ } else if (path !== next) {
1146
+ logger.warn(`SW path "${next}" ignored; using "${path}".`);
1147
+ }
1148
+ }
1149
+ if (config?.scope) {
1150
+ const next = normalizeSwScope(config.scope);
1151
+ if (!hasScope) {
1152
+ scope = next;
1153
+ hasScope = true;
1154
+ } else if (scope !== next) {
1155
+ logger.warn(`SW scope "${next}" ignored; using "${scope}".`);
1156
+ }
1157
+ }
1158
+ if (typeof config?.register === "boolean") {
1159
+ if (!hasRegister) {
1160
+ register = config.register;
1161
+ hasRegister = true;
1162
+ } else if (register !== config.register) {
1163
+ logger.warn(
1164
+ `SW register="${String(config.register)}" ignored; using "${String(register)}".`
1165
+ );
1166
+ }
1167
+ }
1168
+ if (typeof config?.unregister === "boolean") {
1169
+ if (!hasUnregister) {
1170
+ unregister = config.unregister;
1171
+ hasUnregister = true;
1172
+ } else if (unregister !== config.unregister) {
1173
+ logger.warn(
1174
+ `SW unregister="${String(config.unregister)}" ignored; using "${String(unregister)}".`
1175
+ );
1176
+ }
1177
+ }
1178
+ if (typeof config?.basePath !== "undefined") {
1179
+ const values = Array.isArray(config.basePath) ? config.basePath : [config.basePath];
1180
+ for (const value of values) {
1181
+ basePaths.push(normalizeBasePath(value));
1182
+ }
1183
+ continue;
1184
+ }
1185
+ const normalizedPrefix = normalizePrefix(entry.prefix ?? "");
1186
+ if (normalizedPrefix) {
1187
+ basePaths.push(normalizedPrefix);
1188
+ }
1189
+ }
1190
+ return {
1191
+ path,
1192
+ scope,
1193
+ register,
1194
+ unregister,
1195
+ basePaths: Array.from(new Set(basePaths))
1196
+ };
1197
+ }
1198
+ function resolveSwConfig(options, logger) {
1199
+ const swEntries = options.filter((entry) => entry.mode === "sw");
1200
+ if (swEntries.length === 0) {
1201
+ return null;
1202
+ }
1203
+ return resolveSwConfigFromEntries(swEntries, logger);
1204
+ }
1205
+ function resolveSwUnregisterConfig(options, logger) {
1206
+ return resolveSwConfigFromEntries(options, logger);
1207
+ }
1208
+ function toViteImportPath(file, root) {
1209
+ const absolute = pathe.isAbsolute(file) ? file : pathe.resolve(root, file);
1210
+ const rel = pathe.relative(root, absolute);
1211
+ if (!rel.startsWith("..") && !pathe.isAbsolute(rel)) {
1212
+ return `/${toPosix(rel)}`;
1213
+ }
1214
+ return `/@fs/${toPosix(absolute)}`;
1215
+ }
1216
+ function shouldModuleize(handler) {
1217
+ if (typeof handler === "function") {
1218
+ return true;
1219
+ }
1220
+ if (typeof Response !== "undefined" && handler instanceof Response) {
1221
+ return true;
1222
+ }
1223
+ return false;
1224
+ }
1225
+ function toBinaryBody(handler) {
1226
+ if (handler instanceof ArrayBuffer) {
1227
+ return node_buffer.Buffer.from(new Uint8Array(handler)).toString("base64");
1228
+ }
1229
+ if (handler instanceof Uint8Array) {
1230
+ return node_buffer.Buffer.from(handler).toString("base64");
1231
+ }
1232
+ if (node_buffer.Buffer.isBuffer(handler)) {
1233
+ return handler.toString("base64");
1234
+ }
1235
+ return null;
1236
+ }
1237
+ function buildManifestResponse(route, moduleId) {
1238
+ if (moduleId) {
1239
+ const response = {
1240
+ type: "module",
1241
+ module: moduleId
1242
+ };
1243
+ if (typeof route.ruleIndex === "number") {
1244
+ response.ruleIndex = route.ruleIndex;
1245
+ }
1246
+ return response;
1247
+ }
1248
+ const handler = route.handler;
1249
+ if (typeof handler === "string") {
1250
+ return {
1251
+ type: "text",
1252
+ body: handler
1253
+ };
1254
+ }
1255
+ const binary = toBinaryBody(handler);
1256
+ if (binary) {
1257
+ return {
1258
+ type: "binary",
1259
+ body: binary,
1260
+ encoding: "base64"
1261
+ };
1262
+ }
1263
+ return {
1264
+ type: "json",
1265
+ body: handler
1266
+ };
1267
+ }
1268
+ function buildSwScript(params) {
1269
+ const { routes, root } = params;
1270
+ const runtimeImportPath = params.runtimeImportPath ?? "mokup/runtime";
1271
+ const basePaths = params.basePaths ?? [];
1272
+ const ruleModules = /* @__PURE__ */ new Map();
1273
+ const middlewareModules = /* @__PURE__ */ new Map();
1274
+ const manifestRoutes = routes.map((route) => {
1275
+ const moduleId = shouldModuleize(route.handler) ? toViteImportPath(route.file, root) : null;
1276
+ if (moduleId) {
1277
+ ruleModules.set(moduleId, moduleId);
1278
+ }
1279
+ const middleware = route.middlewares?.map((entry) => {
1280
+ const modulePath = toViteImportPath(entry.source, root);
1281
+ middlewareModules.set(modulePath, modulePath);
1282
+ return {
1283
+ module: modulePath,
1284
+ ruleIndex: entry.index
1285
+ };
1286
+ });
1287
+ const response = buildManifestResponse(route, moduleId);
1288
+ const manifestRoute = {
1289
+ method: route.method,
1290
+ url: route.template,
1291
+ ...route.tokens ? { tokens: route.tokens } : {},
1292
+ ...route.score ? { score: route.score } : {},
1293
+ ...route.status ? { status: route.status } : {},
1294
+ ...route.headers ? { headers: route.headers } : {},
1295
+ ...route.delay ? { delay: route.delay } : {},
1296
+ ...middleware && middleware.length > 0 ? { middleware } : {},
1297
+ response
1298
+ };
1299
+ return manifestRoute;
1300
+ });
1301
+ const manifest = {
1302
+ version: 1,
1303
+ routes: manifestRoutes
1304
+ };
1305
+ const imports = [
1306
+ `import { createRuntimeApp, handle } from ${JSON.stringify(runtimeImportPath)}`
1307
+ ];
1308
+ const moduleEntries = [];
1309
+ let moduleIndex = 0;
1310
+ for (const id of ruleModules.keys()) {
1311
+ const name = `module${moduleIndex++}`;
1312
+ imports.push(`import * as ${name} from '${id}'`);
1313
+ moduleEntries.push({ id, name, kind: "rule" });
1314
+ }
1315
+ for (const id of middlewareModules.keys()) {
1316
+ const name = `module${moduleIndex++}`;
1317
+ imports.push(`import * as ${name} from '${id}'`);
1318
+ moduleEntries.push({ id, name, kind: "middleware" });
1319
+ }
1320
+ const lines = [];
1321
+ lines.push(...imports, "");
1322
+ lines.push(
1323
+ "const resolveModuleExport = (mod) => mod?.default ?? mod",
1324
+ "",
1325
+ "const toRuntimeRule = (value) => {",
1326
+ " if (typeof value === 'undefined') {",
1327
+ " return null",
1328
+ " }",
1329
+ " if (typeof value === 'function') {",
1330
+ " return { response: value }",
1331
+ " }",
1332
+ " if (value === null) {",
1333
+ " return { response: null }",
1334
+ " }",
1335
+ " if (typeof value === 'object') {",
1336
+ " if ('response' in value) {",
1337
+ " return value",
1338
+ " }",
1339
+ " if ('handler' in value) {",
1340
+ " const handlerRule = value",
1341
+ " return {",
1342
+ " response: handlerRule.handler,",
1343
+ " ...(typeof handlerRule.status === 'number' ? { status: handlerRule.status } : {}),",
1344
+ " ...(handlerRule.headers ? { headers: handlerRule.headers } : {}),",
1345
+ " ...(typeof handlerRule.delay === 'number' ? { delay: handlerRule.delay } : {}),",
1346
+ " }",
1347
+ " }",
1348
+ " return { response: value }",
1349
+ " }",
1350
+ " return { response: value }",
1351
+ "}",
1352
+ "",
1353
+ "const toRuntimeRules = (value) => {",
1354
+ " if (typeof value === 'undefined') {",
1355
+ " return []",
1356
+ " }",
1357
+ " if (Array.isArray(value)) {",
1358
+ " return value.map(toRuntimeRule).filter(Boolean)",
1359
+ " }",
1360
+ " const rule = toRuntimeRule(value)",
1361
+ " return rule ? [rule] : []",
1362
+ "}",
1363
+ ""
1364
+ );
1365
+ lines.push(
1366
+ `const manifest = ${JSON.stringify(manifest, null, 2)}`,
1367
+ ""
1368
+ );
1369
+ if (moduleEntries.length > 0) {
1370
+ lines.push("const moduleMap = {");
1371
+ for (const entry of moduleEntries) {
1372
+ if (entry.kind === "rule") {
1373
+ lines.push(
1374
+ ` ${JSON.stringify(entry.id)}: { default: toRuntimeRules(resolveModuleExport(${entry.name})) },`
1375
+ );
1376
+ continue;
1377
+ }
1378
+ lines.push(
1379
+ ` ${JSON.stringify(entry.id)}: ${entry.name},`
1380
+ );
1381
+ }
1382
+ lines.push("}", "");
1383
+ }
1384
+ const runtimeOptions = moduleEntries.length > 0 ? "{ manifest, moduleMap }" : "{ manifest }";
1385
+ lines.push(
1386
+ `const basePaths = ${JSON.stringify(basePaths)}`,
1387
+ "",
1388
+ "self.addEventListener('install', () => {",
1389
+ " self.skipWaiting()",
1390
+ "})",
1391
+ "",
1392
+ "self.addEventListener('activate', (event) => {",
1393
+ " event.waitUntil(self.clients.claim())",
1394
+ "})",
1395
+ "",
1396
+ "const shouldHandle = (request) => {",
1397
+ " if (!basePaths || basePaths.length === 0) {",
1398
+ " return true",
1399
+ " }",
1400
+ " const pathname = new URL(request.url).pathname",
1401
+ " return basePaths.some((basePath) => {",
1402
+ " if (basePath === '/') {",
1403
+ " return true",
1404
+ " }",
1405
+ " return pathname === basePath || pathname.startsWith(basePath + '/')",
1406
+ " })",
1407
+ "}",
1408
+ "",
1409
+ "const registerHandler = async () => {",
1410
+ ` const app = await createRuntimeApp(${runtimeOptions})`,
1411
+ " const handler = handle(app)",
1412
+ " self.addEventListener('fetch', (event) => {",
1413
+ " if (!shouldHandle(event.request)) {",
1414
+ " return",
1415
+ " }",
1416
+ " handler(event)",
1417
+ " })",
1418
+ "}",
1419
+ "",
1420
+ "registerHandler().catch((error) => {",
1421
+ " console.error('[mokup] Failed to build service worker app:', error)",
1422
+ "})",
1423
+ ""
1424
+ );
1425
+ return lines.join("\n");
1426
+ }
1427
+
1051
1428
  function buildRouteSignature(routes) {
1052
1429
  return routes.map(
1053
1430
  (route) => [
@@ -1075,9 +1452,75 @@ function resolvePlaygroundInput(list) {
1075
1452
  }
1076
1453
  return void 0;
1077
1454
  }
1455
+ function normalizeBase(base) {
1456
+ if (!base) {
1457
+ return "/";
1458
+ }
1459
+ if (base.startsWith(".")) {
1460
+ return "/";
1461
+ }
1462
+ let normalized = base.startsWith("/") ? base : `/${base}`;
1463
+ if (!normalized.endsWith("/")) {
1464
+ normalized = `${normalized}/`;
1465
+ }
1466
+ return normalized;
1467
+ }
1468
+ function resolveRegisterPath(base, path) {
1469
+ const normalizedBase = normalizeBase(base);
1470
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1471
+ if (normalizedPath.startsWith(normalizedBase)) {
1472
+ return normalizedPath;
1473
+ }
1474
+ return `${normalizedBase}${normalizedPath.slice(1)}`;
1475
+ }
1476
+ function resolveRegisterScope(base, scope) {
1477
+ const normalizedBase = normalizeBase(base);
1478
+ const normalizedScope = scope.startsWith("/") ? scope : `/${scope}`;
1479
+ if (normalizedScope.startsWith(normalizedBase)) {
1480
+ return normalizedScope;
1481
+ }
1482
+ return `${normalizedBase}${normalizedScope.slice(1)}`;
1483
+ }
1484
+ function resolveSwImportPath(base) {
1485
+ const normalizedBase = normalizeBase(base);
1486
+ return `${normalizedBase}@id/mokup/sw`;
1487
+ }
1488
+ function resolveSwRuntimeImportPath(base) {
1489
+ const normalizedBase = normalizeBase(base);
1490
+ return `${normalizedBase}@id/mokup/runtime`;
1491
+ }
1492
+ const swModuleCandidates = [
1493
+ new URL("../sw.ts", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite.cjs', document.baseURI).href))),
1494
+ new URL("../sw.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite.cjs', document.baseURI).href)))
1495
+ ];
1496
+ const localSwModulePath = (() => {
1497
+ for (const candidate of swModuleCandidates) {
1498
+ const filePath = node_url.fileURLToPath(candidate);
1499
+ if (node_fs.existsSync(filePath)) {
1500
+ return filePath;
1501
+ }
1502
+ }
1503
+ return node_url.fileURLToPath(swModuleCandidates[0] ?? new URL("../sw.ts", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vite.cjs', document.baseURI).href))));
1504
+ })();
1505
+ function hasMiddlewareStack(middlewares) {
1506
+ const candidate = middlewares;
1507
+ return Array.isArray(candidate.stack);
1508
+ }
1509
+ function addMiddlewareFirst(server, middleware) {
1510
+ if (hasMiddlewareStack(server.middlewares)) {
1511
+ server.middlewares.stack.unshift({ route: "", handle: middleware });
1512
+ return;
1513
+ }
1514
+ server.middlewares.use(middleware);
1515
+ }
1078
1516
  function createMokupPlugin(options = {}) {
1079
1517
  let root = node_process.cwd();
1518
+ let base = "/";
1519
+ let command = "serve";
1520
+ let assetsDir = "assets";
1080
1521
  let routes = [];
1522
+ let serverRoutes = [];
1523
+ let swRoutes = [];
1081
1524
  let app = null;
1082
1525
  let previewWatcher = null;
1083
1526
  let currentServer = null;
@@ -1087,6 +1530,9 @@ function createMokupPlugin(options = {}) {
1087
1530
  const watchEnabled = optionList.every((entry) => entry.watch !== false);
1088
1531
  const playgroundConfig = resolvePlaygroundOptions(resolvePlaygroundInput(optionList));
1089
1532
  const logger = createLogger(logEnabled);
1533
+ const hasSwEntries = optionList.some((entry) => entry.mode === "sw");
1534
+ const swConfig = resolveSwConfig(optionList, logger);
1535
+ const unregisterConfig = resolveSwUnregisterConfig(optionList, logger);
1090
1536
  const resolveAllDirs = () => {
1091
1537
  const dirs = [];
1092
1538
  const seen = /* @__PURE__ */ new Set();
@@ -1101,15 +1547,85 @@ function createMokupPlugin(options = {}) {
1101
1547
  }
1102
1548
  return dirs;
1103
1549
  };
1550
+ const hasSwRoutes = () => !!swConfig && swRoutes.length > 0;
1551
+ const resolveSwRequestPath = (path) => resolveRegisterPath(base, path);
1552
+ const resolveSwRegisterScope = (scope) => resolveRegisterScope(base, scope);
1553
+ const resolveHtmlAssetPath = (fileName) => {
1554
+ const normalizedFileName = fileName.startsWith("/") ? fileName.slice(1) : fileName;
1555
+ if (base && base.startsWith(".")) {
1556
+ return normalizedFileName;
1557
+ }
1558
+ const normalizedBase = normalizeBase(base);
1559
+ return `${normalizedBase}${normalizedFileName}`;
1560
+ };
1561
+ const resolveAssetsFileName = (fileName) => {
1562
+ const trimmed = assetsDir.replace(/^\/+|\/+$/g, "");
1563
+ if (!trimmed) {
1564
+ return fileName;
1565
+ }
1566
+ return `${trimmed}/${fileName}`;
1567
+ };
1568
+ const swVirtualId = "virtual:mokup-sw";
1569
+ const resolvedSwVirtualId = `\0${swVirtualId}`;
1570
+ const swLifecycleVirtualId = "virtual:mokup-sw-lifecycle";
1571
+ const resolvedSwLifecycleVirtualId = `\0${swLifecycleVirtualId}`;
1572
+ let swLifecycleFileName = null;
1573
+ let swLifecycleScript = null;
1574
+ async function resolveSwModuleImport(context) {
1575
+ const resolved = await context.resolve("mokup/sw");
1576
+ if (resolved?.id) {
1577
+ return resolved.id;
1578
+ }
1579
+ const fallbackResolved = await context.resolve(localSwModulePath);
1580
+ if (fallbackResolved?.id) {
1581
+ return fallbackResolved.id;
1582
+ }
1583
+ return localSwModulePath;
1584
+ }
1585
+ function buildSwLifecycleScript(importPath = "mokup/sw") {
1586
+ const shouldUnregister = unregisterConfig.unregister === true || !hasSwEntries;
1587
+ if (shouldUnregister) {
1588
+ const path2 = resolveSwRequestPath(unregisterConfig.path);
1589
+ const scope2 = resolveSwRegisterScope(unregisterConfig.scope);
1590
+ return [
1591
+ `import { unregisterMokupServiceWorker } from ${JSON.stringify(importPath)}`,
1592
+ "(async () => {",
1593
+ ` await unregisterMokupServiceWorker({ path: ${JSON.stringify(path2)}, scope: ${JSON.stringify(scope2)} })`,
1594
+ "})()"
1595
+ ].join("\n");
1596
+ }
1597
+ if (!swConfig || swConfig.register === false) {
1598
+ return null;
1599
+ }
1600
+ if (!hasSwRoutes()) {
1601
+ return null;
1602
+ }
1603
+ const path = resolveSwRequestPath(swConfig.path);
1604
+ const scope = resolveSwRegisterScope(swConfig.scope);
1605
+ return [
1606
+ `import { registerMokupServiceWorker } from ${JSON.stringify(importPath)}`,
1607
+ "(async () => {",
1608
+ ` const registration = await registerMokupServiceWorker({ path: ${JSON.stringify(path)}, scope: ${JSON.stringify(scope)} })`,
1609
+ " if (import.meta.hot && registration) {",
1610
+ " import.meta.hot.on('mokup:routes-changed', () => {",
1611
+ " registration.update()",
1612
+ " })",
1613
+ " }",
1614
+ "})()"
1615
+ ].join("\n");
1616
+ }
1104
1617
  const playgroundMiddleware = createPlaygroundMiddleware({
1105
1618
  getRoutes: () => routes,
1106
1619
  config: playgroundConfig,
1107
1620
  logger,
1108
1621
  getServer: () => currentServer,
1109
- getDirs: () => resolveAllDirs()
1622
+ getDirs: () => resolveAllDirs(),
1623
+ getSwScript: () => buildSwLifecycleScript(resolveSwImportPath(base))
1110
1624
  });
1111
1625
  const refreshRoutes = async (server) => {
1112
1626
  const collected = [];
1627
+ const collectedServer = [];
1628
+ const collectedSw = [];
1113
1629
  for (const entry of optionList) {
1114
1630
  const dirs = resolveDirs(entry.dir, root);
1115
1631
  const scanParams = {
@@ -1128,9 +1644,19 @@ function createMokupPlugin(options = {}) {
1128
1644
  }
1129
1645
  const scanned = await scanRoutes(scanParams);
1130
1646
  collected.push(...scanned);
1647
+ if (entry.mode === "sw") {
1648
+ collectedSw.push(...scanned);
1649
+ if (entry.sw?.fallback !== false) {
1650
+ collectedServer.push(...scanned);
1651
+ }
1652
+ } else {
1653
+ collectedServer.push(...scanned);
1654
+ }
1131
1655
  }
1132
1656
  routes = sortRoutes(collected);
1133
- app = createHonoApp(routes);
1657
+ serverRoutes = sortRoutes(collectedServer);
1658
+ swRoutes = sortRoutes(collectedSw);
1659
+ app = serverRoutes.length > 0 ? createHonoApp(serverRoutes) : null;
1134
1660
  const signature = buildRouteSignature(routes);
1135
1661
  if (isViteDevServer(server) && server.ws) {
1136
1662
  if (lastSignature && signature !== lastSignature) {
@@ -1146,14 +1672,141 @@ function createMokupPlugin(options = {}) {
1146
1672
  return {
1147
1673
  name: "mokup:vite",
1148
1674
  enforce: "pre",
1675
+ resolveId(id) {
1676
+ if (id === swVirtualId) {
1677
+ return resolvedSwVirtualId;
1678
+ }
1679
+ if (id === swLifecycleVirtualId) {
1680
+ return resolvedSwLifecycleVirtualId;
1681
+ }
1682
+ return null;
1683
+ },
1684
+ async load(id) {
1685
+ if (id !== resolvedSwVirtualId) {
1686
+ if (id !== resolvedSwLifecycleVirtualId) {
1687
+ return null;
1688
+ }
1689
+ if (!swLifecycleScript) {
1690
+ if (swRoutes.length === 0) {
1691
+ await refreshRoutes();
1692
+ }
1693
+ const importPath = await resolveSwModuleImport(this);
1694
+ swLifecycleScript = buildSwLifecycleScript(importPath);
1695
+ }
1696
+ return swLifecycleScript ?? "";
1697
+ }
1698
+ if (swRoutes.length === 0) {
1699
+ await refreshRoutes();
1700
+ }
1701
+ return buildSwScript({
1702
+ routes: swRoutes,
1703
+ root,
1704
+ basePaths: swConfig?.basePaths ?? []
1705
+ });
1706
+ },
1707
+ async buildStart() {
1708
+ if (command !== "build") {
1709
+ return;
1710
+ }
1711
+ await refreshRoutes();
1712
+ const shouldInject = buildSwLifecycleScript() !== null;
1713
+ swLifecycleScript = null;
1714
+ if (shouldInject) {
1715
+ swLifecycleFileName = resolveAssetsFileName("mokup-sw-lifecycle.js");
1716
+ this.emitFile({
1717
+ type: "chunk",
1718
+ id: swLifecycleVirtualId,
1719
+ fileName: swLifecycleFileName
1720
+ });
1721
+ } else {
1722
+ swLifecycleFileName = null;
1723
+ }
1724
+ if (!swConfig || !hasSwRoutes()) {
1725
+ return;
1726
+ }
1727
+ const fileName = swConfig.path.startsWith("/") ? swConfig.path.slice(1) : swConfig.path;
1728
+ this.emitFile({
1729
+ type: "chunk",
1730
+ id: swVirtualId,
1731
+ fileName
1732
+ });
1733
+ },
1734
+ async transformIndexHtml(html) {
1735
+ if (swRoutes.length === 0) {
1736
+ await refreshRoutes(currentServer ?? void 0);
1737
+ }
1738
+ const script = buildSwLifecycleScript();
1739
+ if (!script) {
1740
+ return html;
1741
+ }
1742
+ if (command === "build") {
1743
+ if (!swLifecycleFileName) {
1744
+ return html;
1745
+ }
1746
+ const src = resolveHtmlAssetPath(swLifecycleFileName);
1747
+ return {
1748
+ html,
1749
+ tags: [
1750
+ {
1751
+ tag: "script",
1752
+ attrs: { type: "module", src },
1753
+ injectTo: "head"
1754
+ }
1755
+ ]
1756
+ };
1757
+ }
1758
+ return {
1759
+ html,
1760
+ tags: [
1761
+ {
1762
+ tag: "script",
1763
+ attrs: { type: "module" },
1764
+ children: script,
1765
+ injectTo: "head"
1766
+ }
1767
+ ]
1768
+ };
1769
+ },
1149
1770
  configResolved(config) {
1150
1771
  root = config.root;
1772
+ base = config.base ?? "/";
1773
+ command = config.command;
1774
+ assetsDir = config.build.assetsDir ?? "assets";
1151
1775
  },
1152
1776
  async configureServer(server) {
1153
1777
  currentServer = server;
1154
1778
  await refreshRoutes(server);
1155
- server.middlewares.use(playgroundMiddleware);
1156
- server.middlewares.use(createMiddleware(() => app, logger));
1779
+ addMiddlewareFirst(server, playgroundMiddleware);
1780
+ const swPath = swConfig ? resolveSwRequestPath(swConfig.path) : null;
1781
+ if (swPath && hasSwRoutes()) {
1782
+ server.middlewares.use(async (req, res, next) => {
1783
+ const requestUrl = req.url ?? "/";
1784
+ const parsed = new URL(requestUrl, "http://mokup.local");
1785
+ if (parsed.pathname !== swPath) {
1786
+ return next();
1787
+ }
1788
+ try {
1789
+ const code = buildSwScript({
1790
+ routes: swRoutes,
1791
+ root,
1792
+ runtimeImportPath: resolveSwRuntimeImportPath(base),
1793
+ basePaths: swConfig?.basePaths ?? []
1794
+ });
1795
+ res.statusCode = 200;
1796
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
1797
+ res.setHeader("Cache-Control", "no-cache");
1798
+ res.end(code);
1799
+ } catch (error) {
1800
+ res.statusCode = 500;
1801
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
1802
+ res.end("Failed to generate mokup service worker.");
1803
+ logger.error("SW generation failed:", error);
1804
+ }
1805
+ });
1806
+ }
1807
+ if (serverRoutes.length > 0) {
1808
+ server.middlewares.use(createMiddleware(() => app, logger));
1809
+ }
1157
1810
  if (!watchEnabled) {
1158
1811
  return;
1159
1812
  }
@@ -1179,8 +1832,36 @@ function createMokupPlugin(options = {}) {
1179
1832
  async configurePreviewServer(server) {
1180
1833
  currentServer = server;
1181
1834
  await refreshRoutes(server);
1182
- server.middlewares.use(playgroundMiddleware);
1183
- server.middlewares.use(createMiddleware(() => app, logger));
1835
+ addMiddlewareFirst(server, playgroundMiddleware);
1836
+ const swPath = swConfig ? resolveSwRequestPath(swConfig.path) : null;
1837
+ if (swPath && hasSwRoutes()) {
1838
+ server.middlewares.use(async (req, res, next) => {
1839
+ const requestUrl = req.url ?? "/";
1840
+ const parsed = new URL(requestUrl, "http://mokup.local");
1841
+ if (parsed.pathname !== swPath) {
1842
+ return next();
1843
+ }
1844
+ try {
1845
+ const code = buildSwScript({
1846
+ routes: swRoutes,
1847
+ root,
1848
+ basePaths: swConfig?.basePaths ?? []
1849
+ });
1850
+ res.statusCode = 200;
1851
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
1852
+ res.setHeader("Cache-Control", "no-cache");
1853
+ res.end(code);
1854
+ } catch (error) {
1855
+ res.statusCode = 500;
1856
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
1857
+ res.end("Failed to generate mokup service worker.");
1858
+ logger.error("SW generation failed:", error);
1859
+ }
1860
+ });
1861
+ }
1862
+ if (serverRoutes.length > 0) {
1863
+ server.middlewares.use(createMiddleware(() => app, logger));
1864
+ }
1184
1865
  if (!watchEnabled) {
1185
1866
  return;
1186
1867
  }