rspack-plugin-mock 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 pengzhanbo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,690 @@
1
+ # rspack-plugin-mock
2
+
3
+ [rspack](https://rspack.dev) and [rsbuild](https://rsbuild.dev) plugin for API mock dev server.
4
+
5
+ Implement a mock-dev-server in `rspack` and `rsbuild` that is fully consistent with [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server).
6
+
7
+ ## Features
8
+
9
+ - ⚡️ Lightweight, Flexible, Fast.
10
+ - 🧲 Not injection-based, non-intrusive to client code.
11
+ - 💡 ESModule/commonjs.
12
+ - 🦾 Typescript.
13
+ - 🔥 HMR
14
+ - 🏷 Support `.[cm]js`/ `.ts` /`.json` / `.json5`.
15
+ - 📦 Auto import mock file.
16
+ - 🎨 Support any lib, like `mockjs`, or do not use it.
17
+ - 📥 Path rule matching, request parameter matching.
18
+ - ⚙️ Support Enabled/Disabled any one of the API mock.
19
+ - 📀 Supports response body content type such as `text/json/buffer/stream`.
20
+ - ⚖️ Use `devServer.proxy` in rspack, or `server.proxy` in rsbuild.
21
+ - 🍕 Support `viteConfig.define` and `env` in the mock file.
22
+ - ⚓️ Support `viteConfig.resolve.alias` in the mock file.
23
+ - 📤 Support `multipart` content-type, mock upload file.
24
+ - 📥 Support mock download file.
25
+
26
+ ## Usage
27
+
28
+ ### Install
29
+
30
+ ```sh
31
+ # npm
32
+ npm i -D rspack-plugin-mock
33
+ # yarn
34
+ yarn add rspack-plugin-mock
35
+ # pnp
36
+ pnpm add -D rspack-plugin-mock
37
+ ```
38
+
39
+ ### Configuration
40
+
41
+ **In Rspack**
42
+
43
+ ```ts
44
+ // rspack.config.js
45
+ import { MockServerPlugin } from 'rspack-plugin-mock'
46
+
47
+ export default {
48
+ devServer: {
49
+ // The plugin will read the `proxy` option from the `devServer`
50
+ proxy: [
51
+ { context: '/api', target: 'http://example.com' },
52
+ ],
53
+ },
54
+ plugins: [
55
+ new MockServerPlugin(/* pluginOptions */),
56
+ ]
57
+ }
58
+ ```
59
+
60
+ **In Rsbuild**
61
+
62
+ ```ts
63
+ // rsbuild.config.ts
64
+ import { defineConfig } from '@rsbuild/core'
65
+ import { pluginMockServer } from 'rspack-plugin-mock/rsbuild'
66
+
67
+ export default defineConfig({
68
+ server: {
69
+ // The plugin will read the `proxy` option from the `server`
70
+ proxy: {
71
+ '/api': 'http://example.com',
72
+ },
73
+ },
74
+ plugins: [
75
+ pluginMockServer(/* pluginOptions */),
76
+ ],
77
+ })
78
+ ```
79
+
80
+ ### Edit Mock file
81
+
82
+ By default, write mock data in the mock directory of your project's root directory:
83
+
84
+ `mock/**/*.mock.ts` :
85
+
86
+ ```ts
87
+ import { defineMock } from 'rspack-plugin-mock/helper'
88
+
89
+ export default defineMock({
90
+ url: '/api/test',
91
+ body: { a: 1, b: 2 }
92
+ })
93
+ ```
94
+
95
+ You can write using file formats such as `.js, .mjs, .cjs, .ts, .json, .json5`.
96
+
97
+ ## Methods
98
+
99
+ ### MockServerPlugin(pluginOptions)
100
+
101
+ rspack mock server plugin.
102
+
103
+ The plugin will read the `devServer` configuration and inject middleware into the http-server of `@rspack/dev-server`.
104
+
105
+ ```js
106
+ import { MockServerPlugin } from 'rspack-plugin-mock'
107
+
108
+ export default {
109
+ devServer: {
110
+ // The plugin will read the `proxy` option from the `devServer`
111
+ proxy: [
112
+ { context: '/api', target: 'http://example.com' },
113
+ ],
114
+ },
115
+ plugins: [
116
+ new MockServerPlugin(/* pluginOptions */),
117
+ ]
118
+ }
119
+ ```
120
+
121
+ ### pluginMockServer(pluginOptions)
122
+
123
+ rsbuild mock server plugin. **It is only used in `rsbuild`.**
124
+
125
+ ```ts
126
+ // rsbuild.config.ts
127
+ import { defineConfig } from '@rsbuild/core'
128
+ import { pluginMockServer } from 'rspack-plugin-mock/rsbuild'
129
+
130
+ export default defineConfig({
131
+ server: {
132
+ // The plugin will read the `proxy` option from the `server`
133
+ proxy: {
134
+ '/api': 'http://example.com',
135
+ },
136
+ },
137
+ plugins: [
138
+ pluginMockServer(/* pluginOptions */),
139
+ ],
140
+ })
141
+ ```
142
+
143
+ ### defineMock(options)
144
+
145
+ - **options:** [`MockOptions | MockOptions[]`](#mock-options)
146
+
147
+ mock options Type helper
148
+
149
+ ```ts
150
+ import { defineMock } from 'rspack-plugin-mock/helper'
151
+
152
+ export default defineMock({
153
+ url: '/api/test',
154
+ body: { a: 1, b: 2 }
155
+ })
156
+ ```
157
+
158
+ ### createDefineMock(transformer)
159
+
160
+ - **transformer:** `(mock: MockOptions) => MockOptions`
161
+
162
+ Return a custom defineMock function to support preprocessing of mock config.
163
+
164
+ ```ts
165
+ const definePostMock = createDefineMock((mock) => {
166
+ mock.url = `/api/post/${mock.url}`
167
+ })
168
+
169
+ export default definePostMock({
170
+ url: 'list', // => '/api/post/list'
171
+ body: [{ title: '1' }, { title: '2' }],
172
+ })
173
+ ```
174
+
175
+ ## Plugin Options
176
+
177
+ ### options.prefix
178
+
179
+ - **Type:** `string | string[]`
180
+ - **Details:**
181
+
182
+ To configure the path matching rules for http mock services,
183
+ any request path starting with prefix will be intercepted and proxied.
184
+ If the prefix starts with `^`, it will be recognized as a `RegExp`.
185
+
186
+ ### options.cwd
187
+
188
+ - **Type:** `string`
189
+ - **Default:** `process.cwd()`
190
+ - **Details:**
191
+
192
+ Configure the matching context for `include` and `exclude`.
193
+
194
+ ### options.include
195
+
196
+ - **Type:** `string | string[]`
197
+ - **Default:** `['mock/**/*.mock.{js,ts,cjs,mjs,json,json5}']`
198
+ - **Details:**
199
+
200
+ glob string matching mock includes files. see [picomatch](https://github.com/micromatch/picomatch#globbing-features)
201
+
202
+ ### options.exclude
203
+
204
+ - **Type:** `string | string[]`
205
+ - **Default:** `['**/node_modules/**', '**/.vscode/**', '**/.git/**']`
206
+ - **Details:**
207
+
208
+ glob string matching mock excluded files. see [picomatch](https://github.com/micromatch/picomatch#globbing-features)
209
+
210
+ ### options.log
211
+
212
+ - **Type:** `boolean | 'info' | 'warn' | 'error' | 'silent' | 'debug'`
213
+ - **Default:** `info`
214
+ - **Details:**
215
+
216
+ Enable log and configure log level
217
+
218
+ ### options.cors
219
+
220
+ - **Type:** `boolean | CorsOptions`
221
+ - **Default:** `true`
222
+ - **Details:**
223
+
224
+ Configure to [cors](https://github.com/expressjs/cors#configuration-options)
225
+
226
+ ### options.formidableOptions
227
+
228
+ - **Type:** `FormidableOptions`
229
+ - **Default:** `{ multiples: true }`
230
+ - **Details:**
231
+
232
+ Configure to [formidable](https://github.com/node-formidable/formidable#options)
233
+
234
+ ### options.cookiesOptions
235
+
236
+ - **Type:** `CookiesOptions`
237
+ - **Details:**
238
+
239
+ Configure to [cookies](https://github.com/pillarjs/cookies#new-cookiesrequest-response--options)
240
+
241
+ ### options.bodyParserOptions
242
+
243
+ - **Type:** `BodyParserOptions`
244
+ - **Details:**
245
+
246
+ Configure to [co-body](https://github.com/cojs/co-body#options)
247
+
248
+ ## Mock Options
249
+
250
+ ### options.url
251
+
252
+ - **Type:** `string`
253
+ - **Details:**
254
+
255
+ The interface address that needs to be mocked, supported by [path-to-regexp](https://github.com/pillarjs/path-to-regexp) for path matching.
256
+
257
+ ### options.enabled
258
+
259
+ - **Type:** `boolean`
260
+ - **Default:** `true`
261
+ - **Details:**
262
+
263
+ Whether to enable mock for this interface. In most scenarios, we only need to mock some interfaces instead of all requests that have been configured with mock.
264
+ Therefore, it is important to be able to configure whether to enable it or not.
265
+
266
+ ### options.method
267
+
268
+ - **Type:** `Method | Method[]`
269
+ - **Default:** `['GET', 'POST']`
270
+ - **Details:**
271
+
272
+ The interface allows request methods
273
+
274
+ ```ts
275
+ type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'
276
+ ```
277
+
278
+ ### options.type
279
+
280
+ - **Type:** `'text' | 'json' | 'buffer' | string`
281
+ - **Details:**
282
+
283
+ Response body data type. And also support types included in [mime-db](https://github.com/jshttp/mime-db).
284
+
285
+ When the response body returns a file and you are not sure which type to use,
286
+ you can pass the file name as the value. The plugin will internally search for matching
287
+ `content-type` based on the file name suffix.
288
+
289
+ ### options.headers
290
+
291
+ - **Type:** `object | (request: MockRequest) => object | Promise<object>`
292
+ - **Default:** `{ 'Content-Type': 'application/json' }`
293
+ - **Details:**
294
+
295
+ Configure the response body headers
296
+
297
+ ### options.status
298
+
299
+ - **Type:** `number`
300
+ - **Default:** `200`
301
+ - **Details:**
302
+
303
+ Configure Response Header Status Code
304
+
305
+ ### options.statusText
306
+
307
+ - **Type:** `string`
308
+ - **Default:** `"OK"`
309
+ - **Details:**
310
+
311
+ Configure response header status text
312
+
313
+ ### options.delay
314
+
315
+ - **Type:** `number | [number, number]`
316
+ - **Default:** `0`
317
+ - **Details:**
318
+
319
+ Configure response delay time, If an array is passed in, it represents the range of delay time.
320
+
321
+ unit: `ms`
322
+
323
+ ### options.body
324
+
325
+ - **Type:** `Body | (request: MockRequest) => Body | Promise<Body>`
326
+
327
+ ```ts
328
+ type Body = string | object | Buffer | Readable
329
+ ```
330
+
331
+ - **Details:**
332
+
333
+ Configure response body data content. `body` takes precedence over `response`.
334
+
335
+ ### options.response
336
+
337
+ - **Type:** `(req: MockRequest, res: MockResponse, next: (err?: any) => void) => void | Promise<void>`
338
+ - **Details:**
339
+
340
+ If you need to set complex response content, you can use the response method,
341
+ which is a middleware. Here, you can get information such as req
342
+ and res of the http request,
343
+ and then return response data through res.write() | res.end().
344
+ Otherwise, you need to execute next() method.
345
+ In `req`, you can also get parsed request information such as
346
+ `query`, `params`, `body` and `refererQuery`.
347
+
348
+ ### options.cookies
349
+
350
+ - **Type:** `CookiesOptions | (request: MockRequest) => CookiesOptions | Promise<CookiesOptions>`
351
+
352
+ ```ts
353
+ type CookiesOptions = Record<string, CookieValue>
354
+ type CookieValue = string | [string, SetCookie]
355
+ ```
356
+
357
+ - **Details:**
358
+
359
+ Configure response body cookies
360
+
361
+ ### Types
362
+
363
+ ```ts
364
+ export type MockRequest = http.IncomingMessage & ExtraRequest
365
+
366
+ export type MockResponse = http.ServerResponse<http.IncomingMessage> & {
367
+ /**
368
+ * Set cookie in response
369
+ * @see [cookies](https://github.com/pillarjs/cookies#cookiessetname--values--options)
370
+ */
371
+ setCookie: (
372
+ name: string,
373
+ value?: string | null,
374
+ option?: Cookies.SetOption,
375
+ ) => void
376
+ }
377
+
378
+ interface ExtraRequest {
379
+ /**
380
+ * The query string located after `?` in the request address has been parsed into JSON.
381
+ */
382
+ query: Record<string, any>
383
+ /**
384
+ * The queryString located after `?` in the referer request has been parsed as JSON.
385
+ */
386
+ refererQuery: Record<string, any>
387
+ /**
388
+ * Body data in the request
389
+ */
390
+ body: Record<string, any>
391
+ /**
392
+ * The params parameter parsed from the `/api/id/:id` in the request address.
393
+ */
394
+ params: Record<string, any>
395
+ /**
396
+ * headers data in the request
397
+ */
398
+ headers: Headers
399
+ /**
400
+ * Get the cookie carried in the request.
401
+ * @see [cookies](https://github.com/pillarjs/cookies#cookiesgetname--options)
402
+ */
403
+ getCookie: (name: string, option?: Cookies.GetOption) => string | undefined
404
+ }
405
+ ```
406
+
407
+ ## Examples
408
+
409
+ **exp:** Match `/api/test`, And returns a response body content with empty data
410
+
411
+ ``` ts
412
+ export default defineMock({
413
+ url: '/api/test',
414
+ })
415
+ ```
416
+
417
+ **exp:** Match `/api/test` , And returns a static content data
418
+
419
+ ``` ts
420
+ export default defineMock({
421
+ url: '/api/test',
422
+ body: { a: 1 },
423
+ })
424
+ ```
425
+
426
+ **exp:** Only Support `GET` Method
427
+
428
+ ``` ts
429
+ export default defineMock({
430
+ url: '/api/test',
431
+ method: 'GET'
432
+ })
433
+ ```
434
+
435
+ **exp:** In the response header, add a custom header and cookie
436
+
437
+ ``` ts
438
+ export default defineMock({
439
+ url: '/api/test',
440
+ headers: { 'X-Custom': '12345678' },
441
+ cookies: { 'my-cookie': '123456789' },
442
+ })
443
+ ```
444
+
445
+ ``` ts
446
+ export default defineMock({
447
+ url: '/api/test',
448
+ headers({ query, body, params, headers }) {
449
+ return { 'X-Custom': query.custom }
450
+ },
451
+ cookies() {
452
+ return { 'my-cookie': '123456789' }
453
+ }
454
+ })
455
+ ```
456
+
457
+ **exp:** Define multiple mock requests for the same URL and match valid rules with validators
458
+
459
+ ``` ts
460
+ export default defineMock([
461
+ // Match /api/test?a=1
462
+ {
463
+ url: '/api/test',
464
+ validator: {
465
+ query: { a: 1 },
466
+ },
467
+ body: { message: 'query.a == 1' },
468
+ },
469
+ // Match /api/test?a=2
470
+ {
471
+ url: '/api/test',
472
+ validator: {
473
+ query: { a: 2 },
474
+ },
475
+ body: { message: 'query.a == 2' },
476
+ },
477
+ {
478
+ // `?a=3` will resolve to `validator.query`
479
+ url: '/api/test?a=3',
480
+ body: { message: 'query.a == 3' }
481
+ },
482
+ // Hitting the POST /api/test request, and in the request body,
483
+ // field a is an array that contains items with values of 1 and 2.
484
+ {
485
+ url: '/api/test',
486
+ method: ['POST'],
487
+ validator: { body: { a: [1, 2] } }
488
+ }
489
+ ])
490
+ ```
491
+
492
+ **exp:** Response Delay
493
+
494
+ ``` ts
495
+ export default defineMock({
496
+ url: '/api/test',
497
+ delay: 6000, // delay 6 seconds
498
+ })
499
+ ```
500
+
501
+ **exp:** The interface request failed
502
+
503
+ ``` ts
504
+ export default defineMock({
505
+ url: '/api/test',
506
+ status: 502,
507
+ statusText: 'Bad Gateway'
508
+ })
509
+ ```
510
+
511
+ **exp:** Dynamic route matching
512
+
513
+ ``` ts
514
+ export default defineMock({
515
+ url: '/api/user/:userId',
516
+ body({ params }) {
517
+ return { userId: params.userId }
518
+ }
519
+ })
520
+ ```
521
+
522
+ The `userId` in the route will be resolved into the `request.params` object.
523
+
524
+ **exp:** Use the buffer to respond data
525
+
526
+ ```ts
527
+ import { Buffer } from 'node:buffer'
528
+
529
+ // Since the default value of type is json,
530
+ // although buffer is used for body during transmission,
531
+ // the content-type is still json.
532
+ export default defineMock({
533
+ url: 'api/buffer',
534
+ body: Buffer.from(JSON.stringify({ a: 1 }))
535
+ })
536
+ ```
537
+
538
+ ``` ts
539
+ // When the type is buffer, the content-type is application/octet-stream.
540
+ // The data passed in through body will be converted to a buffer.
541
+ export default defineMock({
542
+ url: 'api/buffer',
543
+ type: 'buffer',
544
+ // Convert using Buffer.from(body) for internal use
545
+ body: { a: 1 }
546
+ })
547
+ ```
548
+
549
+ **exp:** Response file type
550
+
551
+ Simulate file download, and pass in the file reading stream.
552
+
553
+ ``` ts
554
+ import { createReadStream } from 'node:fs'
555
+
556
+ export default defineMock({
557
+ url: '/api/download',
558
+ // When you are unsure of the type, you can pass in the file name for internal parsing by the plugin.
559
+ type: 'my-app.dmg',
560
+ body: () => createReadStream('./my-app.dmg')
561
+ })
562
+ ```
563
+
564
+ ```html
565
+ <a href="/api/download" download="my-app.dmg">Download File</a>
566
+ ```
567
+
568
+ **exp:** Use `mockjs`:
569
+
570
+ ``` ts
571
+ import Mock from 'mockjs'
572
+
573
+ export default defineMock({
574
+ url: '/api/test',
575
+ body: Mock.mock({
576
+ 'list|1-10': [{
577
+ 'id|+1': 1
578
+ }]
579
+ })
580
+ })
581
+ ```
582
+
583
+ You need install `mockjs`
584
+
585
+ **exp:** Use `response` to customize the response
586
+
587
+ ``` ts
588
+ export default defineMock({
589
+ url: '/api/test',
590
+ response(req, res, next) {
591
+ const { query, body, params, headers } = req
592
+ console.log(query, body, params, headers)
593
+
594
+ res.status = 200
595
+ res.setHeader('Content-Type', 'application/json')
596
+ res.end(JSON.stringify({
597
+ query,
598
+ body,
599
+ params,
600
+ }))
601
+ }
602
+ })
603
+ ```
604
+
605
+ **exp:** Use json / json5
606
+
607
+ ``` json
608
+ {
609
+ "url": "/api/test",
610
+ "body": {
611
+ "a": 1
612
+ }
613
+ }
614
+ ```
615
+
616
+ **exp:** multipart, upload files.
617
+
618
+ use [`formidable`](https://www.npmjs.com/package/formidable#readme) to support.
619
+
620
+ ``` html
621
+ <form action="/api/upload" method="post" enctype="multipart/form-data">
622
+ <p>
623
+ <span>file: </span>
624
+ <input type="file" name="files" multiple="multiple">
625
+ </p>
626
+ <p>
627
+ <span>name:</span>
628
+ <input type="text" name="name" value="mark">
629
+ </p>
630
+ <p>
631
+ <input type="submit" value="submit">
632
+ </p>
633
+ </form>
634
+ ```
635
+
636
+ fields `files` mapping to `formidable.File`
637
+
638
+ ``` ts
639
+ export default defineMock({
640
+ url: '/api/upload',
641
+ method: 'POST',
642
+ body(req) {
643
+ const body = req.body
644
+ return {
645
+ name: body.name,
646
+ files: body.files.map((file: any) => file.originalFilename),
647
+ }
648
+ },
649
+ })
650
+ ```
651
+
652
+ **exp:** Graphql
653
+
654
+ ``` ts
655
+ import { buildSchema, graphql } from 'graphql'
656
+
657
+ const schema = buildSchema(`
658
+ type Query {
659
+ hello: String
660
+ }
661
+ `)
662
+ const rootValue = { hello: () => 'Hello world!' }
663
+
664
+ export default defineMock({
665
+ url: '/api/graphql',
666
+ method: 'POST',
667
+ body: async (request) => {
668
+ const source = request.body.source
669
+ const { data } = await graphql({ schema, rootValue, source })
670
+ return data
671
+ },
672
+ })
673
+ ```
674
+
675
+ ``` ts
676
+ fetch('/api/graphql', {
677
+ method: 'POST',
678
+ body: JSON.stringify({ source: '{ hello }' })
679
+ })
680
+ ```
681
+
682
+ ## Redirect
683
+
684
+ - [rspack](https://rspack.dev)
685
+ - [rsbuild](https://rsbuild.dev)
686
+ - [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server)
687
+
688
+ ## License
689
+
690
+ [MIT License](/LICENSE)