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 +14 -2
- package/dist/index.cjs +240 -121
- package/dist/index.d.ts +21 -4
- package/dist/index.js +240 -121
- package/package.json +10 -2
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
|
-
|
|
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
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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 = (
|
|
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
|
|
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(
|
|
112
|
+
declare function mockDevServerPlugin({ include, exclude, }?: MockServerPluginOptions): Plugin;
|
|
107
113
|
|
|
108
|
-
|
|
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
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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 = (
|
|
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.
|
|
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",
|