rspack-plugin-mock 0.2.0 → 0.3.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.
@@ -0,0 +1,888 @@
1
+ # rspack-plugin-mock
2
+
3
+ 在 [Rspack](https://rspack.dev) and [Rsbuild](https://rsbuild.dev) 中注入 API mock 服务。
4
+
5
+ 在 `rspack` 和 `rsbuild` 中实现一个与 [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server) 完全一致的模拟开发服务器。
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/rspack-plugin-mock"><img alt="npm" src="https://img.shields.io/npm/v/rspack-plugin-mock?style=flat-square&colorA=564341&colorB=EDED91"></a>
9
+ <img alt="node-current" src="https://img.shields.io/node/v/rspack-plugin-mock?style=flat-square&colorA=564341&colorB=EDED91">
10
+ <img alt="npm peer dependency version" src="https://img.shields.io/npm/dependency-version/rspack-plugin-mock/peer/@rspack/core?style=flat-square&colorA=564341&colorB=EDED91">
11
+ <img alt="npm peer dependency version" src="https://img.shields.io/npm/dependency-version/rspack-plugin-mock/peer/@rsbuild/core?style=flat-square&colorA=564341&colorB=EDED91">
12
+ <img alt="npm" src="https://img.shields.io/npm/dm/rspack-plugin-mock?style=flat-square&colorA=564341&colorB=EDED91">
13
+ <img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pengzhanbo/rspack-plugin-mock/lint.yml?style=flat-square&colorA=564341&colorB=EDED91">
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="./README.md">English</a> | <span>简体中文</span>
18
+ </p>
19
+
20
+ ## 特性
21
+
22
+ - ⚡️ 轻量,灵活,快速
23
+ - 🧲 非注入式,对客户端代码无侵入
24
+ - 💡 ESModule/commonjs
25
+ - 🦾 Typescript
26
+ - 🔥 热更新
27
+ - 🏷 支持 `.[cm]?js`/ `.ts` / `json` / `json5` 编写 mock 数据
28
+ - 📦 自动加载 mock 文件
29
+ - 🎨 可选择你喜欢的任意用于生成mock数据库,如 `mockjs`,或者不使用其他库
30
+ - 📥 路径规则匹配,请求参数匹配
31
+ - ⚙️ 随意开启或关闭对某个接口的 mock配置
32
+ - 📀 支持多种响应体数据类型,包括 `text/json/buffer/stream`.
33
+ - ⚖️ rspack 中使用 `devServer.proxy` 配置, rsbuild 中使用 `server.proxy` 配置
34
+ - 🍕 支持在 mock文件中使用 `define`配置
35
+ - ⚓️ 支持在 mock文件中使用 `resolve.alias` 路径别名
36
+ - 📤 支持 multipart 类型,模拟文件上传
37
+ - 📥 支持模拟文件下载
38
+ - ⚜️ 支持模拟 `WebSocket`
39
+ - 🗂 支持构建可独立部署的小型mock服务
40
+
41
+ ## 安装
42
+
43
+ ```sh
44
+ # npm
45
+ npm i -D rspack-plugin-mock
46
+ # yarn
47
+ yarn add rspack-plugin-mock
48
+ # pnp
49
+ pnpm add -D rspack-plugin-mock
50
+ ```
51
+
52
+ ## 使用
53
+
54
+ **In Rspack**
55
+
56
+ ```ts
57
+ // rspack.config.js
58
+ import { MockServerPlugin } from 'rspack-plugin-mock'
59
+
60
+ export default {
61
+ devServer: {
62
+ // 插件将会读取 `proxy` 配置
63
+ proxy: [
64
+ { context: '/api', target: 'http://example.com' },
65
+ ],
66
+ },
67
+ plugins: [
68
+ new MockServerPlugin(/* pluginOptions */),
69
+ ]
70
+ }
71
+ ```
72
+
73
+ **In Rsbuild**
74
+
75
+ ```ts
76
+ // rsbuild.config.ts
77
+ import { defineConfig } from '@rsbuild/core'
78
+ import { pluginMockServer } from 'rspack-plugin-mock/rsbuild'
79
+
80
+ export default defineConfig({
81
+ server: {
82
+ // 插件将会读取 `proxy` 配置
83
+ proxy: {
84
+ '/api': 'http://example.com',
85
+ },
86
+ },
87
+ plugins: [
88
+ pluginMockServer(/* pluginOptions */),
89
+ ],
90
+ })
91
+ ```
92
+
93
+ ### 编写 mock 配置文件
94
+
95
+ 插件默认会读取 项目根目录的 `mock` 目录:
96
+
97
+ `mock/**/*.mock.ts` :
98
+
99
+ ```ts
100
+ import { defineMock } from 'rspack-plugin-mock/helper'
101
+
102
+ export default defineMock({
103
+ url: '/api/test',
104
+ body: { a: 1, b: 2 }
105
+ })
106
+ ```
107
+
108
+ 你可以使用 `.js, .mjs, .cjs, .ts, .json, .json5` 文件格式来编写 mock 配置。
109
+
110
+ ## 方法
111
+
112
+ ### MockServerPlugin(pluginOptions)
113
+
114
+ rspack mock 服务插件。
115
+
116
+ 插件将会读取 `devServer.proxy` 配置,然后在 `@rspack/dev-server` 中注入中间件。
117
+
118
+ ```js
119
+ import { MockServerPlugin } from 'rspack-plugin-mock'
120
+
121
+ export default {
122
+ devServer: {
123
+ // 插件将会读取 `proxy` 配置
124
+ proxy: [
125
+ { context: '/api', target: 'http://example.com' },
126
+ ],
127
+ },
128
+ plugins: [
129
+ new MockServerPlugin(/* 插件配置 */),
130
+ ]
131
+ }
132
+ ```
133
+
134
+ ### pluginMockServer(pluginOptions)
135
+
136
+ rsbuild mock 服务插件. **仅适用于 `rsbuild`.**
137
+
138
+ ```ts
139
+ // rsbuild.config.ts
140
+ import { defineConfig } from '@rsbuild/core'
141
+ import { pluginMockServer } from 'rspack-plugin-mock/rsbuild'
142
+
143
+ export default defineConfig({
144
+ server: {
145
+ // 插件将会读取 `proxy` 配置
146
+ proxy: {
147
+ '/api': 'http://example.com',
148
+ },
149
+ },
150
+ plugins: [
151
+ pluginMockServer(/* 插件配置 */),
152
+ ],
153
+ })
154
+ ```
155
+
156
+ ### defineMock(options)
157
+
158
+ - **options:** [`MockOptions | MockOptions[]`](#mock-配置)
159
+
160
+ mock 配置类型帮助函数
161
+
162
+ ```ts
163
+ import { defineMock } from 'rspack-plugin-mock/helper'
164
+
165
+ export default defineMock({
166
+ url: '/api/test',
167
+ body: { a: 1, b: 2 }
168
+ })
169
+ ```
170
+
171
+ ### createDefineMock(transformer)
172
+
173
+ - **transformer:** `(mock: MockOptions) => MockOptions`
174
+
175
+ 返回一个自定义的 defineMock 函数,以支持对 mock 配置进行预处理。
176
+
177
+ ```ts
178
+ import { createDefineMock } from 'rspack-plugin-mock/helper'
179
+
180
+ const definePostMock = createDefineMock((mock) => {
181
+ mock.url = `/api/post/${mock.url}`
182
+ })
183
+
184
+ export default definePostMock({
185
+ url: 'list', // => '/api/post/list'
186
+ body: [{ title: '1' }, { title: '2' }],
187
+ })
188
+ ```
189
+
190
+ ## 插件配置
191
+
192
+ ### options.prefix
193
+
194
+ - **类型:** `string | string[]`
195
+ - **详情:**
196
+
197
+ 为 http mock 服务配置 路径匹配规则,任何请求路径以 prefix 开头的都将被拦截代理。
198
+ 如果 prefix 以 `^` 开头,将被识别为 `RegExp`。
199
+
200
+ ### options.wsPrefix
201
+
202
+ - **类型:** `string | string[]`
203
+ - **详情:**
204
+
205
+ 为 websocket mock 服务配置 路径匹配规则, 任何请求路径以 wsPrefix 开头的 ws/wss请求,都将被代理拦截。
206
+ 如果 wsPrefix 以 `^` 开头,将被识别为 `RegExp`。
207
+
208
+ 请避免在 `devServer.proxy` / `server.proxy` 中出现 `wsPrefix` 配置中相同的规则,因为这可能会导致规则冲突。
209
+
210
+ ### options.cwd
211
+
212
+ - **类型:** `string`
213
+ - **默认值:** `process.cwd()`
214
+ - **详情:**
215
+
216
+ 配置 `include` 和 `exclude` 的匹配上下文。
217
+
218
+ ### options.include
219
+
220
+ - **类型:** `string | string[]`
221
+ - **默认值:** `['mock/**/*.mock.{js,ts,cjs,mjs,json,json5}']`
222
+ - **详情:**
223
+
224
+ glob 字符串匹配 mock 包含的文件。 查看 [picomatch](https://github.com/micromatch/picomatch#globbing-features)
225
+
226
+ ### options.exclude
227
+
228
+ - **类型:** `string | string[]`
229
+ - **默认值:** `['**/node_modules/**', '**/.vscode/**', '**/.git/**']`
230
+ - **详情:**
231
+
232
+ glob 字符串匹配 mock 排除的文件。 查看 [picomatch](https://github.com/micromatch/picomatch#globbing-features)
233
+
234
+ ### options.log
235
+
236
+ - **类型:** `boolean | 'info' | 'warn' | 'error' | 'silent' | 'debug'`
237
+ - **默认值:** `info`
238
+ - **详情:**
239
+
240
+ 开启日志,或配置 日志级别
241
+
242
+ ### options.reload
243
+
244
+ - **类型:** `boolean`
245
+ - **默认值:** `false`
246
+ - **详情:**
247
+
248
+ mock资源热更新时,仅更新了数据内容,但是默认不重新刷新页面。
249
+ 当你希望每次修改mock文件都刷新页面时,可以打开此选项。
250
+
251
+ ### options.cors
252
+
253
+ - **类型:** `boolean | CorsOptions`
254
+ - **默认值:** `true`
255
+ - **详情:**
256
+
257
+ 配置 [cors](https://github.com/expressjs/cors#configuration-options)
258
+
259
+ ### options.formidableOptions
260
+
261
+ - **类型:** `FormidableOptions`
262
+ - **默认值:** `{ multiples: true }`
263
+ - **详情:**
264
+
265
+ 配置 [formidable](https://github.com/node-formidable/formidable#options)
266
+
267
+ ### options.cookiesOptions
268
+
269
+ - **类型:** `CookiesOptions`
270
+ - **详情:**
271
+
272
+ 配置 [cookies](https://github.com/pillarjs/cookies#new-cookiesrequest-response--options)
273
+
274
+ ### options.bodyParserOptions
275
+
276
+ - **类型:** `BodyParserOptions`
277
+ - **详情:**
278
+
279
+ 配置 [co-body](https://github.com/cojs/co-body#options)
280
+
281
+ ## options.build
282
+
283
+ - **类型:** `boolean | ServerBuildOption`
284
+
285
+ ```ts
286
+ interface ServerBuildOption {
287
+ /**
288
+ * 服务启动端口
289
+ * @default 8080
290
+ */
291
+ serverPort?: number
292
+ /**
293
+ * 构建输出目录,相对于 rspack/rsbuild 构建输出目录
294
+ * @default 'mockServer'
295
+ */
296
+ dist?: string
297
+
298
+ /**
299
+ * 服务日志级别
300
+ * @default 'error'
301
+ */
302
+ log?: LogLevel
303
+ }
304
+ ```
305
+
306
+ - **默认值:** `false`
307
+ - **详情:**
308
+
309
+ 当需要构建一个小型mock服务时,可配置此项。插件会在构建生产包时,额外生成一个可部署的node mock 服务包。
310
+
311
+ ## Mock 配置
312
+
313
+ ### options.url
314
+
315
+ - **类型:** `string`
316
+ - **详情:**
317
+
318
+ 需要进行 mock 的接口地址, 由 [path-to-regexp](https://github.com/pillarjs/path-to-regexp) 提供路径匹配支持。
319
+
320
+ ### options.enabled
321
+
322
+ - **类型:** `boolean`
323
+ - **默认值:** `true`
324
+ - **详情:**
325
+
326
+ 是否启动对该接口的mock,在多数场景下,我们仅需要对部分接口进行 mock,
327
+ 而不是对所有配置了mock的请求进行全量mock,所以是否能够配置是否启用很重要
328
+
329
+ ### options.method
330
+
331
+ - **类型:** `Method | Method[]`
332
+
333
+ ```ts
334
+ type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'
335
+ ```
336
+
337
+ - **默认值:** `['GET', 'POST']`
338
+ - **详情:**
339
+
340
+ 该接口允许的 请求方法,默认同时支持 GET 和 POST
341
+
342
+ ### options.type
343
+
344
+ - **类型:** `'text' | 'json' | 'buffer' | string`
345
+ - **详情:**
346
+
347
+ 响应体数据类型。 还支持 [mime-db](https://github.com/jshttp/mime-db) 中的包含的类型。
348
+
349
+ 当响应体返回的是一个文件,而你不确定应该使用哪个类型时,可以将文件名作为值传入,
350
+ 插件内部会根据文件名后缀查找匹配的`content-type`。
351
+
352
+ ### options.headers
353
+
354
+ - **类型:** `object | (request: MockRequest) => object | Promise<object>`
355
+ - **默认值:** `{ 'Content-Type': 'application/json' }`
356
+ - **详情:**
357
+
358
+ 配置响应体 headers
359
+
360
+ ### options.status
361
+
362
+ - **类型:** `number`
363
+ - **默认值:** `200`
364
+ - **详情:**
365
+
366
+ 配置 响应头状态码
367
+
368
+ ### options.statusText
369
+
370
+ - **类型:** `string`
371
+ - **默认值:** `"OK"`
372
+ - **详情:**
373
+
374
+ 配置响应头状态文本
375
+
376
+ ### options.delay
377
+
378
+ - **类型:** `number | [number, number]`
379
+ - **默认值:** `0`
380
+ - **详情:**
381
+
382
+ 配置响应延迟时间, 如果传入的是一个数组,则代表延迟时间的范围。
383
+
384
+ 单位: `ms`
385
+
386
+ ### options.body
387
+
388
+ - **类型:** `Body | (request: MockRequest) => Body | Promise<Body>`
389
+
390
+ ```ts
391
+ type Body = string | object | Buffer | Readable
392
+ ```
393
+
394
+ - **详情:**
395
+
396
+ 配置响应体数据内容 `body` 优先级高于 `response`.
397
+
398
+ ### options.response
399
+
400
+ - **类型:** `(req: MockRequest, res: MockResponse, next: (err?: any) => void) => void | Promise<void>`
401
+ - **详情:**
402
+
403
+ 如果需要设置复杂的响应内容,可以使用 response 方法,
404
+ 该方法是一个 middleware,你可以在这里拿到 http 请求的 req、res等信息,
405
+ 然后通过 `res.write() | res.end()` 返回响应数据, 否则需要执行 `next()` 方法。
406
+ 在 `req` 中,还可以拿到 `query、params、body, refererQuery` 等已解析的请求信息。
407
+
408
+ ### options.cookies
409
+
410
+ - **类型:** `CookiesOptions | (request: MockRequest) => CookiesOptions | Promise<CookiesOptions>`
411
+
412
+ ```ts
413
+ type CookiesOptions = Record<string, CookieValue>
414
+
415
+ type CookieValue = string | [string, SetCookie]
416
+ ```
417
+
418
+ - **详情:**
419
+
420
+ 配置响应体 cookies
421
+
422
+ ### options.validator
423
+
424
+ - **类型:** `Validator | (request: MockRequest) => boolean`
425
+
426
+ ```ts
427
+ interface Validator {
428
+ /**
429
+ * 请求地址中位于 `?` 后面的 queryString,已解析为 json
430
+ */
431
+ query: Record<string, any>
432
+ /**
433
+ * 请求 referer 中位于 `?` 后面的 queryString
434
+ */
435
+ refererQuery: Record<string, any>
436
+ /**
437
+ * 请求体中 body 数据
438
+ */
439
+ body: Record<string, any>
440
+ /**
441
+ * 请求地址中,`/api/id/:id` 解析后的 params 参数
442
+ */
443
+ params: Record<string, any>
444
+ /**
445
+ * 请求体中 headers
446
+ */
447
+ headers: Headers
448
+ }
449
+ ```
450
+
451
+ - **详情:**
452
+
453
+ 请求验证器
454
+
455
+ 有时候,一个相同的API请求,需要根据不同的请求参数,来决定返回数据,
456
+ 但全部都在单个 mock中的 body或者 response 中写,内容会很庞杂,不好管理,
457
+ 验证器的功能,允许你同时配置多条相同url的mock,通过验证器来判断使哪个mock生效。
458
+
459
+ ### options.ws
460
+
461
+ - **类型:** `boolean`
462
+ - **默认值:** `false`
463
+ - **详情:**
464
+
465
+ 配置是否开启 WebSocket Mock
466
+
467
+ ### options.setup
468
+
469
+ - **类型:** `(wss: WebSocketServer, ctx: WebSocketSetupContext) => void`
470
+ - **详情:**
471
+
472
+ 配置 Websocket Server
473
+
474
+ ```ts
475
+ interface WebSocketSetupContext {
476
+ /**
477
+ * 当你在定义 WSS 时,可能会执行一些自动任务或循环任务,
478
+ * 但是当热更新时,插件内部会重新执行 setup() ,
479
+ * 这可能导致出现 重复注册监听事件 和 循环任务如 `setTimeout` 等。
480
+ * 通过 `onCleanup()` 可以来清除这些自动任务或循环任务。
481
+ */
482
+ onCleanup: (cleanup: () => void) => void
483
+ }
484
+ ```
485
+
486
+ ### Types
487
+
488
+ ```ts
489
+ export type MockRequest = http.IncomingMessage & ExtraRequest
490
+
491
+ export type MockResponse = http.ServerResponse<http.IncomingMessage> & {
492
+ /**
493
+ * 设置响应体 cookies
494
+ * @see [cookies](https://github.com/pillarjs/cookies#cookiessetname--values--options)
495
+ */
496
+ setCookie: (
497
+ name: string,
498
+ value?: string | null,
499
+ option?: Cookies.SetOption,
500
+ ) => void
501
+ }
502
+
503
+ interface ExtraRequest {
504
+ /**
505
+ * 请求地址中位于 `?` 后面的 queryString,已解析为 json
506
+ */
507
+ query: Record<string, any>
508
+ /**
509
+ * 请求 referer 中位于 `?` 后面的 queryString,已解析为 json
510
+ */
511
+ refererQuery: Record<string, any>
512
+ /**
513
+ * 请求体中 body 数据
514
+ */
515
+ body: Record<string, any>
516
+ /**
517
+ * 请求地址中,`/api/id/:id` 解析后的 params 参数
518
+ */
519
+ params: Record<string, any>
520
+ /**
521
+ * 请求体中 headers
522
+ */
523
+ headers: Headers
524
+ /**
525
+ * 获取 请求中携带的 cookie
526
+ * @see [cookies](https://github.com/pillarjs/cookies#cookiesgetname--options)
527
+ */
528
+ getCookie: (name: string, option?: Cookies.GetOption) => string | undefined
529
+ }
530
+ ```
531
+
532
+ ## Example
533
+
534
+ `mock/**/*.mock.{ts,js,mjs,cjs,json,json5}`
535
+
536
+ 查看更多示例: [example](/example/)
537
+
538
+ **exp:** 命中 `/api/test` 请求,并返回一个 数据为空的响应体内容
539
+
540
+ ```ts
541
+ export default defineMock({
542
+ url: '/api/test',
543
+ })
544
+ ```
545
+
546
+ **exp:** 命中 `/api/test` 请求,并返回一个固定内容数据
547
+
548
+ ```ts
549
+ export default defineMock({
550
+ url: '/api/test',
551
+ body: { a: 1 },
552
+ })
553
+ ```
554
+
555
+ ```ts
556
+ export default defineMock({
557
+ url: '/api/test',
558
+ body: () => ({ a: 1 })
559
+ })
560
+ ```
561
+
562
+ **exp:** 限定只允许 `GET` 请求
563
+
564
+ ```ts
565
+ export default defineMock({
566
+ url: '/api/test',
567
+ method: 'GET'
568
+ })
569
+ ```
570
+
571
+ **exp:** 在返回的响应头中,添加自定义 header 和 cookie
572
+
573
+ ```ts
574
+ export default defineMock({
575
+ url: '/api/test',
576
+ headers: { 'X-Custom': '12345678' },
577
+ cookies: { 'my-cookie': '123456789' },
578
+ })
579
+ ```
580
+
581
+ ```ts
582
+ export default defineMock({
583
+ url: '/api/test',
584
+ headers({ query, body, params, headers }) {
585
+ return { 'X-Custom': query.custom }
586
+ },
587
+ cookies() {
588
+ return { 'my-cookie': '123456789' }
589
+ }
590
+ })
591
+ ```
592
+
593
+ **exp:** 定义多个相同url请求mock,并使用验证器匹配生效规则
594
+
595
+ ```ts
596
+ export default defineMock([
597
+ // 命中 /api/test?a=1
598
+ {
599
+ url: '/api/test',
600
+ validator: {
601
+ query: { a: 1 },
602
+ },
603
+ body: { message: 'query.a === 1' },
604
+ },
605
+ // 命中 /api/test?a=2
606
+ {
607
+ url: '/api/test',
608
+ validator: {
609
+ query: { a: 2 },
610
+ },
611
+ body: { message: 'query.a === 2' },
612
+ },
613
+ {
614
+ // `?a=3` 将会解析到 `validator.query`
615
+ url: '/api/test?a=3',
616
+ body: { message: 'query.a == 3' },
617
+ },
618
+ // 命中 POST /api/test 请求,且 请求体中,字段 a 为数组,且数组包含值为 1, 2 的项
619
+ {
620
+ url: '/api/test',
621
+ method: ['POST'],
622
+ validator: { body: { a: [1, 2] } }
623
+ }
624
+ ])
625
+ ```
626
+
627
+ **exp:** 延迟接口响应:
628
+
629
+ ```ts
630
+ export default defineMock({
631
+ url: '/api/test',
632
+ delay: 6000, // 延迟 6秒
633
+ })
634
+ ```
635
+
636
+ **exp:** 使接口请求失败
637
+
638
+ ```ts
639
+ export default defineMock({
640
+ url: '/api/test',
641
+ status: 502,
642
+ statusText: 'Bad Gateway'
643
+ })
644
+ ```
645
+
646
+ **exp:** 动态路由匹配
647
+
648
+ ```ts
649
+ export default defineMock({
650
+ url: '/api/user/:userId',
651
+ body({ params }) {
652
+ return { userId: params.userId }
653
+ }
654
+ })
655
+ ```
656
+
657
+ 路由中的 `userId`将会解析到 `request.params` 对象中.
658
+
659
+ **exp:** 使用 buffer 响应数据
660
+
661
+ ```ts
662
+ import { Buffer } from 'node:buffer'
663
+
664
+ // 由于 type 默认值是 json,虽然在传输过程中body使用buffer,
665
+ // 但是 content-type 还是为 json
666
+ export default defineMock({
667
+ url: 'api/buffer',
668
+ body: Buffer.from(JSON.stringify({ a: 1 }))
669
+ })
670
+ ```
671
+
672
+ ```ts
673
+ // 当 type 为 buffer 时,content-type 为 application/octet-stream,
674
+ // body 传入的数据会被转为 buffer
675
+ export default defineMock({
676
+ url: 'api/buffer',
677
+ type: 'buffer',
678
+ // 内部使用 Buffer.from(body) 进行转换
679
+ body: { a: 1 }
680
+ })
681
+ ```
682
+
683
+ **exp:** 响应文件类型
684
+
685
+ 模拟文件下载,传入文件读取流
686
+
687
+ ```ts
688
+ import { createReadStream } from 'node:fs'
689
+
690
+ export default defineMock({
691
+ url: '/api/download',
692
+ // 当你不确定类型,可传入文件名由插件内部进行解析
693
+ type: 'my-app.dmg',
694
+ body: () => createReadStream('./my-app.dmg')
695
+ })
696
+ ```
697
+
698
+ ```html
699
+ <a href="/api/download" download="my-app.dmg">下载文件</a>
700
+ ```
701
+
702
+ **exp:** 使用 `mockjs` 生成响应数据:
703
+
704
+ ```ts
705
+ import Mock from 'mockjs'
706
+
707
+ export default defineMock({
708
+ url: '/api/test',
709
+ body: Mock.mock({
710
+ 'list|1-10': [{
711
+ 'id|+1': 1
712
+ }]
713
+ })
714
+ })
715
+ ```
716
+
717
+ 请先安装 `mockjs`
718
+
719
+ **exp:** 使用 `response` 自定义响应
720
+
721
+ ```ts
722
+ export default defineMock({
723
+ url: '/api/test',
724
+ response(req, res, next) {
725
+ const { query, body, params, headers } = req
726
+ console.log(query, body, params, headers)
727
+
728
+ res.status = 200
729
+ res.setHeader('Content-Type', 'application/json')
730
+ res.end(JSON.stringify({
731
+ query,
732
+ body,
733
+ params,
734
+ }))
735
+ }
736
+ })
737
+ ```
738
+
739
+ **exp:** 使用 json / json5
740
+
741
+ ```json
742
+ {
743
+ "url": "/api/test",
744
+ "body": {
745
+ "a": 1
746
+ }
747
+ }
748
+ ```
749
+
750
+ **exp:** multipart, 文件上传.
751
+
752
+ 通过 [`formidable`](https://www.npmjs.com/package/formidable#readme) 支持。
753
+
754
+ ``` html
755
+ <form action="/api/upload" method="post" enctype="multipart/form-data">
756
+ <p>
757
+ <span>file: </span>
758
+ <input type="file" name="files" multiple="multiple">
759
+ </p>
760
+ <p>
761
+ <span>name:</span>
762
+ <input type="text" name="name" value="mark">
763
+ </p>
764
+ <p>
765
+ <input type="submit" value="submit">
766
+ </p>
767
+ </form>
768
+ ```
769
+
770
+ fields `files` 映射为 `formidable.File` 类型。
771
+
772
+ ``` ts
773
+ export default defineMock({
774
+ url: '/api/upload',
775
+ method: 'POST',
776
+ body(req) {
777
+ const body = req.body
778
+ return {
779
+ name: body.name,
780
+ files: body.files.map((file: any) => file.originalFilename),
781
+ }
782
+ },
783
+ })
784
+ ```
785
+
786
+ **exp:** Graphql
787
+
788
+ ```ts
789
+ import { buildSchema, graphql } from 'graphql'
790
+
791
+ const schema = buildSchema(`
792
+ type Query {
793
+ hello: String
794
+ }
795
+ `)
796
+ const rootValue = { hello: () => 'Hello world!' }
797
+
798
+ export default defineMock({
799
+ url: '/api/graphql',
800
+ method: 'POST',
801
+ body: async (request) => {
802
+ const source = request.body.source
803
+ const { data } = await graphql({ schema, rootValue, source })
804
+ return data
805
+ },
806
+ })
807
+ ```
808
+
809
+ ```ts
810
+ fetch('/api/graphql', {
811
+ method: 'POST',
812
+ body: JSON.stringify({ source: '{ hello }' })
813
+ })
814
+ ```
815
+
816
+ **exp:** WebSocket Mock
817
+
818
+ ```ts
819
+ // ws.mock.ts
820
+ export default defineMock({
821
+ url: '/socket.io',
822
+ ws: true,
823
+ setup(wss, { onCleanup }) {
824
+ const wsMap = new Map()
825
+ wss.on('connection', (ws, req) => {
826
+ const token = req.getCookie('token')
827
+ wsMap.set(token, ws)
828
+ ws.on('message', (raw) => {
829
+ const data = JSON.parse(String(raw))
830
+ if (data.type === 'ping')
831
+ return
832
+ // Broadcast
833
+ for (const [_token, _ws] of wsMap.entires()) {
834
+ if (_token !== token)
835
+ _ws.send(raw)
836
+ }
837
+ })
838
+ })
839
+ wss.on('error', (err) => {
840
+ console.error(err)
841
+ })
842
+ onCleanup(() => wsMap.clear())
843
+ }
844
+ })
845
+ ```
846
+
847
+ ```ts
848
+ // app.ts
849
+ const ws = new WebSocket('ws://localhost:5173/socket.io')
850
+ ws.addEventListener('open', () => {
851
+ setInterval(() => {
852
+ // heartbeat
853
+ ws.send(JSON.stringify({ type: 'ping' }))
854
+ }, 1000)
855
+ }, { once: true })
856
+ ws.addEventListener('message', (raw) => {
857
+ console.log(raw)
858
+ })
859
+ ```
860
+
861
+ ## 独立部署的小型mock服务
862
+
863
+ 在一些场景中,可能会需要使用mock服务提供的数据支持,用于展示,但可能项目已完成打包构建部署,已脱离 `rspack/rsbuild` 和本插件提供的 mock服务支持。由于本插件在设计之初,支持在mock文件中引入各种 `node` 模块,所以不能将 mock文件打包内联到客户端构建代码中。
864
+
865
+ 为了能够满足这类场景,插件提供了在 `production build` 时,也构建一个可独立部署的小型mock服务应用,可以将这个应用部署到相关的环境,后通过其他http服务器如nginx做代理转发到实际端口实现mock支持。
866
+
867
+ 构建默认输出到 `mockServer` 目录中,并生成如下文件:
868
+
869
+ ```sh
870
+ ./mockServer
871
+ ├── index.js
872
+ ├── mock-data.js
873
+ └── package.json
874
+ ```
875
+
876
+ 在该目录下,执行 `npm install` 安装依赖后,执行 `npm start` 即可启动 mock server。
877
+ 默认端口为 `8080`。
878
+ 可通过 `localhost:8080/` 访问相关的 `mock` 接口。
879
+
880
+ ## Links
881
+
882
+ - [rspack](https://rspack.dev)
883
+ - [rsbuild](https://rsbuild.dev)
884
+ - [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server)
885
+
886
+ ## License
887
+
888
+ rspack-plugin-mock is licensed under the [MIT License](./LICENSE)