vite-plugin-mock-dev-server 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,7 @@ vite mock开发服务(mock-dev-server)插件。
15
15
  - ⚙️ 随意开启或关闭对某个接口的 mock配置
16
16
  - 🔥 热更新
17
17
  - ⚖️ 使用 `server.proxy` 配置
18
+ - 🍕 支持在 mock文件中使用 `viteConfig.define`配置字段
18
19
 
19
20
 
20
21
  ## 使用
@@ -41,6 +42,8 @@ export default defineConfig({
41
42
  plugins: [
42
43
  mockDevServerPlugin(),
43
44
  ],
45
+ // 这里定义的字段,在mock中也能使用
46
+ define: {},
44
47
  server: {
45
48
  proxy: {
46
49
  '^/api': {
@@ -50,8 +53,9 @@ export default defineConfig({
50
53
  }
51
54
  })
52
55
  ```
53
- 插件会读取 `server.proxy` 的配置, 仅对设置了代理的 url 匹配,启用
54
- mock 匹配。
56
+ 插件会读取 `server.proxy` 的配置, 仅对设置了代理的 url 匹配,启用mock 匹配。
57
+
58
+ 插件也会读取 `define` 配置, 支持在 mock 文件中直接使用。
55
59
 
56
60
  > 因为一般场景下,我们只需要对有代理的url进行mock,这样才能通过 vite 提供的 http 服务进行 代理和 mock
57
61
 
@@ -99,6 +103,12 @@ export default defineConfig({
99
103
 
100
104
  默认值: `['mock/**/*.mock.*']` (相对于根目录)
101
105
 
106
+ - `options.exclude`
107
+
108
+ 配置读取 mock文件时,需要排除的文件, 可以是一个 目录、glob、或者一个数组
109
+
110
+ 默认值:`['**/node_modules/**','**/test/**','**/cypress/**','src/**','**/.vscode/**','**/.git/**','**/dist/**',]`
111
+
102
112
 
103
113
  ### defineMock(config)
104
114
 
@@ -224,6 +234,8 @@ export default defineMock({
224
234
 
225
235
  `mock/**/*.mock.ts`
226
236
 
237
+ 查看更多示例: [example](/example/)
238
+
227
239
  #### 示例1:
228
240
  命中 `/api/test` 请求,并返回一个 数据为空的响应体内容
229
241
  ```ts
package/dist/index.cjs CHANGED
@@ -33,69 +33,245 @@ __export(src_exports, {
33
33
  module.exports = __toCommonJS(src_exports);
34
34
 
35
35
  // src/mockMiddleware.ts
36
- var import_promises = __toESM(require("fs/promises"), 1);
37
- var import_node_path = __toESM(require("path"), 1);
38
36
  var import_node_url = require("url");
37
+ var import_path_to_regexp = require("path-to-regexp");
38
+
39
+ // src/MockLoader.ts
40
+ var import_node_events = __toESM(require("events"), 1);
41
+ var import_promises2 = __toESM(require("fs/promises"), 1);
42
+ var import_node_path2 = __toESM(require("path"), 1);
43
+ var import_pluginutils = require("@rollup/pluginutils");
39
44
  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
45
  var import_esbuild = require("esbuild");
43
46
  var import_fast_glob = __toESM(require("fast-glob"), 1);
44
- var import_path_to_regexp = require("path-to-regexp");
45
47
 
46
48
  // src/utils.ts
49
+ var import_promises = __toESM(require("fs/promises"), 1);
50
+ var import_node_path = __toESM(require("path"), 1);
51
+ var import_debug = __toESM(require("debug"), 1);
47
52
  var isArray = (val) => Array.isArray(val);
48
53
  var isFunction = (val) => typeof val === "function";
49
54
  function sleep(timeout) {
50
55
  return new Promise((resolve) => setTimeout(resolve, timeout));
51
56
  }
52
-
53
- // src/mockMiddleware.ts
54
- var MOCK_TEMP = "node_modules/.cache/.mock_server";
55
57
  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 external = await getExternal(process.cwd());
60
- const modules = /* @__PURE__ */ Object.create(null);
61
- let mockList;
62
- for (const filepath of includePaths) {
63
- modules[filepath] = await loadModule(filepath, external);
58
+ async function getPackageDeps(cwd) {
59
+ const filepath = import_node_path.default.resolve(cwd, "package.json");
60
+ const content = await import_promises.default.readFile(filepath, "utf-8");
61
+ const pkg = JSON.parse(content);
62
+ const { dependencies = {}, devDependencies = {} } = pkg;
63
+ const deps = [...Object.keys(dependencies), ...Object.keys(devDependencies)];
64
+ return deps;
65
+ }
66
+
67
+ // src/MockLoader.ts
68
+ var MockLoader = class extends import_node_events.default {
69
+ constructor(options) {
70
+ super();
71
+ this.moduleCache = /* @__PURE__ */ new Map();
72
+ this.moduleDeps = /* @__PURE__ */ new Map();
73
+ this._mockList = [];
74
+ this.options = options;
75
+ this.cwd = options.cwd || process.cwd();
76
+ this.tempDir = options.tempDir || "node_modules/.cache/.mock_server";
64
77
  }
65
- setupMockList();
66
- debug("start watcher: ", include);
67
- debug("watcher api length: ", mockList.length);
68
- const watcher = import_chokidar.default.watch(include.splice(0)[0], {
69
- ignoreInitial: true,
70
- cwd: process.cwd()
71
- });
72
- include.length > 0 && include.forEach((item) => watcher.add(item));
73
- watcher.on("add", async (filepath) => {
74
- debug("watcher add: ", filepath);
75
- modules[filepath] = await loadModule(filepath, external);
76
- setupMockList();
77
- });
78
- watcher.on("change", async (filepath) => {
79
- debug("watcher change", filepath);
80
- modules[filepath] = await loadModule(filepath, external);
81
- setupMockList();
82
- });
83
- watcher.on("unlink", (filepath) => {
84
- debug("watcher unlink", filepath);
85
- delete modules[filepath];
86
- setupMockList();
87
- });
88
- function setupMockList() {
89
- mockList = [];
90
- Object.keys(modules).forEach((key) => {
91
- const handle = modules[key];
92
- isArray(handle) ? mockList.push(...handle) : mockList.push(handle);
78
+ get mockList() {
79
+ return this._mockList;
80
+ }
81
+ async load() {
82
+ const { include, exclude } = this.options;
83
+ const includePaths = await (0, import_fast_glob.default)(include, {
84
+ cwd: this.cwd
85
+ });
86
+ const includeFilter = (0, import_pluginutils.createFilter)(include, exclude, {
87
+ resolve: false
88
+ });
89
+ this.watchMockEntry();
90
+ this.watchDeps();
91
+ for (const filepath of includePaths.filter(includeFilter)) {
92
+ await this.loadModule(filepath);
93
+ }
94
+ this.updateMockList();
95
+ this.on("mock:update", async (filepath) => {
96
+ if (!includeFilter(filepath))
97
+ return;
98
+ await this.loadModule(filepath);
99
+ this.updateMockList();
100
+ });
101
+ this.on("mock:unlink", async (filepath) => {
102
+ if (!includeFilter(filepath))
103
+ return;
104
+ this.moduleCache.delete(filepath);
105
+ this.updateMockList();
106
+ });
107
+ }
108
+ watchMockEntry() {
109
+ const { include } = this.options;
110
+ const [firstGlob, ...otherGlob] = include;
111
+ const watcher = import_chokidar.default.watch(firstGlob, {
112
+ ignoreInitial: true,
113
+ cwd: this.cwd
114
+ });
115
+ otherGlob.length > 0 && otherGlob.forEach((glob) => watcher.add(glob));
116
+ watcher.on("add", async (filepath) => {
117
+ this.emit("mock:update", filepath);
118
+ debug("watcher:add", filepath);
119
+ });
120
+ watcher.on("change", async (filepath) => {
121
+ this.emit("mock:update", filepath);
122
+ debug("watcher:change", filepath);
93
123
  });
94
- mockList = mockList.filter(
124
+ watcher.on("unlink", async (filepath) => {
125
+ this.emit("mock:unlink", filepath);
126
+ debug("watcher:unlink", filepath);
127
+ });
128
+ this.mockWatcher = watcher;
129
+ }
130
+ watchDeps() {
131
+ const oldDeps = [];
132
+ this.on("update:deps", () => {
133
+ const deps = [];
134
+ for (const [dep] of this.moduleDeps.entries()) {
135
+ deps.push(dep);
136
+ }
137
+ if (!this.depsWatcher && deps.length > 0) {
138
+ this.depsWatcher = import_chokidar.default.watch(deps, {
139
+ ignoreInitial: true,
140
+ cwd: this.cwd
141
+ });
142
+ this.depsWatcher.on("change", (filepath) => {
143
+ const mockFiles = this.moduleDeps.get(filepath);
144
+ mockFiles && mockFiles.forEach((file) => {
145
+ this.emit("mock:update", file);
146
+ });
147
+ });
148
+ this.depsWatcher.on("unlink", (filepath) => {
149
+ this.moduleDeps.delete(filepath);
150
+ });
151
+ }
152
+ const exactDeps = deps.filter((dep) => !oldDeps.includes(dep));
153
+ exactDeps.length > 0 && this.depsWatcher.add(exactDeps);
154
+ });
155
+ }
156
+ close() {
157
+ var _a, _b;
158
+ (_a = this.mockWatcher) == null ? void 0 : _a.close();
159
+ (_b = this.depsWatcher) == null ? void 0 : _b.close();
160
+ }
161
+ updateMockList() {
162
+ const mockList = [];
163
+ for (const [, handle] of this.moduleCache.entries()) {
164
+ isArray(handle) ? mockList.push(...handle) : mockList.push(handle);
165
+ }
166
+ this._mockList = mockList.filter(
95
167
  (mock) => mock.enabled || typeof mock.enabled === "undefined"
96
168
  );
97
169
  }
98
- httpServer == null ? void 0 : httpServer.on("close", () => watcher.close());
170
+ updateModuleDeps(filepath, deps) {
171
+ Object.keys(deps).forEach((mPath) => {
172
+ const imports = deps[mPath].imports.map((_) => _.path);
173
+ imports.forEach((dep) => {
174
+ if (!this.moduleDeps.has(dep)) {
175
+ this.moduleDeps.set(dep, /* @__PURE__ */ new Set());
176
+ }
177
+ const cur = this.moduleDeps.get(dep);
178
+ cur.add(filepath);
179
+ });
180
+ });
181
+ this.emit("update:deps");
182
+ }
183
+ async loadModule(filepath) {
184
+ if (!filepath)
185
+ return;
186
+ const { code, deps } = await this.transform(filepath);
187
+ const tempFile = import_node_path2.default.join(
188
+ this.cwd,
189
+ this.tempDir,
190
+ filepath.replace(/\.ts$/, ".mjs")
191
+ );
192
+ const tempBasename = import_node_path2.default.dirname(tempFile);
193
+ await import_promises2.default.mkdir(tempBasename, { recursive: true });
194
+ await import_promises2.default.writeFile(tempFile, code, "utf8");
195
+ const handle = await import(`${tempFile}?${Date.now()}`);
196
+ const mockConfig = handle && handle.default ? handle.default : Object.keys(handle || {}).map((key) => handle[key]);
197
+ this.moduleCache.set(filepath, mockConfig);
198
+ this.updateModuleDeps(filepath, deps);
199
+ }
200
+ async transform(filepath) {
201
+ var _a;
202
+ try {
203
+ const result = await (0, import_esbuild.build)({
204
+ entryPoints: [filepath],
205
+ outfile: "out.js",
206
+ write: false,
207
+ target: "es2020",
208
+ platform: "node",
209
+ bundle: true,
210
+ external: this.options.external,
211
+ metafile: true,
212
+ format: "esm",
213
+ define: this.options.define
214
+ });
215
+ return {
216
+ code: result.outputFiles[0].text,
217
+ deps: ((_a = result.metafile) == null ? void 0 : _a.inputs) || {}
218
+ };
219
+ } catch (e) {
220
+ console.error(e);
221
+ }
222
+ return {
223
+ code: "",
224
+ deps: {}
225
+ };
226
+ }
227
+ };
228
+
229
+ // src/parseReqBody.ts
230
+ var import_co_body = __toESM(require("co-body"), 1);
231
+ async function parseReqBody(req) {
232
+ const method = req.method.toUpperCase();
233
+ if (["GET", "DELETE", "HEAD"].includes(method))
234
+ return void 0;
235
+ const type = req.headers["content-type"];
236
+ if (type === "application/json") {
237
+ return await import_co_body.default.json(req);
238
+ }
239
+ if (type === "application/x-www-form-urlencoded") {
240
+ return await import_co_body.default.form(req);
241
+ }
242
+ if (type === "text/plain") {
243
+ return await import_co_body.default.text(req);
244
+ }
245
+ return await (0, import_co_body.default)(req);
246
+ }
247
+
248
+ // src/validator.ts
249
+ function validate(request, validator) {
250
+ return equalObj(request.headers, validator.headers) && equalObj(request.body, validator.body) && equalObj(request.params, validator.params) && equalObj(request.query, validator.query);
251
+ }
252
+ function equalObj(left, right) {
253
+ if (!right)
254
+ return true;
255
+ for (const key in right) {
256
+ if (right[key] !== left[key])
257
+ return false;
258
+ }
259
+ return true;
260
+ }
261
+
262
+ // src/mockMiddleware.ts
263
+ async function mockServerMiddleware(httpServer, config, options) {
264
+ const include = isArray(options.include) ? options.include : [options.include];
265
+ const exclude = isArray(options.exclude) ? options.exclude : [options.exclude];
266
+ const external = await getPackageDeps(process.cwd());
267
+ const loader = new MockLoader({
268
+ include,
269
+ exclude,
270
+ external,
271
+ define: config.define || {}
272
+ });
273
+ await loader.load();
274
+ httpServer == null ? void 0 : httpServer.on("close", () => loader.close());
99
275
  const proxies = Object.keys(config.server.proxy || {});
100
276
  return async function(req, res, next) {
101
277
  if (proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) {
@@ -104,7 +280,7 @@ async function mockServerMiddleware(httpServer, config, options) {
104
280
  const method = req.method.toUpperCase();
105
281
  const { query, pathname } = (0, import_node_url.parse)(req.url, true);
106
282
  const reqBody = await parseReqBody(req);
107
- const currentMock = mockList.find((mock) => {
283
+ const currentMock = loader.mockList.find((mock) => {
108
284
  if (!pathname || !mock || !mock.url)
109
285
  return false;
110
286
  const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
@@ -186,91 +362,34 @@ async function mockServerMiddleware(httpServer, config, options) {
186
362
  function doesProxyContextMatchUrl(context, url) {
187
363
  return context.startsWith("^") && new RegExp(context).test(url) || url.startsWith(context);
188
364
  }
189
- async function parseReqBody(req) {
190
- const method = req.method.toUpperCase();
191
- if (["GET", "DELETE", "HEAD"].includes(method))
192
- return void 0;
193
- const type = req.headers["content-type"];
194
- if (type === "application/json") {
195
- return await import_co_body.default.json(req);
196
- }
197
- if (type === "application/x-www-form-urlencoded") {
198
- return await import_co_body.default.form(req);
199
- }
200
- if (type === "text/plain") {
201
- return await import_co_body.default.text(req);
202
- }
203
- return await (0, import_co_body.default)(req);
204
- }
205
- async function loadModule(filepath, external) {
206
- try {
207
- const result = await (0, import_esbuild.build)({
208
- entryPoints: [filepath],
209
- outfile: "out.js",
210
- write: false,
211
- target: "es2020",
212
- platform: "node",
213
- bundle: true,
214
- external,
215
- metafile: true,
216
- format: "esm"
217
- });
218
- const tempFile = import_node_path.default.join(
219
- process.cwd(),
220
- MOCK_TEMP,
221
- filepath.replace(/\.ts$/, ".mjs")
222
- );
223
- const tempBasename = import_node_path.default.dirname(tempFile);
224
- await import_promises.default.mkdir(tempBasename, { recursive: true });
225
- await import_promises.default.writeFile(tempFile, result.outputFiles[0].text, "utf8");
226
- const handle = await import(`${tempFile}?${Date.now()}`);
227
- if (handle && handle.default)
228
- return handle.default;
229
- return Object.keys(handle || {}).map((key) => handle[key]);
230
- } catch (e) {
231
- console.error(e);
232
- }
233
- return { url: "" };
234
- }
235
- async function getExternal(cwd) {
236
- const filepath = import_node_path.default.resolve(cwd, "package.json");
237
- const content = await import_promises.default.readFile(filepath, "utf-8");
238
- const pkg = JSON.parse(content);
239
- const { dependencies = {}, devDependencies = {} } = pkg;
240
- const external = [
241
- ...Object.keys(dependencies),
242
- ...Object.keys(devDependencies)
243
- ];
244
- return external;
245
- }
246
- function validate(request, validator) {
247
- return equalObj(request.headers, validator.headers) && equalObj(request.body, validator.body) && equalObj(request.params, validator.params) && equalObj(request.query, validator.query);
248
- }
249
- function equalObj(left, right) {
250
- if (!right)
251
- return true;
252
- for (const key in right) {
253
- if (right[key] !== left[key])
254
- return false;
255
- }
256
- return true;
257
- }
258
365
 
259
366
  // src/plugin.ts
260
- function mockDevServerPlugin(options = { include: ["mock/**/*.mock.*"] }) {
367
+ function mockDevServerPlugin({
368
+ include = ["mock/**/*.mock.*"],
369
+ exclude = [
370
+ "**/node_modules/**",
371
+ "**/test/**",
372
+ "**/cypress/**",
373
+ "src/**",
374
+ "**/.vscode/**",
375
+ "**/.git/**",
376
+ "**/dist/**"
377
+ ]
378
+ } = {}) {
261
379
  return {
262
380
  name: "vite-plugin-mock-dev-server",
263
381
  async configureServer({ middlewares, config, httpServer }) {
264
- const middleware = await mockServerMiddleware(httpServer, config, options);
382
+ const middleware = await mockServerMiddleware(httpServer, config, {
383
+ include,
384
+ exclude
385
+ });
265
386
  middlewares.use(middleware);
266
387
  }
267
388
  };
268
389
  }
269
390
 
270
391
  // src/define.ts
271
- var defineMock = (mock) => {
272
- return mock;
273
- };
392
+ var defineMock = (config) => config;
274
393
 
275
394
  // src/index.ts
276
395
  var src_default = mockDevServerPlugin;
package/dist/index.d.ts CHANGED
@@ -3,10 +3,16 @@ import http from 'node:http';
3
3
 
4
4
  interface MockServerPluginOptions {
5
5
  /**
6
- * glob字符串匹配 mock数据文件
6
+ * glob 字符串匹配 mock 包含的文件
7
+ * @see https://github.com/micromatch/picomatch#globbing-features
7
8
  * @default []
8
9
  */
9
- include: string | string[];
10
+ include?: string | string[];
11
+ /**
12
+ * glob 字符串匹配 mock 过滤的文件
13
+ * @see https://github.com/micromatch/picomatch#globbing-features
14
+ */
15
+ exclude?: string | string[];
10
16
  }
11
17
  declare type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'TRACE' | 'OPTIONS';
12
18
  declare type ResponseBody = Record<string, any> | any[] | string | number | null;
@@ -103,8 +109,19 @@ interface MockOptionsItem {
103
109
  }
104
110
  declare type MockOptions = MockOptionsItem[];
105
111
 
106
- declare function mockDevServerPlugin(options?: MockServerPluginOptions): Plugin;
112
+ declare function mockDevServerPlugin({ include, exclude, }?: MockServerPluginOptions): Plugin;
107
113
 
108
- declare const defineMock: (mock: MockOptionsItem | MockOptions) => MockOptionsItem | MockOptions;
114
+ /**
115
+ * mock配置帮助函数
116
+ * @param config
117
+ * @example
118
+ * ```ts
119
+ * export default defineMock({
120
+ * url: '/api/example',
121
+ * body: { a: 1 },
122
+ * })
123
+ * ```
124
+ */
125
+ declare const defineMock: (config: MockOptionsItem | MockOptions) => MockOptionsItem | MockOptions;
109
126
 
110
127
  export { MockOptions, MockOptionsItem, MockServerPluginOptions, mockDevServerPlugin as default, defineMock, mockDevServerPlugin };
package/dist/index.js CHANGED
@@ -1,67 +1,243 @@
1
1
  // src/mockMiddleware.ts
2
- import fs from "fs/promises";
3
- import path from "path";
4
2
  import { parse as urlParse } from "url";
3
+ import { match, pathToRegexp } from "path-to-regexp";
4
+
5
+ // src/MockLoader.ts
6
+ import EventEmitter from "events";
7
+ import fs2 from "fs/promises";
8
+ import path2 from "path";
9
+ import { createFilter } from "@rollup/pluginutils";
5
10
  import chokidar from "chokidar";
6
- import bodyParser from "co-body";
7
- import Debug from "debug";
8
11
  import { build } from "esbuild";
9
12
  import fastGlob from "fast-glob";
10
- import { match, pathToRegexp } from "path-to-regexp";
11
13
 
12
14
  // src/utils.ts
15
+ import fs from "fs/promises";
16
+ import path from "path";
17
+ import Debug from "debug";
13
18
  var isArray = (val) => Array.isArray(val);
14
19
  var isFunction = (val) => typeof val === "function";
15
20
  function sleep(timeout) {
16
21
  return new Promise((resolve) => setTimeout(resolve, timeout));
17
22
  }
18
-
19
- // src/mockMiddleware.ts
20
- var MOCK_TEMP = "node_modules/.cache/.mock_server";
21
23
  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 external = await getExternal(process.cwd());
26
- const modules = /* @__PURE__ */ Object.create(null);
27
- let mockList;
28
- for (const filepath of includePaths) {
29
- modules[filepath] = await loadModule(filepath, external);
24
+ async function getPackageDeps(cwd) {
25
+ const filepath = path.resolve(cwd, "package.json");
26
+ const content = await fs.readFile(filepath, "utf-8");
27
+ const pkg = JSON.parse(content);
28
+ const { dependencies = {}, devDependencies = {} } = pkg;
29
+ const deps = [...Object.keys(dependencies), ...Object.keys(devDependencies)];
30
+ return deps;
31
+ }
32
+
33
+ // src/MockLoader.ts
34
+ var MockLoader = class extends EventEmitter {
35
+ constructor(options) {
36
+ super();
37
+ this.moduleCache = /* @__PURE__ */ new Map();
38
+ this.moduleDeps = /* @__PURE__ */ new Map();
39
+ this._mockList = [];
40
+ this.options = options;
41
+ this.cwd = options.cwd || process.cwd();
42
+ this.tempDir = options.tempDir || "node_modules/.cache/.mock_server";
30
43
  }
31
- setupMockList();
32
- debug("start watcher: ", include);
33
- debug("watcher api length: ", mockList.length);
34
- const watcher = chokidar.watch(include.splice(0)[0], {
35
- ignoreInitial: true,
36
- cwd: process.cwd()
37
- });
38
- include.length > 0 && include.forEach((item) => watcher.add(item));
39
- watcher.on("add", async (filepath) => {
40
- debug("watcher add: ", filepath);
41
- modules[filepath] = await loadModule(filepath, external);
42
- setupMockList();
43
- });
44
- watcher.on("change", async (filepath) => {
45
- debug("watcher change", filepath);
46
- modules[filepath] = await loadModule(filepath, external);
47
- setupMockList();
48
- });
49
- watcher.on("unlink", (filepath) => {
50
- debug("watcher unlink", filepath);
51
- delete modules[filepath];
52
- setupMockList();
53
- });
54
- function setupMockList() {
55
- mockList = [];
56
- Object.keys(modules).forEach((key) => {
57
- const handle = modules[key];
58
- isArray(handle) ? mockList.push(...handle) : mockList.push(handle);
44
+ get mockList() {
45
+ return this._mockList;
46
+ }
47
+ async load() {
48
+ const { include, exclude } = this.options;
49
+ const includePaths = await fastGlob(include, {
50
+ cwd: this.cwd
51
+ });
52
+ const includeFilter = createFilter(include, exclude, {
53
+ resolve: false
54
+ });
55
+ this.watchMockEntry();
56
+ this.watchDeps();
57
+ for (const filepath of includePaths.filter(includeFilter)) {
58
+ await this.loadModule(filepath);
59
+ }
60
+ this.updateMockList();
61
+ this.on("mock:update", async (filepath) => {
62
+ if (!includeFilter(filepath))
63
+ return;
64
+ await this.loadModule(filepath);
65
+ this.updateMockList();
66
+ });
67
+ this.on("mock:unlink", async (filepath) => {
68
+ if (!includeFilter(filepath))
69
+ return;
70
+ this.moduleCache.delete(filepath);
71
+ this.updateMockList();
72
+ });
73
+ }
74
+ watchMockEntry() {
75
+ const { include } = this.options;
76
+ const [firstGlob, ...otherGlob] = include;
77
+ const watcher = chokidar.watch(firstGlob, {
78
+ ignoreInitial: true,
79
+ cwd: this.cwd
80
+ });
81
+ otherGlob.length > 0 && otherGlob.forEach((glob) => watcher.add(glob));
82
+ watcher.on("add", async (filepath) => {
83
+ this.emit("mock:update", filepath);
84
+ debug("watcher:add", filepath);
85
+ });
86
+ watcher.on("change", async (filepath) => {
87
+ this.emit("mock:update", filepath);
88
+ debug("watcher:change", filepath);
59
89
  });
60
- mockList = mockList.filter(
90
+ watcher.on("unlink", async (filepath) => {
91
+ this.emit("mock:unlink", filepath);
92
+ debug("watcher:unlink", filepath);
93
+ });
94
+ this.mockWatcher = watcher;
95
+ }
96
+ watchDeps() {
97
+ const oldDeps = [];
98
+ this.on("update:deps", () => {
99
+ const deps = [];
100
+ for (const [dep] of this.moduleDeps.entries()) {
101
+ deps.push(dep);
102
+ }
103
+ if (!this.depsWatcher && deps.length > 0) {
104
+ this.depsWatcher = chokidar.watch(deps, {
105
+ ignoreInitial: true,
106
+ cwd: this.cwd
107
+ });
108
+ this.depsWatcher.on("change", (filepath) => {
109
+ const mockFiles = this.moduleDeps.get(filepath);
110
+ mockFiles && mockFiles.forEach((file) => {
111
+ this.emit("mock:update", file);
112
+ });
113
+ });
114
+ this.depsWatcher.on("unlink", (filepath) => {
115
+ this.moduleDeps.delete(filepath);
116
+ });
117
+ }
118
+ const exactDeps = deps.filter((dep) => !oldDeps.includes(dep));
119
+ exactDeps.length > 0 && this.depsWatcher.add(exactDeps);
120
+ });
121
+ }
122
+ close() {
123
+ var _a, _b;
124
+ (_a = this.mockWatcher) == null ? void 0 : _a.close();
125
+ (_b = this.depsWatcher) == null ? void 0 : _b.close();
126
+ }
127
+ updateMockList() {
128
+ const mockList = [];
129
+ for (const [, handle] of this.moduleCache.entries()) {
130
+ isArray(handle) ? mockList.push(...handle) : mockList.push(handle);
131
+ }
132
+ this._mockList = mockList.filter(
61
133
  (mock) => mock.enabled || typeof mock.enabled === "undefined"
62
134
  );
63
135
  }
64
- httpServer == null ? void 0 : httpServer.on("close", () => watcher.close());
136
+ updateModuleDeps(filepath, deps) {
137
+ Object.keys(deps).forEach((mPath) => {
138
+ const imports = deps[mPath].imports.map((_) => _.path);
139
+ imports.forEach((dep) => {
140
+ if (!this.moduleDeps.has(dep)) {
141
+ this.moduleDeps.set(dep, /* @__PURE__ */ new Set());
142
+ }
143
+ const cur = this.moduleDeps.get(dep);
144
+ cur.add(filepath);
145
+ });
146
+ });
147
+ this.emit("update:deps");
148
+ }
149
+ async loadModule(filepath) {
150
+ if (!filepath)
151
+ return;
152
+ const { code, deps } = await this.transform(filepath);
153
+ const tempFile = path2.join(
154
+ this.cwd,
155
+ this.tempDir,
156
+ filepath.replace(/\.ts$/, ".mjs")
157
+ );
158
+ const tempBasename = path2.dirname(tempFile);
159
+ await fs2.mkdir(tempBasename, { recursive: true });
160
+ await fs2.writeFile(tempFile, code, "utf8");
161
+ const handle = await import(`${tempFile}?${Date.now()}`);
162
+ const mockConfig = handle && handle.default ? handle.default : Object.keys(handle || {}).map((key) => handle[key]);
163
+ this.moduleCache.set(filepath, mockConfig);
164
+ this.updateModuleDeps(filepath, deps);
165
+ }
166
+ async transform(filepath) {
167
+ var _a;
168
+ try {
169
+ const result = await build({
170
+ entryPoints: [filepath],
171
+ outfile: "out.js",
172
+ write: false,
173
+ target: "es2020",
174
+ platform: "node",
175
+ bundle: true,
176
+ external: this.options.external,
177
+ metafile: true,
178
+ format: "esm",
179
+ define: this.options.define
180
+ });
181
+ return {
182
+ code: result.outputFiles[0].text,
183
+ deps: ((_a = result.metafile) == null ? void 0 : _a.inputs) || {}
184
+ };
185
+ } catch (e) {
186
+ console.error(e);
187
+ }
188
+ return {
189
+ code: "",
190
+ deps: {}
191
+ };
192
+ }
193
+ };
194
+
195
+ // src/parseReqBody.ts
196
+ import bodyParser from "co-body";
197
+ async function parseReqBody(req) {
198
+ const method = req.method.toUpperCase();
199
+ if (["GET", "DELETE", "HEAD"].includes(method))
200
+ return void 0;
201
+ const type = req.headers["content-type"];
202
+ if (type === "application/json") {
203
+ return await bodyParser.json(req);
204
+ }
205
+ if (type === "application/x-www-form-urlencoded") {
206
+ return await bodyParser.form(req);
207
+ }
208
+ if (type === "text/plain") {
209
+ return await bodyParser.text(req);
210
+ }
211
+ return await bodyParser(req);
212
+ }
213
+
214
+ // src/validator.ts
215
+ function validate(request, validator) {
216
+ return equalObj(request.headers, validator.headers) && equalObj(request.body, validator.body) && equalObj(request.params, validator.params) && equalObj(request.query, validator.query);
217
+ }
218
+ function equalObj(left, right) {
219
+ if (!right)
220
+ return true;
221
+ for (const key in right) {
222
+ if (right[key] !== left[key])
223
+ return false;
224
+ }
225
+ return true;
226
+ }
227
+
228
+ // src/mockMiddleware.ts
229
+ async function mockServerMiddleware(httpServer, config, options) {
230
+ const include = isArray(options.include) ? options.include : [options.include];
231
+ const exclude = isArray(options.exclude) ? options.exclude : [options.exclude];
232
+ const external = await getPackageDeps(process.cwd());
233
+ const loader = new MockLoader({
234
+ include,
235
+ exclude,
236
+ external,
237
+ define: config.define || {}
238
+ });
239
+ await loader.load();
240
+ httpServer == null ? void 0 : httpServer.on("close", () => loader.close());
65
241
  const proxies = Object.keys(config.server.proxy || {});
66
242
  return async function(req, res, next) {
67
243
  if (proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) {
@@ -70,7 +246,7 @@ async function mockServerMiddleware(httpServer, config, options) {
70
246
  const method = req.method.toUpperCase();
71
247
  const { query, pathname } = urlParse(req.url, true);
72
248
  const reqBody = await parseReqBody(req);
73
- const currentMock = mockList.find((mock) => {
249
+ const currentMock = loader.mockList.find((mock) => {
74
250
  if (!pathname || !mock || !mock.url)
75
251
  return false;
76
252
  const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
@@ -152,91 +328,34 @@ async function mockServerMiddleware(httpServer, config, options) {
152
328
  function doesProxyContextMatchUrl(context, url) {
153
329
  return context.startsWith("^") && new RegExp(context).test(url) || url.startsWith(context);
154
330
  }
155
- async function parseReqBody(req) {
156
- const method = req.method.toUpperCase();
157
- if (["GET", "DELETE", "HEAD"].includes(method))
158
- return void 0;
159
- const type = req.headers["content-type"];
160
- if (type === "application/json") {
161
- return await bodyParser.json(req);
162
- }
163
- if (type === "application/x-www-form-urlencoded") {
164
- return await bodyParser.form(req);
165
- }
166
- if (type === "text/plain") {
167
- return await bodyParser.text(req);
168
- }
169
- return await bodyParser(req);
170
- }
171
- async function loadModule(filepath, external) {
172
- try {
173
- const result = await build({
174
- entryPoints: [filepath],
175
- outfile: "out.js",
176
- write: false,
177
- target: "es2020",
178
- platform: "node",
179
- bundle: true,
180
- external,
181
- metafile: true,
182
- format: "esm"
183
- });
184
- const tempFile = path.join(
185
- process.cwd(),
186
- MOCK_TEMP,
187
- filepath.replace(/\.ts$/, ".mjs")
188
- );
189
- const tempBasename = path.dirname(tempFile);
190
- await fs.mkdir(tempBasename, { recursive: true });
191
- await fs.writeFile(tempFile, result.outputFiles[0].text, "utf8");
192
- const handle = await import(`${tempFile}?${Date.now()}`);
193
- if (handle && handle.default)
194
- return handle.default;
195
- return Object.keys(handle || {}).map((key) => handle[key]);
196
- } catch (e) {
197
- console.error(e);
198
- }
199
- return { url: "" };
200
- }
201
- async function getExternal(cwd) {
202
- const filepath = path.resolve(cwd, "package.json");
203
- const content = await fs.readFile(filepath, "utf-8");
204
- const pkg = JSON.parse(content);
205
- const { dependencies = {}, devDependencies = {} } = pkg;
206
- const external = [
207
- ...Object.keys(dependencies),
208
- ...Object.keys(devDependencies)
209
- ];
210
- return external;
211
- }
212
- function validate(request, validator) {
213
- return equalObj(request.headers, validator.headers) && equalObj(request.body, validator.body) && equalObj(request.params, validator.params) && equalObj(request.query, validator.query);
214
- }
215
- function equalObj(left, right) {
216
- if (!right)
217
- return true;
218
- for (const key in right) {
219
- if (right[key] !== left[key])
220
- return false;
221
- }
222
- return true;
223
- }
224
331
 
225
332
  // src/plugin.ts
226
- function mockDevServerPlugin(options = { include: ["mock/**/*.mock.*"] }) {
333
+ function mockDevServerPlugin({
334
+ include = ["mock/**/*.mock.*"],
335
+ exclude = [
336
+ "**/node_modules/**",
337
+ "**/test/**",
338
+ "**/cypress/**",
339
+ "src/**",
340
+ "**/.vscode/**",
341
+ "**/.git/**",
342
+ "**/dist/**"
343
+ ]
344
+ } = {}) {
227
345
  return {
228
346
  name: "vite-plugin-mock-dev-server",
229
347
  async configureServer({ middlewares, config, httpServer }) {
230
- const middleware = await mockServerMiddleware(httpServer, config, options);
348
+ const middleware = await mockServerMiddleware(httpServer, config, {
349
+ include,
350
+ exclude
351
+ });
231
352
  middlewares.use(middleware);
232
353
  }
233
354
  };
234
355
  }
235
356
 
236
357
  // src/define.ts
237
- var defineMock = (mock) => {
238
- return mock;
239
- };
358
+ var defineMock = (config) => config;
240
359
 
241
360
  // src/index.ts
242
361
  var src_default = mockDevServerPlugin;
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "vite-plugin-mock-dev-server",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
+ "keywords": [
5
+ "vite",
6
+ "plugin",
7
+ "vite-plugin",
8
+ "mock",
9
+ "mock-server"
10
+ ],
4
11
  "repository": {
5
12
  "type": "git",
6
13
  "url": "https://github.com/pengzhanbo/vite-plugin-mock-dev-server"
@@ -35,6 +42,7 @@
35
42
  },
36
43
  "prettier": "@pengzhanbo/prettier-config",
37
44
  "dependencies": {
45
+ "@rollup/pluginutils": "^5.0.2",
38
46
  "chokidar": "^3.5.3",
39
47
  "co-body": "^6.1.0",
40
48
  "debug": "^4.3.4",
@@ -50,8 +58,8 @@
50
58
  "@types/debug": "^4.1.7",
51
59
  "@types/node": "^18.11.7",
52
60
  "bumpp": "^8.2.1",
53
- "concurrently": "^7.5.0",
54
61
  "eslint": "^8.26.0",
62
+ "mockjs": "^1.1.0",
55
63
  "prettier": "^2.7.1",
56
64
  "tsup": "^6.3.0",
57
65
  "typescript": "^4.8.4",