@farm.js/msw 0.1.0-beta.0
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/LICENSE +22 -0
- package/README.md +51 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +23 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +29 -0
- package/dist/handlers.d.ts +3 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +209 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Farm.js Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @farm.js/msw
|
|
2
|
+
|
|
3
|
+
Run one set of [Mock Service Worker](https://mswjs.io/) handlers in the browser and during Farm
|
|
4
|
+
development SSR.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add -D @farm.js/msw
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Configure
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { defineConfig } from "@farm.js/core";
|
|
16
|
+
import { msw } from "@farm.js/msw";
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
plugins: [msw({ handlers: "./src/mocks/handlers.ts" })],
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { delay, http, HttpResponse } from "@farm.js/msw/handlers";
|
|
25
|
+
|
|
26
|
+
export const handlers = [
|
|
27
|
+
http.get("/api/products", async () => {
|
|
28
|
+
await delay(200);
|
|
29
|
+
return HttpResponse.json([{ id: "tractor", name: "Tractor" }]);
|
|
30
|
+
}),
|
|
31
|
+
];
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The plugin starts MSW's Node interceptor before development requests reach Farm and starts the
|
|
35
|
+
browser worker before hydration. Handler changes are reloaded through Vite HMR. During a production
|
|
36
|
+
build the plugin removes itself from Farm's resolved configuration, so MSW is not bundled or
|
|
37
|
+
started.
|
|
38
|
+
|
|
39
|
+
## Options
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
msw({
|
|
43
|
+
handlers: "./src/mocks/handlers.ts",
|
|
44
|
+
browser: true,
|
|
45
|
+
server: true,
|
|
46
|
+
onUnhandledRequest: "bypass",
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Set `browser` or `server` to `false` when only one development runtime should be mocked. Use
|
|
51
|
+
`onUnhandledRequest: "warn"` or `"error"` when every request is expected to have a handler.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MswUnhandledRequestBehavior } from "./config.js";
|
|
2
|
+
export interface MswBrowserRuntimeOptions {
|
|
3
|
+
workerUrl: string;
|
|
4
|
+
scope: string;
|
|
5
|
+
onUnhandledRequest: MswUnhandledRequestBehavior;
|
|
6
|
+
}
|
|
7
|
+
export interface FarmMswBrowserRuntime {
|
|
8
|
+
stop(): void;
|
|
9
|
+
}
|
|
10
|
+
/** Start the browser worker used by the Farm plugin during development. */
|
|
11
|
+
export declare function startMswBrowserRuntime(options: MswBrowserRuntimeOptions): Promise<FarmMswBrowserRuntime>;
|
|
12
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,2BAA2B,CAAC;CACjD;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,IAAI,IAAI,CAAC;CACd;AAED,2EAA2E;AAC3E,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,qBAAqB,CAAC,CAkBhC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { setupWorker } from "msw/browser";
|
|
3
|
+
import { handlers } from "virtual:farm-msw-handlers";
|
|
4
|
+
/** Start the browser worker used by the Farm plugin during development. */
|
|
5
|
+
export async function startMswBrowserRuntime(options) {
|
|
6
|
+
const worker = setupWorker(...handlers);
|
|
7
|
+
await worker.start({
|
|
8
|
+
onUnhandledRequest: options.onUnhandledRequest,
|
|
9
|
+
serviceWorker: {
|
|
10
|
+
url: options.workerUrl,
|
|
11
|
+
options: { scope: options.scope },
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
let stopped = false;
|
|
15
|
+
return {
|
|
16
|
+
stop() {
|
|
17
|
+
if (stopped)
|
|
18
|
+
return;
|
|
19
|
+
stopped = true;
|
|
20
|
+
worker.stop();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type MswUnhandledRequestBehavior = "bypass" | "warn" | "error";
|
|
2
|
+
export interface MswPluginOptions {
|
|
3
|
+
/** Module exporting a `handlers` array. Resolved from the Farm app root. */
|
|
4
|
+
handlers: string;
|
|
5
|
+
/** Mock requests made by browser code. Defaults to true. */
|
|
6
|
+
browser?: boolean;
|
|
7
|
+
/** Mock requests made during development SSR and by server code. Defaults to true. */
|
|
8
|
+
server?: boolean;
|
|
9
|
+
/** How MSW handles requests that have no matching handler. Defaults to bypass. */
|
|
10
|
+
onUnhandledRequest?: MswUnhandledRequestBehavior;
|
|
11
|
+
/** Set false to leave the plugin configured without starting either runtime. */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ResolvedMswOptions {
|
|
15
|
+
handlers: string;
|
|
16
|
+
browser: boolean;
|
|
17
|
+
server: boolean;
|
|
18
|
+
onUnhandledRequest: MswUnhandledRequestBehavior;
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function resolveMswOptions(options: MswPluginOptions): ResolvedMswOptions;
|
|
22
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,2BAA2B,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sFAAsF;IACtF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,2BAA2B,CAAC;IACjD,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,kBAAkB,EAAE,2BAA2B,CAAC;IAChD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,kBAAkB,CA2B/E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function resolveMswOptions(options) {
|
|
2
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
3
|
+
throw new TypeError("msw options must be an object");
|
|
4
|
+
}
|
|
5
|
+
if (typeof options.handlers !== "string" || !options.handlers.trim()) {
|
|
6
|
+
throw new TypeError("msw handlers must be a non-empty module path");
|
|
7
|
+
}
|
|
8
|
+
assertBoolean(options.browser, "browser");
|
|
9
|
+
assertBoolean(options.server, "server");
|
|
10
|
+
assertBoolean(options.enabled, "enabled");
|
|
11
|
+
if (options.onUnhandledRequest !== undefined &&
|
|
12
|
+
options.onUnhandledRequest !== "bypass" &&
|
|
13
|
+
options.onUnhandledRequest !== "warn" &&
|
|
14
|
+
options.onUnhandledRequest !== "error") {
|
|
15
|
+
throw new TypeError('msw onUnhandledRequest must be "bypass", "warn", or "error"');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
handlers: options.handlers.trim(),
|
|
19
|
+
browser: options.browser ?? true,
|
|
20
|
+
server: options.server ?? true,
|
|
21
|
+
onUnhandledRequest: options.onUnhandledRequest ?? "bypass",
|
|
22
|
+
enabled: options.enabled ?? true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function assertBoolean(value, key) {
|
|
26
|
+
if (value !== undefined && typeof value !== "boolean") {
|
|
27
|
+
throw new TypeError(`msw ${key} must be boolean`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,KAAK,CAAC;AAClF,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC"}
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { bypass, delay, graphql, http, HttpResponse, passthrough, ws } from "msw";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type FarmPlugin } from "@farm.js/core/plugin";
|
|
2
|
+
import type { RequestHandler } from "msw";
|
|
3
|
+
import { type MswPluginOptions, type MswUnhandledRequestBehavior } from "./config.js";
|
|
4
|
+
export type { MswPluginOptions, MswUnhandledRequestBehavior };
|
|
5
|
+
interface MswPluginState {
|
|
6
|
+
server?: MswServerRuntime;
|
|
7
|
+
viteServer?: FarmViteDevServer;
|
|
8
|
+
closeListener?: () => void;
|
|
9
|
+
}
|
|
10
|
+
interface MswServerRuntime {
|
|
11
|
+
listen(options: {
|
|
12
|
+
onUnhandledRequest: MswUnhandledRequestBehavior;
|
|
13
|
+
}): void;
|
|
14
|
+
resetHandlers(...handlers: RequestHandler[]): void;
|
|
15
|
+
close(): void;
|
|
16
|
+
}
|
|
17
|
+
interface MswBrowserState {
|
|
18
|
+
stop(): void;
|
|
19
|
+
}
|
|
20
|
+
interface FarmModuleNode {
|
|
21
|
+
file?: string | null;
|
|
22
|
+
id?: string | null;
|
|
23
|
+
importedModules?: Set<FarmModuleNode>;
|
|
24
|
+
}
|
|
25
|
+
interface FarmViteDevServer {
|
|
26
|
+
ssrLoadModule(id: string): Promise<Record<string, unknown>>;
|
|
27
|
+
moduleGraph?: {
|
|
28
|
+
getModulesByFile(file: string): Set<FarmModuleNode> | undefined;
|
|
29
|
+
};
|
|
30
|
+
httpServer?: {
|
|
31
|
+
once(event: "close", listener: () => void): unknown;
|
|
32
|
+
off?(event: "close", listener: () => void): unknown;
|
|
33
|
+
} | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Use one MSW handler module for browser requests and development SSR.
|
|
37
|
+
* The plugin removes itself from production config, so neither MSW runtime is bundled or started there.
|
|
38
|
+
*/
|
|
39
|
+
export declare function msw(options: MswPluginOptions): FarmPlugin<MswPluginState, Record<string, unknown>, MswBrowserState | undefined, {
|
|
40
|
+
workerUrl: string;
|
|
41
|
+
scope: string;
|
|
42
|
+
onUnhandledRequest: MswUnhandledRequestBehavior;
|
|
43
|
+
}>;
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAA+B,MAAM,sBAAsB,CAAC;AAClG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,2BAA2B,EACjC,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,CAAC;AAM9D,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,UAAU,gBAAgB;IACxB,MAAM,CAAC,OAAO,EAAE;QAAE,kBAAkB,EAAE,2BAA2B,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,aAAa,CAAC,GAAG,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IACnD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,UAAU,eAAe;IACvB,IAAI,IAAI,IAAI,CAAC;CACd;AAED,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,eAAe,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CACvC;AAED,UAAU,iBAAiB;IACzB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5D,WAAW,CAAC,EAAE;QACZ,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;KACjE,CAAC;IACF,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;QACpD,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;KACrD,GAAG,IAAI,CAAC;CACV;AAwBD;;;GAGG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,gBAAgB;;;;GA8G5C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { definePlugin } from "@farm.js/core/plugin";
|
|
5
|
+
import { resolveMswOptions, } from "./config.js";
|
|
6
|
+
const VIRTUAL_HANDLERS_ID = "virtual:farm-msw-handlers";
|
|
7
|
+
const RESOLVED_VIRTUAL_HANDLERS_ID = `\0${VIRTUAL_HANDLERS_ID}`;
|
|
8
|
+
const DEFAULT_WORKER_FILE = "mockServiceWorker.js";
|
|
9
|
+
/**
|
|
10
|
+
* Use one MSW handler module for browser requests and development SSR.
|
|
11
|
+
* The plugin removes itself from production config, so neither MSW runtime is bundled or started there.
|
|
12
|
+
*/
|
|
13
|
+
export function msw(options) {
|
|
14
|
+
const resolved = resolveMswOptions(options);
|
|
15
|
+
let handlersFile = "";
|
|
16
|
+
const publicConfig = {
|
|
17
|
+
workerUrl: `/${DEFAULT_WORKER_FILE}`,
|
|
18
|
+
scope: "/",
|
|
19
|
+
onUnhandledRequest: resolved.onUnhandledRequest,
|
|
20
|
+
};
|
|
21
|
+
const browserClient = resolved.enabled && resolved.browser
|
|
22
|
+
? {
|
|
23
|
+
public: publicConfig,
|
|
24
|
+
async setup({ public: config, isDev }) {
|
|
25
|
+
if (!isDev)
|
|
26
|
+
return undefined;
|
|
27
|
+
const runtime = await import("@farm.js/msw/client");
|
|
28
|
+
return runtime.startMswBrowserRuntime(config);
|
|
29
|
+
},
|
|
30
|
+
close({ state }) {
|
|
31
|
+
state?.stop();
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
: undefined;
|
|
35
|
+
let plugin;
|
|
36
|
+
plugin = definePlugin({
|
|
37
|
+
name: "farm:msw",
|
|
38
|
+
enforce: "pre",
|
|
39
|
+
configure(config, context) {
|
|
40
|
+
if (!resolved.enabled || context.isProd) {
|
|
41
|
+
return withoutPlugin(config, plugin);
|
|
42
|
+
}
|
|
43
|
+
const root = path.resolve(config.root ?? ".");
|
|
44
|
+
handlersFile = path.resolve(root, resolved.handlers);
|
|
45
|
+
const basePath = normalizeBasePath(config.basePath);
|
|
46
|
+
publicConfig.workerUrl = `${basePath === "/" ? "" : basePath}/${DEFAULT_WORKER_FILE}`;
|
|
47
|
+
publicConfig.scope = basePath === "/" ? "/" : `${basePath}/`;
|
|
48
|
+
if (!resolved.browser)
|
|
49
|
+
return;
|
|
50
|
+
const vite = config.vite ?? {};
|
|
51
|
+
return {
|
|
52
|
+
...config,
|
|
53
|
+
vite: {
|
|
54
|
+
...vite,
|
|
55
|
+
plugins: [
|
|
56
|
+
createMswVitePlugin({
|
|
57
|
+
handlersFile,
|
|
58
|
+
workerUrl: publicConfig.workerUrl,
|
|
59
|
+
scope: publicConfig.scope,
|
|
60
|
+
}),
|
|
61
|
+
...(vite.plugins ?? []),
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
setup() {
|
|
67
|
+
return {};
|
|
68
|
+
},
|
|
69
|
+
dev: {
|
|
70
|
+
async server(viteServer, { state }) {
|
|
71
|
+
if (!resolved.enabled || !resolved.server)
|
|
72
|
+
return;
|
|
73
|
+
const server = viteServer;
|
|
74
|
+
const handlers = await loadHandlers(server, handlersFile);
|
|
75
|
+
const { setupServer } = await import("msw/node");
|
|
76
|
+
const mockServer = setupServer(...handlers);
|
|
77
|
+
mockServer.listen({ onUnhandledRequest: resolved.onUnhandledRequest });
|
|
78
|
+
state.server = mockServer;
|
|
79
|
+
state.viteServer = server;
|
|
80
|
+
state.closeListener = () => closeMswServer(state);
|
|
81
|
+
server.httpServer?.once("close", state.closeListener);
|
|
82
|
+
},
|
|
83
|
+
async update(update, { state }) {
|
|
84
|
+
if (!state.server || !state.viteServer)
|
|
85
|
+
return;
|
|
86
|
+
if (!isHandlersUpdate(state.viteServer, handlersFile, update.file))
|
|
87
|
+
return;
|
|
88
|
+
const handlers = await loadHandlers(state.viteServer, handlersFile, Date.now());
|
|
89
|
+
state.server.resetHandlers(...handlers);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
runtime: {
|
|
93
|
+
close({ state }) {
|
|
94
|
+
closeMswServer(state);
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
client: browserClient,
|
|
98
|
+
});
|
|
99
|
+
return plugin;
|
|
100
|
+
}
|
|
101
|
+
function withoutPlugin(config, plugin) {
|
|
102
|
+
if (!config.plugins?.includes(plugin))
|
|
103
|
+
return;
|
|
104
|
+
return {
|
|
105
|
+
...config,
|
|
106
|
+
plugins: config.plugins.filter((candidate) => candidate !== plugin),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function createMswVitePlugin(input) {
|
|
110
|
+
const require = createRequire(import.meta.url);
|
|
111
|
+
const workerFile = require.resolve("msw/mockServiceWorker.js");
|
|
112
|
+
const workerSource = readFileSync(workerFile, "utf8");
|
|
113
|
+
return {
|
|
114
|
+
name: "farm:msw-vite",
|
|
115
|
+
enforce: "pre",
|
|
116
|
+
resolveId(id) {
|
|
117
|
+
return id === VIRTUAL_HANDLERS_ID ? RESOLVED_VIRTUAL_HANDLERS_ID : undefined;
|
|
118
|
+
},
|
|
119
|
+
load(id) {
|
|
120
|
+
if (id !== RESOLVED_VIRTUAL_HANDLERS_ID)
|
|
121
|
+
return undefined;
|
|
122
|
+
return renderVirtualHandlersModule(input.handlersFile);
|
|
123
|
+
},
|
|
124
|
+
configureServer(server) {
|
|
125
|
+
server.middlewares.use((request, response, next) => {
|
|
126
|
+
if (request.method !== "GET" && request.method !== "HEAD")
|
|
127
|
+
return next();
|
|
128
|
+
if (requestPathname(request.url) !== input.workerUrl)
|
|
129
|
+
return next();
|
|
130
|
+
response.statusCode = 200;
|
|
131
|
+
response.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
132
|
+
response.setHeader("Cache-Control", "no-store");
|
|
133
|
+
response.setHeader("Service-Worker-Allowed", input.scope);
|
|
134
|
+
response.end(request.method === "HEAD" ? undefined : workerSource);
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function renderVirtualHandlersModule(handlersFile) {
|
|
140
|
+
return `import * as handlerModule from ${JSON.stringify(toModuleId(handlersFile))};
|
|
141
|
+
const candidate = handlerModule.handlers ?? handlerModule.default;
|
|
142
|
+
if (!Array.isArray(candidate)) {
|
|
143
|
+
throw new TypeError(${JSON.stringify(invalidHandlersMessage(handlersFile))});
|
|
144
|
+
}
|
|
145
|
+
export const handlers = candidate;`;
|
|
146
|
+
}
|
|
147
|
+
async function loadHandlers(server, handlersFile, cacheBust) {
|
|
148
|
+
const id = `${toModuleId(handlersFile)}${cacheBust ? `?farm-msw=${cacheBust}` : ""}`;
|
|
149
|
+
const loaded = await server.ssrLoadModule(id);
|
|
150
|
+
const candidate = loaded.handlers ?? loaded.default;
|
|
151
|
+
if (!Array.isArray(candidate))
|
|
152
|
+
throw new TypeError(invalidHandlersMessage(handlersFile));
|
|
153
|
+
return candidate;
|
|
154
|
+
}
|
|
155
|
+
function closeMswServer(state) {
|
|
156
|
+
if (!state.server)
|
|
157
|
+
return;
|
|
158
|
+
state.server.close();
|
|
159
|
+
state.server = undefined;
|
|
160
|
+
if (state.closeListener) {
|
|
161
|
+
state.viteServer?.httpServer?.off?.("close", state.closeListener);
|
|
162
|
+
state.closeListener = undefined;
|
|
163
|
+
}
|
|
164
|
+
state.viteServer = undefined;
|
|
165
|
+
}
|
|
166
|
+
function isHandlersUpdate(server, handlersFile, changedFile) {
|
|
167
|
+
const normalizedChangedFile = normalizeFile(changedFile);
|
|
168
|
+
if (normalizedChangedFile === normalizeFile(handlersFile))
|
|
169
|
+
return true;
|
|
170
|
+
const handlerModules = server.moduleGraph?.getModulesByFile(handlersFile);
|
|
171
|
+
if (!handlerModules)
|
|
172
|
+
return false;
|
|
173
|
+
return [...handlerModules].some((module) => moduleDependsOnFile(module, normalizedChangedFile));
|
|
174
|
+
}
|
|
175
|
+
function moduleDependsOnFile(module, changedFile, seen = new Set()) {
|
|
176
|
+
if (seen.has(module))
|
|
177
|
+
return false;
|
|
178
|
+
seen.add(module);
|
|
179
|
+
if (moduleFile(module) === changedFile)
|
|
180
|
+
return true;
|
|
181
|
+
return [...(module.importedModules ?? [])].some((dependency) => moduleDependsOnFile(dependency, changedFile, seen));
|
|
182
|
+
}
|
|
183
|
+
function moduleFile(module) {
|
|
184
|
+
return normalizeFile(module.file ?? module.id?.split("?", 1)[0] ?? "");
|
|
185
|
+
}
|
|
186
|
+
function invalidHandlersMessage(handlersFile) {
|
|
187
|
+
return `[farm:msw] ${handlersFile} must export a handlers array (named or default export).`;
|
|
188
|
+
}
|
|
189
|
+
function normalizeBasePath(value) {
|
|
190
|
+
if (typeof value !== "string" || !value.trim() || value === "/")
|
|
191
|
+
return "/";
|
|
192
|
+
return `/${value.replace(/^\/+|\/+$/g, "")}`;
|
|
193
|
+
}
|
|
194
|
+
function requestPathname(url) {
|
|
195
|
+
if (!url)
|
|
196
|
+
return "";
|
|
197
|
+
try {
|
|
198
|
+
return new URL(url, "http://farm.local").pathname;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function normalizeFile(file) {
|
|
205
|
+
return path.normalize(file).replace(/\\/g, "/");
|
|
206
|
+
}
|
|
207
|
+
function toModuleId(file) {
|
|
208
|
+
return file.replace(/\\/g, "/");
|
|
209
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@farm.js/msw",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Shared browser and server API mocking for Farm.js development",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"api-mocking",
|
|
7
|
+
"farm.js",
|
|
8
|
+
"mock-service-worker",
|
|
9
|
+
"msw",
|
|
10
|
+
"testing"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/farming-labs/farm.js",
|
|
16
|
+
"directory": "packages/farm-msw"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./client": {
|
|
31
|
+
"types": "./dist/client.d.ts",
|
|
32
|
+
"import": "./dist/client.js"
|
|
33
|
+
},
|
|
34
|
+
"./handlers": {
|
|
35
|
+
"types": "./dist/handlers.d.ts",
|
|
36
|
+
"import": "./dist/handlers.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"msw": "^2.15.0",
|
|
44
|
+
"@farm.js/core": "0.1.0-beta.87"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.10.5",
|
|
48
|
+
"typescript": "^5.3.3",
|
|
49
|
+
"vitest": "^3.2.7"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=22.13.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
|
|
56
|
+
"dev": "tsc --watch",
|
|
57
|
+
"type-check": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run"
|
|
59
|
+
}
|
|
60
|
+
}
|