vite-plugin-mock-dev-server 0.1.0 → 0.2.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/README.md +14 -2
- package/dist/index.cjs +241 -107
- package/dist/index.d.ts +21 -4
- package/dist/index.js +241 -107
- package/package.json +4 -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,68 +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
|
|
41
|
-
var import_debug = __toESM(require("debug"), 1);
|
|
45
|
+
var import_esbuild = require("esbuild");
|
|
42
46
|
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
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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";
|
|
63
77
|
}
|
|
64
|
-
|
|
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
|
-
|
|
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
|
|
92
114
|
});
|
|
93
|
-
|
|
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);
|
|
123
|
+
});
|
|
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(
|
|
94
167
|
(mock) => mock.enabled || typeof mock.enabled === "undefined"
|
|
95
168
|
);
|
|
96
169
|
}
|
|
97
|
-
|
|
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());
|
|
98
275
|
const proxies = Object.keys(config.server.proxy || {});
|
|
99
276
|
return async function(req, res, next) {
|
|
100
277
|
if (proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) {
|
|
@@ -103,7 +280,7 @@ async function mockServerMiddleware(httpServer, config, options) {
|
|
|
103
280
|
const method = req.method.toUpperCase();
|
|
104
281
|
const { query, pathname } = (0, import_node_url.parse)(req.url, true);
|
|
105
282
|
const reqBody = await parseReqBody(req);
|
|
106
|
-
const currentMock = mockList.find((mock) => {
|
|
283
|
+
const currentMock = loader.mockList.find((mock) => {
|
|
107
284
|
if (!pathname || !mock || !mock.url)
|
|
108
285
|
return false;
|
|
109
286
|
const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
|
|
@@ -185,77 +362,34 @@ async function mockServerMiddleware(httpServer, config, options) {
|
|
|
185
362
|
function doesProxyContextMatchUrl(context, url) {
|
|
186
363
|
return context.startsWith("^") && new RegExp(context).test(url) || url.startsWith(context);
|
|
187
364
|
}
|
|
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
365
|
|
|
244
366
|
// src/plugin.ts
|
|
245
|
-
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
|
+
} = {}) {
|
|
246
379
|
return {
|
|
247
380
|
name: "vite-plugin-mock-dev-server",
|
|
248
381
|
async configureServer({ middlewares, config, httpServer }) {
|
|
249
|
-
const middleware = await mockServerMiddleware(httpServer, config,
|
|
382
|
+
const middleware = await mockServerMiddleware(httpServer, config, {
|
|
383
|
+
include,
|
|
384
|
+
exclude
|
|
385
|
+
});
|
|
250
386
|
middlewares.use(middleware);
|
|
251
387
|
}
|
|
252
388
|
};
|
|
253
389
|
}
|
|
254
390
|
|
|
255
391
|
// src/define.ts
|
|
256
|
-
var defineMock = (
|
|
257
|
-
return mock;
|
|
258
|
-
};
|
|
392
|
+
var defineMock = (config) => config;
|
|
259
393
|
|
|
260
394
|
// src/index.ts
|
|
261
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,66 +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
|
|
7
|
-
import Debug from "debug";
|
|
11
|
+
import { build } from "esbuild";
|
|
8
12
|
import fastGlob from "fast-glob";
|
|
9
|
-
import { match, pathToRegexp } from "path-to-regexp";
|
|
10
|
-
import { transformWithEsbuild } from "vite";
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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";
|
|
29
43
|
}
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
58
80
|
});
|
|
59
|
-
|
|
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);
|
|
89
|
+
});
|
|
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(
|
|
60
133
|
(mock) => mock.enabled || typeof mock.enabled === "undefined"
|
|
61
134
|
);
|
|
62
135
|
}
|
|
63
|
-
|
|
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());
|
|
64
241
|
const proxies = Object.keys(config.server.proxy || {});
|
|
65
242
|
return async function(req, res, next) {
|
|
66
243
|
if (proxies.length === 0 || !proxies.some((context) => doesProxyContextMatchUrl(context, req.url))) {
|
|
@@ -69,7 +246,7 @@ async function mockServerMiddleware(httpServer, config, options) {
|
|
|
69
246
|
const method = req.method.toUpperCase();
|
|
70
247
|
const { query, pathname } = urlParse(req.url, true);
|
|
71
248
|
const reqBody = await parseReqBody(req);
|
|
72
|
-
const currentMock = mockList.find((mock) => {
|
|
249
|
+
const currentMock = loader.mockList.find((mock) => {
|
|
73
250
|
if (!pathname || !mock || !mock.url)
|
|
74
251
|
return false;
|
|
75
252
|
const methods = mock.method ? isArray(mock.method) ? mock.method : [mock.method] : ["GET", "POST"];
|
|
@@ -151,77 +328,34 @@ async function mockServerMiddleware(httpServer, config, options) {
|
|
|
151
328
|
function doesProxyContextMatchUrl(context, url) {
|
|
152
329
|
return context.startsWith("^") && new RegExp(context).test(url) || url.startsWith(context);
|
|
153
330
|
}
|
|
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
331
|
|
|
210
332
|
// src/plugin.ts
|
|
211
|
-
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
|
+
} = {}) {
|
|
212
345
|
return {
|
|
213
346
|
name: "vite-plugin-mock-dev-server",
|
|
214
347
|
async configureServer({ middlewares, config, httpServer }) {
|
|
215
|
-
const middleware = await mockServerMiddleware(httpServer, config,
|
|
348
|
+
const middleware = await mockServerMiddleware(httpServer, config, {
|
|
349
|
+
include,
|
|
350
|
+
exclude
|
|
351
|
+
});
|
|
216
352
|
middlewares.use(middleware);
|
|
217
353
|
}
|
|
218
354
|
};
|
|
219
355
|
}
|
|
220
356
|
|
|
221
357
|
// src/define.ts
|
|
222
|
-
var defineMock = (
|
|
223
|
-
return mock;
|
|
224
|
-
};
|
|
358
|
+
var defineMock = (config) => config;
|
|
225
359
|
|
|
226
360
|
// src/index.ts
|
|
227
361
|
var src_default = mockDevServerPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-mock-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/pengzhanbo/vite-plugin-mock-dev-server"
|
|
@@ -35,9 +35,11 @@
|
|
|
35
35
|
},
|
|
36
36
|
"prettier": "@pengzhanbo/prettier-config",
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@rollup/pluginutils": "^5.0.2",
|
|
38
39
|
"chokidar": "^3.5.3",
|
|
39
40
|
"co-body": "^6.1.0",
|
|
40
41
|
"debug": "^4.3.4",
|
|
42
|
+
"esbuild": "^0.15.12",
|
|
41
43
|
"fast-glob": "^3.2.12",
|
|
42
44
|
"path-to-regexp": "^6.2.1",
|
|
43
45
|
"vite": "^3.2.0"
|
|
@@ -49,8 +51,8 @@
|
|
|
49
51
|
"@types/debug": "^4.1.7",
|
|
50
52
|
"@types/node": "^18.11.7",
|
|
51
53
|
"bumpp": "^8.2.1",
|
|
52
|
-
"concurrently": "^7.5.0",
|
|
53
54
|
"eslint": "^8.26.0",
|
|
55
|
+
"mockjs": "^1.1.0",
|
|
54
56
|
"prettier": "^2.7.1",
|
|
55
57
|
"tsup": "^6.3.0",
|
|
56
58
|
"typescript": "^4.8.4",
|