mokup 0.2.2 → 1.0.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.
@@ -0,0 +1,88 @@
1
+ import { MokupViteOptionsInput } from './index.js';
2
+ export { DirectoryConfig, HttpMethod, MockContext, MockMiddleware, MockResponse, MockResponseHandler, MockRule, MokupMockMode, MokupSwOptions, MokupViteOptions } from './index.js';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+ import '@mokup/shared/hono';
5
+
6
+ interface WebpackPluginInstance {
7
+ apply: (compiler: WebpackCompiler) => void;
8
+ }
9
+ interface WebpackDevMiddleware {
10
+ name?: string;
11
+ middleware: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void | Promise<void>;
12
+ }
13
+ interface WebpackDevServer {
14
+ app?: {
15
+ use: (middleware: WebpackDevMiddleware['middleware']) => void;
16
+ };
17
+ }
18
+ interface WebpackCompilation {
19
+ hooks: {
20
+ processAssets: {
21
+ tapPromise: (options: {
22
+ name: string;
23
+ stage: number;
24
+ }, handler: () => Promise<void>) => void;
25
+ };
26
+ };
27
+ emitAsset: (name: string, source: {
28
+ source: () => string;
29
+ }) => void;
30
+ updateAsset: (name: string, source: {
31
+ source: () => string;
32
+ }) => void;
33
+ getAsset: (name: string) => unknown;
34
+ getAssetPath: (name: string, data?: {
35
+ hash?: string;
36
+ }) => string;
37
+ outputOptions: {
38
+ publicPath?: unknown;
39
+ };
40
+ hash?: string;
41
+ }
42
+ interface WebpackCompiler {
43
+ context?: string;
44
+ options: {
45
+ output?: {
46
+ publicPath?: unknown;
47
+ assetModuleFilename?: unknown;
48
+ };
49
+ devServer?: {
50
+ setupMiddlewares?: (middlewares: WebpackDevMiddleware[], devServer: WebpackDevServer) => WebpackDevMiddleware[];
51
+ devMiddleware?: {
52
+ publicPath?: unknown;
53
+ };
54
+ };
55
+ };
56
+ hooks: {
57
+ beforeCompile: {
58
+ tapPromise: (name: string, handler: () => Promise<void>) => void;
59
+ };
60
+ thisCompilation: {
61
+ tap: (name: string, handler: (compilation: WebpackCompilation) => void) => void;
62
+ };
63
+ watchRun: {
64
+ tap: (name: string, handler: (compiler: WebpackCompiler) => void) => void;
65
+ };
66
+ watchClose: {
67
+ tap: (name: string, handler: () => void) => void;
68
+ };
69
+ };
70
+ watching?: {
71
+ invalidate: () => void;
72
+ };
73
+ webpack: {
74
+ Compilation: {
75
+ PROCESS_ASSETS_STAGE_ADDITIONS: number;
76
+ };
77
+ sources: {
78
+ RawSource: new (source: string) => {
79
+ source: () => string;
80
+ };
81
+ };
82
+ };
83
+ }
84
+ declare function createMokupWebpackPlugin(options?: MokupViteOptionsInput): WebpackPluginInstance;
85
+
86
+ // @ts-ignore
87
+ export = createMokupWebpackPlugin;
88
+ export { MokupViteOptionsInput, createMokupWebpackPlugin };
@@ -0,0 +1,442 @@
1
+ import { createRequire } from 'node:module';
2
+ import { cwd } from 'node:process';
3
+ import chokidar from '@mokup/shared/chokidar';
4
+ import { build } from '@mokup/shared/esbuild';
5
+ import { isAbsolute, resolve } from '@mokup/shared/pathe';
6
+ import { r as resolvePlaygroundOptions, a as resolveSwConfig, b as resolveSwUnregisterConfig, c as createPlaygroundMiddleware, e as createLogger, f as createMiddleware, i as isInDirs, g as createDebouncer, h as resolveDirs, s as scanRoutes, j as sortRoutes, k as createHonoApp, d as buildSwScript, t as toPosix } from './shared/mokup.C6zZ8YEh.mjs';
7
+ import 'node:buffer';
8
+ import '@mokup/shared/hono';
9
+ import 'node:fs';
10
+ import 'node:url';
11
+ import '@mokup/shared/jsonc-parser';
12
+ import '@mokup/runtime';
13
+
14
+ const pluginName = "mokup:webpack";
15
+ const lifecycleBaseName = "mokup-sw-lifecycle.js";
16
+ function normalizeOptions(options) {
17
+ const list = Array.isArray(options) ? options : [options];
18
+ return list.length > 0 ? list : [{}];
19
+ }
20
+ function resolvePlaygroundInput(list) {
21
+ for (const entry of list) {
22
+ if (typeof entry.playground !== "undefined") {
23
+ return entry.playground;
24
+ }
25
+ }
26
+ return void 0;
27
+ }
28
+ function normalizeBase(base) {
29
+ if (!base) {
30
+ return "/";
31
+ }
32
+ if (base.startsWith(".")) {
33
+ return "/";
34
+ }
35
+ let normalized = base.startsWith("/") ? base : `/${base}`;
36
+ if (!normalized.endsWith("/")) {
37
+ normalized = `${normalized}/`;
38
+ }
39
+ return normalized;
40
+ }
41
+ function resolveRegisterPath(base, path) {
42
+ const normalizedBase = normalizeBase(base);
43
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
44
+ if (normalizedPath.startsWith(normalizedBase)) {
45
+ return normalizedPath;
46
+ }
47
+ return `${normalizedBase}${normalizedPath.slice(1)}`;
48
+ }
49
+ function resolveRegisterScope(base, scope) {
50
+ const normalizedBase = normalizeBase(base);
51
+ const normalizedScope = scope.startsWith("/") ? scope : `/${scope}`;
52
+ if (normalizedScope.startsWith(normalizedBase)) {
53
+ return normalizedScope;
54
+ }
55
+ return `${normalizedBase}${normalizedScope.slice(1)}`;
56
+ }
57
+ function isAbsoluteUrl(value) {
58
+ return /^https?:\/\//.test(value);
59
+ }
60
+ function resolveBaseFromPublicPath(publicPath) {
61
+ if (typeof publicPath !== "string") {
62
+ return "/";
63
+ }
64
+ if (!publicPath || publicPath === "auto") {
65
+ return "/";
66
+ }
67
+ if (isAbsoluteUrl(publicPath)) {
68
+ return "/";
69
+ }
70
+ return normalizeBase(publicPath);
71
+ }
72
+ function resolveAssetsDir(assetModuleFilename) {
73
+ if (typeof assetModuleFilename !== "string") {
74
+ return "assets";
75
+ }
76
+ const normalized = assetModuleFilename.replace(/\\/g, "/");
77
+ const prefix = normalized.split("/")[0] ?? "";
78
+ if (!prefix || prefix.includes("[")) {
79
+ return "assets";
80
+ }
81
+ return prefix;
82
+ }
83
+ function joinPublicPath(publicPath, fileName) {
84
+ if (!publicPath) {
85
+ return fileName;
86
+ }
87
+ const normalized = publicPath.endsWith("/") ? publicPath : `${publicPath}/`;
88
+ return `${normalized}${fileName}`;
89
+ }
90
+ function buildSwLifecycleScript(params) {
91
+ const { base, importPath, swConfig, unregisterConfig, hasSwEntries, hasSwRoutes } = params;
92
+ const shouldUnregister = unregisterConfig.unregister === true || !hasSwEntries;
93
+ if (shouldUnregister) {
94
+ const path2 = resolveRegisterPath(base, unregisterConfig.path);
95
+ const scope2 = resolveRegisterScope(base, unregisterConfig.scope);
96
+ return [
97
+ `import { unregisterMokupServiceWorker } from ${JSON.stringify(importPath)}`,
98
+ "(async () => {",
99
+ ` await unregisterMokupServiceWorker({ path: ${JSON.stringify(path2)}, scope: ${JSON.stringify(scope2)} })`,
100
+ "})()"
101
+ ].join("\n");
102
+ }
103
+ if (!swConfig || swConfig.register === false) {
104
+ return null;
105
+ }
106
+ if (!hasSwRoutes) {
107
+ return null;
108
+ }
109
+ const path = resolveRegisterPath(base, swConfig.path);
110
+ const scope = resolveRegisterScope(base, swConfig.scope);
111
+ return [
112
+ `import { registerMokupServiceWorker } from ${JSON.stringify(importPath)}`,
113
+ "(async () => {",
114
+ ` const registration = await registerMokupServiceWorker({ path: ${JSON.stringify(path)}, scope: ${JSON.stringify(scope)} })`,
115
+ " if (import.meta.hot && registration) {",
116
+ " import.meta.hot.on('mokup:routes-changed', () => {",
117
+ " registration.update()",
118
+ " })",
119
+ " }",
120
+ "})()"
121
+ ].join("\n");
122
+ }
123
+ function resolveModuleFilePath(file, root) {
124
+ const absolute = isAbsolute(file) ? file : resolve(root, file);
125
+ const normalized = toPosix(absolute);
126
+ if (/^[a-z]:\//i.test(normalized)) {
127
+ return `file:///${normalized}`;
128
+ }
129
+ return normalized;
130
+ }
131
+ async function bundleScript(params) {
132
+ const result = await build({
133
+ stdin: {
134
+ contents: params.code,
135
+ resolveDir: params.root,
136
+ sourcefile: params.sourceName,
137
+ loader: "js"
138
+ },
139
+ absWorkingDir: params.root,
140
+ bundle: true,
141
+ platform: "browser",
142
+ format: "esm",
143
+ target: "es2020",
144
+ write: false
145
+ });
146
+ return result.outputFiles[0]?.text ?? "";
147
+ }
148
+ const require$1 = createRequire(import.meta.url);
149
+ function resolveHtmlWebpackPlugin() {
150
+ try {
151
+ const mod = require$1("html-webpack-plugin");
152
+ const plugin = mod.default ?? mod;
153
+ return plugin;
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+ function createMokupWebpackPlugin(options = {}) {
159
+ const optionList = normalizeOptions(options);
160
+ const logEnabled = optionList.every((entry) => entry.log !== false);
161
+ const watchEnabled = optionList.every((entry) => entry.watch !== false);
162
+ const playgroundConfig = resolvePlaygroundOptions(resolvePlaygroundInput(optionList));
163
+ const logger = createLogger(logEnabled);
164
+ const hasSwEntries = optionList.some((entry) => entry.mode === "sw");
165
+ const swConfig = resolveSwConfig(optionList, logger);
166
+ const unregisterConfig = resolveSwUnregisterConfig(optionList, logger);
167
+ let root = cwd();
168
+ let base = "/";
169
+ let assetsDir = "assets";
170
+ let routes = [];
171
+ let serverRoutes = [];
172
+ let swRoutes = [];
173
+ let app = null;
174
+ let watcher = null;
175
+ let watchingCompiler = null;
176
+ let swLifecycleBundle = null;
177
+ let swBundle = null;
178
+ let swLifecycleFileName = `${assetsDir}/${lifecycleBaseName}`;
179
+ let warnedHtml = false;
180
+ let buildPromise = null;
181
+ const resolveAllDirs = () => {
182
+ const dirs = [];
183
+ const seen = /* @__PURE__ */ new Set();
184
+ for (const entry of optionList) {
185
+ for (const dir of resolveDirs(entry.dir, root)) {
186
+ if (seen.has(dir)) {
187
+ continue;
188
+ }
189
+ seen.add(dir);
190
+ dirs.push(dir);
191
+ }
192
+ }
193
+ return dirs;
194
+ };
195
+ const hasSwRoutes = () => !!swConfig && swRoutes.length > 0;
196
+ const refreshRoutes = async () => {
197
+ const collected = [];
198
+ const collectedServer = [];
199
+ const collectedSw = [];
200
+ for (const entry of optionList) {
201
+ const dirs = resolveDirs(entry.dir, root);
202
+ const scanParams = {
203
+ dirs,
204
+ prefix: entry.prefix ?? "",
205
+ logger
206
+ };
207
+ if (entry.include) {
208
+ scanParams.include = entry.include;
209
+ }
210
+ if (entry.exclude) {
211
+ scanParams.exclude = entry.exclude;
212
+ }
213
+ const scanned = await scanRoutes(scanParams);
214
+ collected.push(...scanned);
215
+ if (entry.mode === "sw") {
216
+ collectedSw.push(...scanned);
217
+ if (entry.sw?.fallback !== false) {
218
+ collectedServer.push(...scanned);
219
+ }
220
+ } else {
221
+ collectedServer.push(...scanned);
222
+ }
223
+ }
224
+ routes = sortRoutes(collected);
225
+ serverRoutes = sortRoutes(collectedServer);
226
+ swRoutes = sortRoutes(collectedSw);
227
+ app = serverRoutes.length > 0 ? createHonoApp(serverRoutes) : null;
228
+ };
229
+ const rebuildBundles = async () => {
230
+ const lifecycle = buildSwLifecycleScript({
231
+ base,
232
+ importPath: "mokup/sw",
233
+ swConfig,
234
+ unregisterConfig,
235
+ hasSwEntries,
236
+ hasSwRoutes: hasSwRoutes()
237
+ });
238
+ swLifecycleBundle = lifecycle ? await bundleScript({
239
+ code: lifecycle,
240
+ root,
241
+ sourceName: lifecycleBaseName
242
+ }) : null;
243
+ if (swConfig && hasSwRoutes()) {
244
+ const swScript = buildSwScript({
245
+ routes: swRoutes,
246
+ root,
247
+ runtimeImportPath: "mokup/runtime",
248
+ basePaths: swConfig.basePaths ?? [],
249
+ resolveModulePath: resolveModuleFilePath
250
+ });
251
+ swBundle = await bundleScript({
252
+ code: swScript,
253
+ root,
254
+ sourceName: "mokup-sw.js"
255
+ });
256
+ } else {
257
+ swBundle = null;
258
+ }
259
+ };
260
+ const ensureBuilt = async () => {
261
+ if (!buildPromise) {
262
+ buildPromise = (async () => {
263
+ await refreshRoutes();
264
+ await rebuildBundles();
265
+ })().catch((error) => {
266
+ logger.error("Failed to build mokup bundles:", error);
267
+ }).finally(() => {
268
+ buildPromise = null;
269
+ });
270
+ }
271
+ await buildPromise;
272
+ };
273
+ const playgroundMiddleware = createPlaygroundMiddleware({
274
+ getRoutes: () => routes,
275
+ config: playgroundConfig,
276
+ logger,
277
+ getDirs: () => resolveAllDirs(),
278
+ getServer: () => ({ config: { base, root } })
279
+ });
280
+ const swMiddleware = async (req, res, next) => {
281
+ if (!swConfig || !hasSwRoutes()) {
282
+ return next();
283
+ }
284
+ const requestUrl = req.url ?? "/";
285
+ const parsed = new URL(requestUrl, "http://mokup.local");
286
+ const swPath = resolveRegisterPath(base, swConfig.path);
287
+ if (parsed.pathname !== swPath) {
288
+ return next();
289
+ }
290
+ await ensureBuilt();
291
+ if (!swBundle) {
292
+ res.statusCode = 500;
293
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
294
+ res.end("Failed to generate mokup service worker.");
295
+ return;
296
+ }
297
+ res.statusCode = 200;
298
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
299
+ res.setHeader("Cache-Control", "no-cache");
300
+ res.end(swBundle);
301
+ };
302
+ const mockMiddleware = createMiddleware(() => app, logger);
303
+ return {
304
+ apply(compiler) {
305
+ root = compiler.context ?? cwd();
306
+ assetsDir = resolveAssetsDir(compiler.options.output?.assetModuleFilename);
307
+ swLifecycleFileName = `${assetsDir}/${lifecycleBaseName}`;
308
+ base = resolveBaseFromPublicPath(compiler.options.output?.publicPath);
309
+ compiler.hooks.watchRun.tap(pluginName, (active) => {
310
+ watchingCompiler = active;
311
+ });
312
+ compiler.hooks.beforeCompile.tapPromise(pluginName, async () => {
313
+ const devPublicPath = compiler.options.devServer?.devMiddleware?.publicPath;
314
+ base = resolveBaseFromPublicPath(devPublicPath ?? compiler.options.output?.publicPath);
315
+ await ensureBuilt();
316
+ });
317
+ compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
318
+ const HtmlWebpackPlugin = resolveHtmlWebpackPlugin();
319
+ if (HtmlWebpackPlugin) {
320
+ const hooks = HtmlWebpackPlugin.getHooks(compilation);
321
+ const injectTag = () => {
322
+ if (!swLifecycleBundle) {
323
+ return;
324
+ }
325
+ const tagBase = {
326
+ tagName: "script",
327
+ voidTag: false,
328
+ attributes: {
329
+ type: "module"
330
+ },
331
+ meta: { plugin: pluginName }
332
+ };
333
+ if ("alterAssetTagGroups" in hooks && hooks.alterAssetTagGroups) {
334
+ hooks.alterAssetTagGroups.tap(pluginName, (data) => {
335
+ const src = joinPublicPath(data.publicPath ?? "", swLifecycleFileName);
336
+ data.headTags.unshift({
337
+ ...tagBase,
338
+ attributes: {
339
+ ...tagBase.attributes,
340
+ src
341
+ }
342
+ });
343
+ });
344
+ return;
345
+ }
346
+ if ("alterAssetTags" in hooks && hooks.alterAssetTags) {
347
+ hooks.alterAssetTags.tap(pluginName, (data) => {
348
+ const src = joinPublicPath(data.publicPath ?? "", swLifecycleFileName);
349
+ data.assetTags.scripts.unshift({
350
+ ...tagBase,
351
+ attributes: {
352
+ ...tagBase.attributes,
353
+ src
354
+ }
355
+ });
356
+ });
357
+ }
358
+ };
359
+ injectTag();
360
+ } else if (!warnedHtml) {
361
+ warnedHtml = true;
362
+ logger.warn("html-webpack-plugin not found; skip SW lifecycle injection.");
363
+ }
364
+ compilation.hooks.processAssets.tapPromise(
365
+ { name: pluginName, stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS },
366
+ async () => {
367
+ await ensureBuilt();
368
+ if (swLifecycleBundle) {
369
+ const RawSource = compiler.webpack.sources.RawSource;
370
+ const source = new RawSource(swLifecycleBundle);
371
+ if (compilation.getAsset(swLifecycleFileName)) {
372
+ compilation.updateAsset(swLifecycleFileName, source);
373
+ } else {
374
+ compilation.emitAsset(swLifecycleFileName, source);
375
+ }
376
+ }
377
+ if (swBundle && swConfig) {
378
+ const fileName = swConfig.path.startsWith("/") ? swConfig.path.slice(1) : swConfig.path;
379
+ const RawSource = compiler.webpack.sources.RawSource;
380
+ const source = new RawSource(swBundle);
381
+ if (compilation.getAsset(fileName)) {
382
+ compilation.updateAsset(fileName, source);
383
+ } else {
384
+ compilation.emitAsset(fileName, source);
385
+ }
386
+ }
387
+ }
388
+ );
389
+ });
390
+ const devServer = compiler.options.devServer;
391
+ if (devServer) {
392
+ const originalSetup = devServer.setupMiddlewares;
393
+ devServer.setupMiddlewares = (middlewares, server) => {
394
+ const devPublicPath = compiler.options.devServer?.devMiddleware?.publicPath;
395
+ base = resolveBaseFromPublicPath(devPublicPath ?? compiler.options.output?.publicPath);
396
+ void ensureBuilt();
397
+ const resolved = originalSetup ? originalSetup(middlewares, server) ?? middlewares : middlewares;
398
+ resolved.unshift(
399
+ { name: "mokup-playground", middleware: playgroundMiddleware },
400
+ { name: "mokup-sw", middleware: swMiddleware },
401
+ { name: "mokup-mock", middleware: mockMiddleware }
402
+ );
403
+ if (!watcher && watchEnabled) {
404
+ const dirs = resolveAllDirs();
405
+ watcher = chokidar.watch(dirs, { ignoreInitial: true });
406
+ const scheduleRefresh = createDebouncer(80, () => {
407
+ void refreshRoutes().then(rebuildBundles).then(() => {
408
+ if (watchingCompiler?.watching) {
409
+ watchingCompiler.watching.invalidate();
410
+ }
411
+ }).catch((error) => {
412
+ logger.error("Failed to refresh mokup routes:", error);
413
+ });
414
+ });
415
+ watcher.on("add", (file) => {
416
+ if (isInDirs(file, dirs)) {
417
+ scheduleRefresh();
418
+ }
419
+ });
420
+ watcher.on("change", (file) => {
421
+ if (isInDirs(file, dirs)) {
422
+ scheduleRefresh();
423
+ }
424
+ });
425
+ watcher.on("unlink", (file) => {
426
+ if (isInDirs(file, dirs)) {
427
+ scheduleRefresh();
428
+ }
429
+ });
430
+ }
431
+ return resolved;
432
+ };
433
+ }
434
+ compiler.hooks.watchClose.tap(pluginName, () => {
435
+ watcher?.close();
436
+ watcher = null;
437
+ });
438
+ }
439
+ };
440
+ }
441
+
442
+ export { createMokupWebpackPlugin, createMokupWebpackPlugin as default };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mokup",
3
3
  "type": "module",
