vite-plugin-mock-dev-server 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021-2022 pengzhanbo
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.
package/README.md ADDED
@@ -0,0 +1,359 @@
1
+ # vite-plugin-mock-dev-server
2
+
3
+ vite mock开发服务(mock-dev-server)插件。
4
+
5
+ 在 vite 开发环境中,注入一个 mock-dev-server。
6
+
7
+ ## 特性
8
+
9
+ - ⚡️ 轻量,灵活,快速
10
+ - 🧲 非注入式,对客户端代码无侵入
11
+ - 🦾 Typescript
12
+ - 📦 自动加载 mock 文件
13
+ - 🎨 可选择你喜欢的任意用于生成mock数据库,如 `mockjs`,或者不是用其他库
14
+ - 📥 路径规则匹配,请求参数匹配
15
+ - ⚙️ 随意开启或关闭对某个接口的 mock配置
16
+ - 🔥 热更新
17
+ - ⚖️ 使用 `server.proxy` 配置
18
+
19
+
20
+ ## 使用
21
+
22
+ ### 安装
23
+
24
+ ```sh
25
+ # npm
26
+ npm i -D vite-plugin-mock-dev-server
27
+ # yarn
28
+ yarn add vite-plugin-mock-dev-server
29
+ # pnpm
30
+ pnpm add -D vite-plugin-mock-dev-server
31
+ ```
32
+
33
+ ### 配置
34
+
35
+ `vite.config.ts`
36
+ ```ts
37
+ import { defineConfig } from 'vite'
38
+ import mockDevServerPlugin from 'vite-plugin-mock-dev-server'
39
+
40
+ export default defineConfig({
41
+ plugins: [
42
+ mockDevServerPlugin(),
43
+ ],
44
+ server: {
45
+ proxy: {
46
+ '^/api': {
47
+ target: 'http://example.com'
48
+ }
49
+ }
50
+ }
51
+ })
52
+ ```
53
+ 插件会读取 `server.proxy` 的配置, 仅对设置了代理的 url 匹配,启用
54
+ mock 匹配。
55
+
56
+ > 因为一般场景下,我们只需要对有代理的url进行mock,这样才能通过 vite 提供的 http 服务进行 代理和 mock
57
+
58
+ ### 编写mock文件
59
+
60
+ 默认配置,在你的项目根目录的 `mock` 目录中编写mock数据:
61
+
62
+ `mock/api.mock.ts` :
63
+ ```ts
64
+ import { defineMock } from 'vite-plugin-mock-dev-server'
65
+
66
+ export default defineMock({
67
+ url: '/api/test',
68
+ body: {
69
+ a: 1,
70
+ b: 2,
71
+ }
72
+ })
73
+ ```
74
+
75
+ ## 方法
76
+
77
+ ### mockDevServerPlugin(options)
78
+
79
+ vite plugin
80
+
81
+
82
+ `vite.config.ts`
83
+ ```ts
84
+ import { defineConfig } from 'vite'
85
+ import mockDevServerPlugin from 'vite-plugin-mock-dev-server'
86
+
87
+ export default defineConfig({
88
+ plugins: [
89
+ mockDevServerPlugin(),
90
+ ]
91
+ })
92
+ ```
93
+
94
+ #### options
95
+
96
+ - `option.include`
97
+
98
+ 配置读取 mock文件,可以是一个 目录,glob,或者一个数组
99
+
100
+ 默认值: `['mock/**/*.mock.*']` (相对于根目录)
101
+
102
+
103
+ ### defineMock(config)
104
+
105
+ mock 配置帮助函数,提供类型检查帮助
106
+
107
+ ```ts
108
+ import { defineMock } from 'vite-plugin-mock-dev-server'
109
+
110
+ export default defineMock({
111
+ url: '/api/test',
112
+ body: {}
113
+ })
114
+ ```
115
+
116
+ ## Mock 配置
117
+
118
+ ```ts
119
+ export default defineMock({
120
+ /**
121
+ * 请求地址,支持 `/api/user/:id` 格式
122
+ */
123
+ url: '/api/test',
124
+ /**
125
+ * 接口支持的请求方法
126
+ *
127
+ * @type string | string[]
128
+ * @default ['POST','GET']
129
+ *
130
+ */
131
+ method: ['GET', 'POST'],
132
+ /**
133
+ * 是否启用当前 mock请求
134
+ *
135
+ * 在实际场景中,我们一般只需要某几个mock接口生效,
136
+ * 而不是所以mock接口都启用。
137
+ * 对当前不需要mock的接口,可设置为 false
138
+ *
139
+ * @default true
140
+ */
141
+ enable: true,
142
+ /**
143
+ * 设置接口响应延迟, 单位:ms
144
+ *
145
+ * @default 0
146
+ */
147
+ delay: 1000,
148
+ /**
149
+ * 响应状态码
150
+ *
151
+ * @default 200
152
+ */
153
+ status: 200,
154
+ /**
155
+ * 响应状态文本
156
+ */
157
+ statusText: 'OK',
158
+ /**
159
+ * 请求验证器,通过验证器则返回 mock数据,否则不是用当前mock。
160
+ * 这对于一些场景中,某个接口需要通过不同的入参来返回不同的数据,
161
+ * 验证器可以很好的解决这一类问题,将同个 url 分为多个 mock配置,
162
+ * 根据 验证器来判断哪个mock配置生效。
163
+ *
164
+ * @type { header?: object; body?: object; query?: object; params?: object }
165
+ *
166
+ * 如果 validator 传入的是一个对象,那么验证方式是严格比较 请求的接口
167
+ * 中,headers/body/query/params 的各个`key`的`value`是否全等,
168
+ * 全等则校验通过
169
+ *
170
+ * @type ({ header: object; body: object; query: object; params: object }) => boolean
171
+ * 如果 validator 传入的是一个函数,那么会讲 请求的接口相关数据作为入参,提供给使用者进行自定义校验,并返回一个 boolean
172
+ *
173
+ */
174
+ validator: {
175
+ headers: {},
176
+ body: {},
177
+ query: {},
178
+ params: {},
179
+ },
180
+ /**
181
+ *
182
+ * 响应状态 headers
183
+ *
184
+ * @type Record<string, any>
185
+ *
186
+ * @type (({ query, body, params, headers }) => Record<string, any>)
187
+ * 入参部分为 请求相关信息
188
+ */
189
+ headers: {
190
+ 'Content-Type': 'application/json'
191
+ },
192
+
193
+ /**
194
+ * 响应体数据
195
+ * 定义返回的响应体数据内容。
196
+ * 在这里,你可以直接返回JavaScript支持的数据类型如 `string/number/array/object` 等
197
+ * 同时,你也可以使用如 `mockjs` 等库来生成数据内容
198
+ *
199
+ * @type string | number | array | object
200
+ * 直接返回定义的数据
201
+ *
202
+ * @type (request: { header, query, body, params }) => any | Promise<any>
203
+ * 如果传入一个函数,那么可以更加灵活的定义返回响应体数据
204
+ */
205
+ body: {},
206
+
207
+ /**
208
+ * 如果通过 body 配置不能解决mock需求,
209
+ * 那么可以通过 配置 response,暴露http server 的接口,
210
+ * 实现完全可控的自定义配置
211
+ *
212
+ * 在 req参数中,已内置了 query、body、params 的解析,
213
+ * 你可以直接使用它们
214
+ *
215
+ * 别忘了,需要通过 `res.end()` 返回响应体数据,
216
+ * 或者需要跳过mock,那么别忘了调用 `next()`
217
+ */
218
+ response(req, res, next) {
219
+ res.end()
220
+ }
221
+ })
222
+
223
+ ```
224
+
225
+ `mock/**/*.mock.ts`
226
+
227
+ #### 示例1:
228
+ 命中 `/api/test` 请求,并返回一个 数据为空的响应体内容
229
+ ```ts
230
+ export default defineMock({
231
+ url: '/api/test',
232
+ })
233
+ ```
234
+
235
+ #### 示例2:
236
+ 命中 `/api/test` 请求,并返回一个固定内容数据
237
+ ```ts
238
+ export default defineMock({
239
+ url: '/api/test',
240
+ body: {
241
+ a: 1
242
+ }
243
+ })
244
+ ```
245
+
246
+ #### 示例3:
247
+ 限定只允许 `GET` 请求
248
+ ```ts
249
+ export default defineMock({
250
+ url: '/api/test',
251
+ method: 'GET'
252
+ })
253
+ ```
254
+
255
+ #### 示例4:
256
+ 在返回的响应头中,添加自定义header
257
+ ```ts
258
+ export default defineMock({
259
+ url: '/api/test',
260
+ headers: {
261
+ 'X-Custom': '12345678'
262
+ }
263
+ })
264
+ ```
265
+ ```ts
266
+ export default defineMock({
267
+ url: '/api/test',
268
+ headers({ query, body, params, headers }) {
269
+ return {
270
+ 'X-Custom': query.custom
271
+ }
272
+ }
273
+ })
274
+ ```
275
+
276
+ #### 示例5:
277
+ 定义多个相同url请求mock,并使用验证器匹配生效规则
278
+ ```ts
279
+ export default defineMock([
280
+ // 命中 /api/test?a=1
281
+ {
282
+ url: '/api/test',
283
+ validator: {
284
+ query: {
285
+ a: 1
286
+ }
287
+ },
288
+ body: {
289
+ message: 'query.a === 1'
290
+ }
291
+ },
292
+ // 命中 /api/test?a=2
293
+ {
294
+ url: '/api/test',
295
+ validator: {
296
+ query: {
297
+ a: 2
298
+ }
299
+ },
300
+ body: {
301
+ message: 'query.a === 2'
302
+ }
303
+ }
304
+ ])
305
+ ```
306
+
307
+ #### 示例6:
308
+ 延迟接口响应:
309
+ ```ts
310
+ export default defineMock({
311
+ url: '/api/test',
312
+ delay: 6000, // 延迟 6秒
313
+ })
314
+ ```
315
+
316
+ #### 示例7:
317
+ 使接口请求失败
318
+ ```ts
319
+ export default defineMock({
320
+ url: '/api/test',
321
+ status: 504,
322
+ statusText: 'Bad Gateway'
323
+ })
324
+ ```
325
+
326
+ #### 示例8:
327
+ 使用 `mockjs` 生成响应数据:
328
+ ```ts
329
+ import Mock from 'mockjs'
330
+ export default defineMock({
331
+ url: '/api/test',
332
+ body: Mock.mock({
333
+ 'list|1-10': [{
334
+ 'id|+1': 1
335
+ }]
336
+ })
337
+ })
338
+ ```
339
+ 请先安装 `mockjs`
340
+
341
+ ### 示例9:
342
+ 使用 `response` 自定义响应
343
+ ```ts
344
+ export default defineMock({
345
+ url: '/api/test',
346
+ response(req, res, next) {
347
+ const { query, body, params, headers } = req
348
+ console.log(query, body, params, headers)
349
+
350
+ res.status = 200
351
+ res.setHeader('Content-Type', 'application/json')
352
+ res.end(JSON.stringify({
353
+ query,
354
+ body,
355
+ params,
356
+ }))
357
+ }
358
+ })
359
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ // src/index.ts
27
+ var src_exports = {};
28
+ __export(src_exports, {
29
+ default: () => src_default,
30
+ defineMock: () => defineMock,
31
+ mockDevServerPlugin: () => mockDevServerPlugin
32
+ });
33
+ module.exports = __toCommonJS(src_exports);
34
+
35
+ // src/mockMiddleware.ts
36
+ var import_promises = __toESM(require("fs/promises"), 1);
37
+ var import_node_path = __toESM(require("path"), 1);
38
+ var import_node_url = require("url");
39
+ var import_chokidar = __toESM(require("chokidar"), 1);
40
+ var import_co_body = __toESM(require("co-body"), 1);
41
+ var import_debug = __toESM(require("debug"), 1);
42
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
43
+ var import_path_to_regexp = require("path-to-regexp");
44
+ var import_vite = require("vite");
45
+
46
+ // src/utils.ts
47
+ var isArray = (val) => Array.isArray(val);
48
+ var isFunction = (val) => typeof val === "function";
49
+ function sleep(timeout) {
50
+ return new Promise((resolve) => setTimeout(resolve, timeout));
51
+ }
52
+
53
+ // src/mockMiddleware.ts
54
+ var MOCK_TEMP = "node_modules/.cache/.mock_server";
55
+ var debug = (0, import_debug.default)("vite:plugin-mock-dev-server");
56
+ async function mockServerMiddleware(httpServer, config, options) {
57
+ const include = isArray(options.include) ? options.include : [options.include];
58
+ const includePaths = await (0, import_fast_glob.default)(include, { cwd: process.cwd() });
59
+ const modules = /* @__PURE__ */ Object.create(null);
60
+ let mockList;
61
+ for (const filepath of includePaths) {
62
+ modules[filepath] = await loadModule(filepath);
63
+ }
64
+ setupMockList();
65
+ debug("start watcher: ", include);
66
+ debug("watcher api length: ", mockList.length);
67
+ const watcher = import_chokidar.default.watch(include.splice(0)[0], {
68
+ ignoreInitial: true,
69
+ cwd: process.cwd()
70
+ });
71
+ include.length > 0 && include.forEach((item) => watcher.add(item));
72
+ watcher.on("add", async (filepath) => {
73
+ debug("watcher add: ", filepath);
74
+ modules[filepath] = await loadModule(filepath);
75
+ setupMockList();
76
+ });
77
+ watcher.on("change", async (filepath) => {
78
+ debug("watcher change", filepath);
79
+ modules[filepath] = await loadModule(filepath);
80
+ setupMockList();
81
+ });
82
+ watcher.on("unlink", (filepath) => {
83
+ debug("watcher unlink", filepath);
84
+ delete modules[filepath];
85
+ setupMockList();
86
+ });
87
+ function setupMockList() {
88
+ mockList = [];
89
+ Object.keys(modules).forEach((key) => {
90
+ const handle = modules[key];
91
+ isArray(handle) ? mockList.push(...handle) : mockList.push(handle);
92
+ });
93
+ mockList = mockList.filter(
94
+ (mock) => mock.enabled || typeof mock.enabled === "undefined"
95
+ );
96
+ }
97
+ httpServer == null ? void 0 : httpServer.on("close", () => watcher.close());
98
+ const proxies = Object.keys(config.server.proxy || {});
99
+ return async function(req, res, next) {
100
+ if (proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) {
101
+ return next();
102
+ }
103
+ const method = req.method.toUpperCase();
104
+ const { query, pathname } = (0, import_node_url.parse)(req.url, true);
105
+ const reqBody = await parseReqBody(req);
106
+ const currentMock = mockList.find((mock) => {
107
+ if (!pathname || !mock || !mock.url)
108
+ return false;
109
+ const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
110
+ if (!methods.includes(req.method.toUpperCase()))
111
+ return false;
112
+ const hasMock = (0, import_path_to_regexp.pathToRegexp)(mock.url).test(pathname);
113
+ if (hasMock && mock.validator) {
114
+ const urlMatch2 = (0, import_path_to_regexp.match)(mock.url, { decode: decodeURIComponent })(
115
+ pathname
116
+ ) || { params: {} };
117
+ const params2 = urlMatch2.params || {};
118
+ const request = {
119
+ query,
120
+ params: params2,
121
+ body: reqBody,
122
+ headers: req.headers
123
+ };
124
+ if (isFunction(mock.validator)) {
125
+ return mock.validator(request);
126
+ } else {
127
+ return validate(request, mock.validator);
128
+ }
129
+ }
130
+ return hasMock;
131
+ });
132
+ if (!currentMock)
133
+ return next();
134
+ debug("middleware: ", method, pathname);
135
+ if (currentMock.delay && currentMock.delay > 0) {
136
+ await sleep(currentMock.delay);
137
+ }
138
+ res.statusCode = currentMock.status || 200;
139
+ res.statusMessage = currentMock.statusText || "OK";
140
+ const urlMatch = (0, import_path_to_regexp.match)(currentMock.url, { decode: decodeURIComponent })(
141
+ pathname
142
+ ) || { params: {} };
143
+ const params = urlMatch.params || {};
144
+ req.body = reqBody;
145
+ req.query = query;
146
+ req.params = params;
147
+ res.setHeader("Content-Type", "application/json");
148
+ if (currentMock.headers) {
149
+ const headers = isFunction(currentMock.headers) ? await currentMock.headers({
150
+ query,
151
+ body: reqBody,
152
+ params,
153
+ headers: req.headers
154
+ }) : currentMock.headers;
155
+ Object.keys(headers).forEach((key) => {
156
+ res.setHeader(key, headers[key]);
157
+ });
158
+ }
159
+ if (currentMock.body) {
160
+ let body;
161
+ if (isFunction(currentMock.body)) {
162
+ body = await currentMock.body({
163
+ query,
164
+ body: reqBody,
165
+ params,
166
+ headers: req.headers
167
+ });
168
+ } else {
169
+ body = currentMock.body;
170
+ }
171
+ res.end(JSON.stringify(body));
172
+ return;
173
+ }
174
+ if (currentMock.response) {
175
+ await currentMock.response(
176
+ req,
177
+ res,
178
+ next
179
+ );
180
+ return;
181
+ }
182
+ res.end("");
183
+ };
184
+ }
185
+ function doesProxyContextMatchUrl(context, url) {
186
+ return context.startsWith("^") && new RegExp(context).test(url) || url.startsWith(context);
187
+ }
188
+ async function parseReqBody(req) {
189
+ const method = req.method.toUpperCase();
190
+ if (["GET", "DELETE", "HEAD"].includes(method))
191
+ return void 0;
192
+ const type = req.headers["content-type"];
193
+ if (type === "application/json") {
194
+ return await import_co_body.default.json(req);
195
+ }
196
+ if (type === "application/x-www-form-urlencoded") {
197
+ return await import_co_body.default.form(req);
198
+ }
199
+ if (type === "text/plain") {
200
+ return await import_co_body.default.text(req);
201
+ }
202
+ return await (0, import_co_body.default)(req);
203
+ }
204
+ async function loadModule(filepath) {
205
+ const ext = import_node_path.default.extname(filepath);
206
+ if (ext === ".ts") {
207
+ const tsText = await import_promises.default.readFile(filepath, "utf-8");
208
+ const { code } = await (0, import_vite.transformWithEsbuild)(tsText, filepath, {
209
+ target: "es2020",
210
+ platform: "node",
211
+ format: "esm"
212
+ });
213
+ const tempFile = import_node_path.default.join(
214
+ process.cwd(),
215
+ MOCK_TEMP,
216
+ filepath.replace(/\.ts$/, ".mjs")
217
+ );
218
+ const tempBasename = import_node_path.default.dirname(tempFile);
219
+ await import_promises.default.mkdir(tempBasename, { recursive: true });
220
+ await import_promises.default.writeFile(tempFile, code, "utf8");
221
+ return await loadESModule(tempFile);
222
+ }
223
+ return await loadESModule(filepath);
224
+ }
225
+ async function loadESModule(filepath) {
226
+ const handle = await import(`${filepath}?${Date.now()}`);
227
+ if (handle && handle.default)
228
+ return handle.default;
229
+ return Object.keys(handle || {}).map((key) => handle[key]);
230
+ }
231
+ function validate(request, validator) {
232
+ return equalObj(request.headers, validator.headers) && equalObj(request.body, validator.body) && equalObj(request.params, validator.params) && equalObj(request.query, validator.query);
233
+ }
234
+ function equalObj(left, right) {
235
+ if (!right)
236
+ return true;
237
+ for (const key in right) {
238
+ if (right[key] !== left[key])
239
+ return false;
240
+ }
241
+ return true;
242
+ }
243
+
244
+ // src/plugin.ts
245
+ function mockDevServerPlugin(options = { include: ["mock/**/*.mock.*"] }) {
246
+ return {
247
+ name: "vite-plugin-mock-dev-server",
248
+ async configureServer({ middlewares, config, httpServer }) {
249
+ const middleware = await mockServerMiddleware(httpServer, config, options);
250
+ middlewares.use(middleware);
251
+ }
252
+ };
253
+ }
254
+
255
+ // src/define.ts
256
+ var defineMock = (mock) => {
257
+ return mock;
258
+ };
259
+
260
+ // src/index.ts
261
+ var src_default = mockDevServerPlugin;
262
+ // Annotate the CommonJS export names for ESM import in node:
263
+ 0 && (module.exports = {
264
+ defineMock,
265
+ mockDevServerPlugin
266
+ });
@@ -0,0 +1,110 @@
1
+ import { Connect, Plugin } from 'vite';
2
+ import http from 'node:http';
3
+
4
+ interface MockServerPluginOptions {
5
+ /**
6
+ * glob字符串匹配 mock数据文件
7
+ * @default []
8
+ */
9
+ include: string | string[];
10
+ }
11
+ declare type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'TRACE' | 'OPTIONS';
12
+ declare type ResponseBody = Record<string, any> | any[] | string | number | null;
13
+ interface ResponseReq {
14
+ /**
15
+ * 请求地址中位于 `?` 后面的 queryString,已解析为 json
16
+ */
17
+ query: Record<string, any>;
18
+ /**
19
+ * 请求体中 body 数据
20
+ */
21
+ body: Record<string, any>;
22
+ /**
23
+ * 请求地址中,/api/id/:id 解析后的 params 参数
24
+ */
25
+ params: Record<string, any>;
26
+ /**
27
+ * 请求 中的 headers
28
+ */
29
+ headers: Record<string, any>;
30
+ }
31
+ interface ResponseBodyFn {
32
+ (request: ResponseReq): ResponseBody | Promise<ResponseBody>;
33
+ }
34
+ interface ResponseHeaderFn {
35
+ (request: ResponseReq): Headers | Promise<Headers>;
36
+ }
37
+ declare type Headers = Record<string, any>;
38
+ interface MockOptionsItem {
39
+ /**
40
+ * 需要做mock的接口地址,
41
+ * exp: `/api/login`, `/api/login/:id`
42
+ */
43
+ url: string;
44
+ /**
45
+ * 该接口支持的 请求方法,默认同时支持 GET 和 POST
46
+ *
47
+ * @default ['POST','GET']
48
+ */
49
+ method?: Method | Method[];
50
+ /**
51
+ * 是否启动对该接口的mock,在多数场景下,我们进需要对部分接口进行 mock,
52
+ * 而不是对所有配置了mock的请求进行全量mock,所以是否能够配置是否启用很重要
53
+ *
54
+ * @default true
55
+ */
56
+ enabled?: boolean;
57
+ /**
58
+ * 配置响应体 header
59
+ *
60
+ * @default {'Content-Type':'application/json'}
61
+ */
62
+ headers?: Headers | ResponseHeaderFn;
63
+ /**
64
+ * 配置 响应头状态码
65
+ *
66
+ * @default 200
67
+ */
68
+ status?: number;
69
+ /**
70
+ * 配置响应头状态文本
71
+ *
72
+ * @default 'OK'
73
+ */
74
+ statusText?: string;
75
+ /**
76
+ * 配置响应延迟时间, 单位: `ms`
77
+ *
78
+ * @default 0
79
+ */
80
+ delay?: number;
81
+ /**
82
+ * 配置响应体数据内容
83
+ *
84
+ * @default {}
85
+ */
86
+ body?: ResponseBody | ResponseBodyFn;
87
+ /**
88
+ * 如果需要设置复杂的响应内容,可以使用 response 方法,
89
+ * 该方法是一个 middleware,你可以在这里拿到 http 请求的 req、res等信息,
90
+ * 然后通过 res.write() | res.end() 返回响应数据, 否则执行 next() 方法。
91
+ *
92
+ * 在 req 中,还可以拿到 query、params、body等已解析的请求信息
93
+ */
94
+ response?: (req: Connect.IncomingMessage & ResponseReq, res: http.ServerResponse<http.IncomingMessage>, next: Connect.NextFunction) => void | Promise<void>;
95
+ /**
96
+ * 请求验证器
97
+ *
98
+ * 有时候,一个相同的API请求,需要根据不同的请求参数,来决定返回数据,
99
+ * 但全部都在单个 mock中的 body或者 response 中写,内容会很庞杂,不好管理,
100
+ * 验证器的功能,允许你同时配置多条相同url的mock,通过验证器来判断使哪个mock生效。
101
+ */
102
+ validator?: Partial<ResponseReq> | ((request: ResponseReq) => boolean);
103
+ }
104
+ declare type MockOptions = MockOptionsItem[];
105
+
106
+ declare function mockDevServerPlugin(options?: MockServerPluginOptions): Plugin;
107
+
108
+ declare const defineMock: (mock: MockOptionsItem | MockOptions) => MockOptionsItem | MockOptions;
109
+
110
+ export { MockOptions, MockOptionsItem, MockServerPluginOptions, mockDevServerPlugin as default, defineMock, mockDevServerPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,232 @@
1
+ // src/mockMiddleware.ts
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import { parse as urlParse } from "url";
5
+ import chokidar from "chokidar";
6
+ import bodyParser from "co-body";
7
+ import Debug from "debug";
8
+ import fastGlob from "fast-glob";
9
+ import { match, pathToRegexp } from "path-to-regexp";
10
+ import { transformWithEsbuild } from "vite";
11
+
12
+ // src/utils.ts
13
+ var isArray = (val) => Array.isArray(val);
14
+ var isFunction = (val) => typeof val === "function";
15
+ function sleep(timeout) {
16
+ return new Promise((resolve) => setTimeout(resolve, timeout));
17
+ }
18
+
19
+ // src/mockMiddleware.ts
20
+ var MOCK_TEMP = "node_modules/.cache/.mock_server";
21
+ var debug = Debug("vite:plugin-mock-dev-server");
22
+ async function mockServerMiddleware(httpServer, config, options) {
23
+ const include = isArray(options.include) ? options.include : [options.include];
24
+ const includePaths = await fastGlob(include, { cwd: process.cwd() });
25
+ const modules = /* @__PURE__ */ Object.create(null);
26
+ let mockList;
27
+ for (const filepath of includePaths) {
28
+ modules[filepath] = await loadModule(filepath);
29
+ }
30
+ setupMockList();
31
+ debug("start watcher: ", include);
32
+ debug("watcher api length: ", mockList.length);
33
+ const watcher = chokidar.watch(include.splice(0)[0], {
34
+ ignoreInitial: true,
35
+ cwd: process.cwd()
36
+ });
37
+ include.length > 0 && include.forEach((item) => watcher.add(item));
38
+ watcher.on("add", async (filepath) => {
39
+ debug("watcher add: ", filepath);
40
+ modules[filepath] = await loadModule(filepath);
41
+ setupMockList();
42
+ });
43
+ watcher.on("change", async (filepath) => {
44
+ debug("watcher change", filepath);
45
+ modules[filepath] = await loadModule(filepath);
46
+ setupMockList();
47
+ });
48
+ watcher.on("unlink", (filepath) => {
49
+ debug("watcher unlink", filepath);
50
+ delete modules[filepath];
51
+ setupMockList();
52
+ });
53
+ function setupMockList() {
54
+ mockList = [];
55
+ Object.keys(modules).forEach((key) => {
56
+ const handle = modules[key];
57
+ isArray(handle) ? mockList.push(...handle) : mockList.push(handle);
58
+ });
59
+ mockList = mockList.filter(
60
+ (mock) => mock.enabled || typeof mock.enabled === "undefined"
61
+ );
62
+ }
63
+ httpServer == null ? void 0 : httpServer.on("close", () => watcher.close());
64
+ const proxies = Object.keys(config.server.proxy || {});
65
+ return async function(req, res, next) {
66
+ if (proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) {
67
+ return next();
68
+ }
69
+ const method = req.method.toUpperCase();
70
+ const { query, pathname } = urlParse(req.url, true);
71
+ const reqBody = await parseReqBody(req);
72
+ const currentMock = mockList.find((mock) => {
73
+ if (!pathname || !mock || !mock.url)
74
+ return false;
75
+ const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
76
+ if (!methods.includes(req.method.toUpperCase()))
77
+ return false;
78
+ const hasMock = pathToRegexp(mock.url).test(pathname);
79
+ if (hasMock && mock.validator) {
80
+ const urlMatch2 = match(mock.url, { decode: decodeURIComponent })(
81
+ pathname
82
+ ) || { params: {} };
83
+ const params2 = urlMatch2.params || {};
84
+ const request = {
85
+ query,
86
+ params: params2,
87
+ body: reqBody,
88
+ headers: req.headers
89
+ };
90
+ if (isFunction(mock.validator)) {
91
+ return mock.validator(request);
92
+ } else {
93
+ return validate(request, mock.validator);
94
+ }
95
+ }
96
+ return hasMock;
97
+ });
98
+ if (!currentMock)
99
+ return next();
100
+ debug("middleware: ", method, pathname);
101
+ if (currentMock.delay && currentMock.delay > 0) {
102
+ await sleep(currentMock.delay);
103
+ }
104
+ res.statusCode = currentMock.status || 200;
105
+ res.statusMessage = currentMock.statusText || "OK";
106
+ const urlMatch = match(currentMock.url, { decode: decodeURIComponent })(
107
+ pathname
108
+ ) || { params: {} };
109
+ const params = urlMatch.params || {};
110
+ req.body = reqBody;
111
+ req.query = query;
112
+ req.params = params;
113
+ res.setHeader("Content-Type", "application/json");
114
+ if (currentMock.headers) {
115
+ const headers = isFunction(currentMock.headers) ? await currentMock.headers({
116
+ query,
117
+ body: reqBody,
118
+ params,
119
+ headers: req.headers
120
+ }) : currentMock.headers;
121
+ Object.keys(headers).forEach((key) => {
122
+ res.setHeader(key, headers[key]);
123
+ });
124
+ }
125
+ if (currentMock.body) {
126
+ let body;
127
+ if (isFunction(currentMock.body)) {
128
+ body = await currentMock.body({
129
+ query,
130
+ body: reqBody,
131
+ params,
132
+ headers: req.headers
133
+ });
134
+ } else {
135
+ body = currentMock.body;
136
+ }
137
+ res.end(JSON.stringify(body));
138
+ return;
139
+ }
140
+ if (currentMock.response) {
141
+ await currentMock.response(
142
+ req,
143
+ res,
144
+ next
145
+ );
146
+ return;
147
+ }
148
+ res.end("");
149
+ };
150
+ }
151
+ function doesProxyContextMatchUrl(context, url) {
152
+ return context.startsWith("^") && new RegExp(context).test(url) || url.startsWith(context);
153
+ }
154
+ async function parseReqBody(req) {
155
+ const method = req.method.toUpperCase();
156
+ if (["GET", "DELETE", "HEAD"].includes(method))
157
+ return void 0;
158
+ const type = req.headers["content-type"];
159
+ if (type === "application/json") {
160
+ return await bodyParser.json(req);
161
+ }
162
+ if (type === "application/x-www-form-urlencoded") {
163
+ return await bodyParser.form(req);
164
+ }
165
+ if (type === "text/plain") {
166
+ return await bodyParser.text(req);
167
+ }
168
+ return await bodyParser(req);
169
+ }
170
+ async function loadModule(filepath) {
171
+ const ext = path.extname(filepath);
172
+ if (ext === ".ts") {
173
+ const tsText = await fs.readFile(filepath, "utf-8");
174
+ const { code } = await transformWithEsbuild(tsText, filepath, {
175
+ target: "es2020",
176
+ platform: "node",
177
+ format: "esm"
178
+ });
179
+ const tempFile = path.join(
180
+ process.cwd(),
181
+ MOCK_TEMP,
182
+ filepath.replace(/\.ts$/, ".mjs")
183
+ );
184
+ const tempBasename = path.dirname(tempFile);
185
+ await fs.mkdir(tempBasename, { recursive: true });
186
+ await fs.writeFile(tempFile, code, "utf8");
187
+ return await loadESModule(tempFile);
188
+ }
189
+ return await loadESModule(filepath);
190
+ }
191
+ async function loadESModule(filepath) {
192
+ const handle = await import(`${filepath}?${Date.now()}`);
193
+ if (handle && handle.default)
194
+ return handle.default;
195
+ return Object.keys(handle || {}).map((key) => handle[key]);
196
+ }
197
+ function validate(request, validator) {
198
+ return equalObj(request.headers, validator.headers) && equalObj(request.body, validator.body) && equalObj(request.params, validator.params) && equalObj(request.query, validator.query);
199
+ }
200
+ function equalObj(left, right) {
201
+ if (!right)
202
+ return true;
203
+ for (const key in right) {
204
+ if (right[key] !== left[key])
205
+ return false;
206
+ }
207
+ return true;
208
+ }
209
+
210
+ // src/plugin.ts
211
+ function mockDevServerPlugin(options = { include: ["mock/**/*.mock.*"] }) {
212
+ return {
213
+ name: "vite-plugin-mock-dev-server",
214
+ async configureServer({ middlewares, config, httpServer }) {
215
+ const middleware = await mockServerMiddleware(httpServer, config, options);
216
+ middlewares.use(middleware);
217
+ }
218
+ };
219
+ }
220
+
221
+ // src/define.ts
222
+ var defineMock = (mock) => {
223
+ return mock;
224
+ };
225
+
226
+ // src/index.ts
227
+ var src_default = mockDevServerPlugin;
228
+ export {
229
+ src_default as default,
230
+ defineMock,
231
+ mockDevServerPlugin
232
+ };
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "vite-plugin-mock-dev-server",
3
+ "version": "0.1.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/pengzhanbo/vite-plugin-mock-dev-server"
7
+ },
8
+ "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo)",
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "main": "dist/index.cjs",
12
+ "module": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "tsup": {
24
+ "entry": [
25
+ "src/index.ts"
26
+ ],
27
+ "sourcemap": false,
28
+ "dts": true,
29
+ "splitting": false,
30
+ "clean": true,
31
+ "format": [
32
+ "esm",
33
+ "cjs"
34
+ ]
35
+ },
36
+ "prettier": "@pengzhanbo/prettier-config",
37
+ "dependencies": {
38
+ "chokidar": "^3.5.3",
39
+ "co-body": "^6.1.0",
40
+ "debug": "^4.3.4",
41
+ "fast-glob": "^3.2.12",
42
+ "path-to-regexp": "^6.2.1",
43
+ "vite": "^3.2.0"
44
+ },
45
+ "devDependencies": {
46
+ "@pengzhanbo/eslint-config-ts": "^0.2.10",
47
+ "@pengzhanbo/prettier-config": "^0.2.10",
48
+ "@types/co-body": "^6.1.0",
49
+ "@types/debug": "^4.1.7",
50
+ "@types/node": "^18.11.7",
51
+ "bumpp": "^8.2.1",
52
+ "concurrently": "^7.5.0",
53
+ "eslint": "^8.26.0",
54
+ "prettier": "^2.7.1",
55
+ "tsup": "^6.3.0",
56
+ "typescript": "^4.8.4",
57
+ "vite-plugin-mock-dev-server": "file:"
58
+ },
59
+ "packageManager": "pnpm@7.14.0",
60
+ "engines": {
61
+ "node": ">=16"
62
+ },
63
+ "scripts": {
64
+ "dev": "DEBUG=vite:plugin-mock-dev-server vite example --config ./example/vite.config.ts",
65
+ "build": "tsup",
66
+ "release": "bumpp package.json --commit --push --tag && pnpm publish --access public"
67
+ }
68
+ }