4
- "version": "0.2.2",
4
+ "version": "1.0.1",
5
5
  "description": "Mock utilities and Vite plugin for mokup.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://mokup.icebreaker.top",
@@ -36,6 +36,11 @@
36
36
  "import": "./dist/vite.mjs",
37
37
  "require": "./dist/vite.cjs"
38
38
  },
39
+ "./webpack": {
40
+ "types": "./dist/webpack.d.ts",
41
+ "import": "./dist/webpack.mjs",
42
+ "require": "./dist/webpack.cjs"
43
+ },
39
44
  "./runtime": {
40
45
  "types": "./dist/runtime.d.ts",
41
46
  "import": "./dist/runtime.mjs",
@@ -60,18 +65,27 @@
60
65
  "dist"
61
66
  ],
62
67
  "peerDependencies": {
63
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
68
+ "html-webpack-plugin": "^5.0.0",
69
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
70
+ "webpack": "^5.0.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "html-webpack-plugin": {
74
+ "optional": true
75
+ },
76
+ "vite": {
77
+ "optional": true
78
+ },
79
+ "webpack": {
80
+ "optional": true
81
+ }
64
82
  },
65
83
  "dependencies": {
66
- "chokidar": "^5.0.0",
67
- "esbuild": "^0.27.2",
68
- "hono": "^4.11.4",
69
- "jsonc-parser": "^3.3.1",
70
- "pathe": "^2.0.3",
71
- "@mokup/playground": "0.0.4",
72
- "@mokup/server": "0.0.2",
73
- "@mokup/cli": "0.2.0",
74
- "@mokup/runtime": "0.1.0"
84
+ "@mokup/cli": "0.3.1",
85
+ "@mokup/playground": "0.0.5",
86
+ "@mokup/runtime": "0.1.1",
87
+ "@mokup/server": "1.0.1",
88
+ "@mokup/shared": "0.1.0"
75
89
  },
76
90
  "devDependencies": {
77
91
  "@types/node": "^25.0.9",