rspack-plugin-mock
Rspack and Rsbuild plugin for API mock dev server.
Implement a mock-dev-server in rspack
and rsbuild
that is fully consistent with vite-plugin-mock-dev-server.
English | 简体中文
Features
- ⚡️ Lightweight, Flexible, Fast.
- 🧲 Not injection-based, non-intrusive to client code.
- 💡 ESModule/commonjs.
- 🦾 Typescript.
- 🔥 HMR
- 🏷 Support
.[cm]?js
/ .ts
/.json
/ .json5
. - 📦 Auto import mock file.
- 🎨 Support any lib, like
mockjs
, or do not use it. - 📥 Path rule matching, request parameter matching.
- ⚙️ Support Enabled/Disabled any one of the API mock.
- 📀 Supports response body content type such as
text/json/buffer/stream
. - ⚖️ Use
devServer.proxy
in rspack, or server.proxy
in rsbuild. - 🍕 Support
define
in the mock file. - ⚓️ Support
alias
in the mock file. - 📤 Support
multipart
content-type, mock upload file. - 📥 Support mock download file.
- ⚜️ Support
WebSocket Mock
and Server-Sent Events Mock
- 🗂 Support building small independent deployable mock services.
Install
npm i -D rspack-plugin-mock
yarn add rspack-plugin-mock
pnpm add -D rspack-plugin-mock
Usage
In Rspack
import { MockServerPlugin } from 'rspack-plugin-mock'
export default {
devServer: {
proxy: [
{ context: '/api', target: 'http://example.com' },
],
},
plugins: [
new MockServerPlugin(),
]
}
In Rsbuild
import { defineConfig } from '@rsbuild/core'
import { pluginMockServer } from 'rspack-plugin-mock/rsbuild'
export default defineConfig({
server: {
proxy: {
'/api': 'http://example.com',
},
},
plugins: [
pluginMockServer(),
],
})
Edit Mock file
By default, write mock data in the mock
directory of your project's root directory:
mock/**/*.mock.ts
:
import { defineMock } from 'rspack-plugin-mock/helper'
export default defineMock({
url: '/api/test',
body: { a: 1, b: 2 }
})
You can write using file formats such as .js, .mjs, .cjs, .ts, .json, .json5
.
Methods
MockServerPlugin(pluginOptions)
rspack mock server plugin.
The plugin will read the devServer
configuration and inject middleware into the http-server of @rspack/dev-server
.
import { MockServerPlugin } from 'rspack-plugin-mock'
export default {
devServer: {
proxy: [
{ context: '/api', target: 'http://example.com' },
],
},
plugins: [
new MockServerPlugin(),
]
}
pluginMockServer(pluginOptions)
rsbuild mock server plugin. It is only used in rsbuild
.
import { defineConfig } from '@rsbuild/core'
import { pluginMockServer } from 'rspack-plugin-mock/rsbuild'
export default defineConfig({
server: {
proxy: {
'/api': 'http://example.com',
},
},
plugins: [
pluginMockServer(),
],
})
defineMock(options)
mock options Type helper
import { defineMock } from 'rspack-plugin-mock/helper'
export default defineMock({
url: '/api/test',
body: { a: 1, b: 2 }
})
createDefineMock(transformer)
- transformer:
(mock: MockOptions) => MockOptions
Return a custom defineMock function to support preprocessing of mock config.
import { createDefineMock } from 'rspack-plugin-mock/helper'
const definePostMock = createDefineMock((mock) => {
mock.url = `/api/post/${mock.url}`
})
export default definePostMock({
url: 'list',
body: [{ title: '1' }, { title: '2' }],
})
createSSEStream(req, res)
Create a Server-sent events
write stream to support mocking EventSource
.
import { createSSEStream, defineMock } from 'rspack-plugin-mock/helper'
export default defineMock({
url: '/api/sse',
response: (req, res) => {
const sse = createSSEStream(req, res)
sse.write({ event: 'message', data: { message: 'hello world' } })
sse.end()
}
})
Plugin Options
options.prefix
options.wsPrefix
-
Type: string | string[]
-
Details:
Configure path matching rules for WebSocket mock service.
Any ws/wss requests with a request path starting with wsPrefix
will be intercepted by the proxy.
If wsPrefix starts with ^
, it will be recognized as a RegExp
.
Please avoid having the configurations in wsPrefix
appear in devServer.proxy
/ server.proxy
, as this may lead to conflicts in the rules.
options.cwd
options.include
-
Type: string | string[]
-
Default: ['mock/**/*.mock.{js,ts,cjs,mjs,json,json5}']
-
Details:
glob string matching mock includes files. see picomatch
options.exclude
-
Type: string | string[]
-
Default: ['**/node_modules/**', '**/.vscode/**', '**/.git/**']
-
Details:
glob string matching mock excluded files. see picomatch
options.log
options.reload
options.cors
options.formidableOptions
options.cookiesOptions
-
Type: CookiesOptions
-
Details:
Configure to cookies
options.bodyParserOptions
-
Type: BodyParserOptions
-
Details:
Configure to co-body
options.build
-
Type: boolean | ServerBuildOption
interface ServerBuildOption {
serverPort?: number
dist?: string
log?: LogLevel
}
-
Default: false
-
Details:
When you need to build a small mock service, you can configure this option.
Mock Options
options.url
options.enabled
-
Type: boolean
-
Default: true
-
Details:
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.
Therefore, it is important to be able to configure whether to enable it or not.
options.method
options.type
-
Type: 'text' | 'json' | 'buffer' | string
-
Details:
Response body data type. And also support types included in mime-db.
When the response body returns a file and you are not sure which type to use,
you can pass the file name as the value. The plugin will internally search for matching
content-type
based on the file name suffix.
-
Type: object | (request: MockRequest) => object | Promise<object>
-
Default: { 'Content-Type': 'application/json' }
-
Details:
Configure the response body headers
options.status
options.statusText
options.delay
-
Type: number | [number, number]
-
Default: 0
-
Details:
Configure response delay time, If an array is passed in, it represents the range of delay time.
unit: ms
options.body
-
Type: Body | (request: MockRequest) => Body | Promise<Body>
type Body = string | object | Buffer | Readable
-
Details:
Configure response body data content. body
takes precedence over response
.
options.response
-
Type: (req: MockRequest, res: MockResponse, next: (err?: any) => void) => void | Promise<void>
-
Details:
If you need to set complex response content, you can use the response method,
which is a middleware. Here, you can get information such as req
and res of the http request,
and then return response data through res.write() | res.end().
Otherwise, you need to execute next() method.
In req
, you can also get parsed request information such as
query
, params
, body
and refererQuery
.
options.cookies
-
Type: CookiesOptions | (request: MockRequest) => CookiesOptions | Promise<CookiesOptions>
type CookiesOptions = Record<string, CookieValue>
type CookieValue = string | [string, SetCookie]
-
Details:
Configure response body cookies
options.validator
-
Type: Validator | (request: MockRequest) => boolean
interface Validator {
query: Record<string, any>
refererQuery: Record<string, any>
body: Record<string, any>
params: Record<string, any>
headers: Headers
}
-
Details:
Request Validator
Sometimes, for the same API request, data needs to be returned based
on different request parameters.
However, if all of this is written in a single mock's body or response,
the content can become cumbersome and difficult to manage.
The function of a validator allows you to configure multiple mocks with
the same URL simultaneously and determine which mock should be used through validation.
options.ws
options.setup
interface WebSocketSetupContext {
onCleanup: (cleanup: () => void) => void
}
Types
export type MockRequest = http.IncomingMessage & ExtraRequest
export type MockResponse = http.ServerResponse<http.IncomingMessage> & {
setCookie: (
name: string,
value?: string | null,
option?: Cookies.SetOption,
) => void
}
interface ExtraRequest {
query: Record<string, any>
refererQuery: Record<string, any>
body: Record<string, any>
params: Record<string, any>
headers: Headers
getCookie: (name: string, option?: Cookies.GetOption) => string | undefined
}
Examples
exp: Match /api/test
, And returns a response body content with empty data
export default defineMock({
url: '/api/test',
})
exp: Match /api/test
, And returns a static content data
export default defineMock({
url: '/api/test',
body: { a: 1 },
})
exp: Only Support GET
Method
export default defineMock({
url: '/api/test',
method: 'GET'
})
exp: In the response header, add a custom header and cookie
export default defineMock({
url: '/api/test',
headers: { 'X-Custom': '12345678' },
cookies: { 'my-cookie': '123456789' },
})
export default defineMock({
url: '/api/test',
headers({ query, body, params, headers }) {
return { 'X-Custom': query.custom }
},
cookies() {
return { 'my-cookie': '123456789' }
}
})
exp: Define multiple mock requests for the same URL and match valid rules with validators
export default defineMock([
{
url: '/api/test',
validator: {
query: { a: 1 },
},
body: { message: 'query.a == 1' },
},
{
url: '/api/test',
validator: {
query: { a: 2 },
},
body: { message: 'query.a == 2' },
},
{
url: '/api/test?a=3',
body: { message: 'query.a == 3' }
},
{
url: '/api/test',
method: ['POST'],
validator: { body: { a: [1, 2] } }
}
])
exp: Response Delay
export default defineMock({
url: '/api/test',
delay: 6000,
})
exp: The interface request failed
export default defineMock({
url: '/api/test',
status: 502,
statusText: 'Bad Gateway'
})
exp: Dynamic route matching
export default defineMock({
url: '/api/user/:userId',
body({ params }) {
return { userId: params.userId }
}
})
The userId
in the route will be resolved into the request.params
object.
exp: Use the buffer to respond data
import { Buffer } from 'node:buffer'
export default defineMock({
url: 'api/buffer',
body: Buffer.from(JSON.stringify({ a: 1 }))
})
export default defineMock({
url: 'api/buffer',
type: 'buffer',
body: { a: 1 }
})
exp: Response file type
Simulate file download, and pass in the file reading stream.
import { createReadStream } from 'node:fs'
export default defineMock({
url: '/api/download',
type: 'my-app.dmg',
body: () => createReadStream('./my-app.dmg')
})
<a href="/api/download" download="my-app.dmg">Download File</a>
exp: Use mockjs
:
import Mock from 'mockjs'
export default defineMock({
url: '/api/test',
body: Mock.mock({
'list|1-10': [{
'id|+1': 1
}]
})
})
You need install mockjs
exp: Use response
to customize the response
export default defineMock({
url: '/api/test',
response(req, res, next) {
const { query, body, params, headers } = req
console.log(query, body, params, headers)
res.status = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({
query,
body,
params,
}))
}
})
exp: Use json / json5
{
"url": "/api/test",
"body": {
"a": 1
}
}
exp: multipart, upload files.
use formidable
to support.
<form action="/api/upload" method="post" enctype="multipart/form-data">
<p>
<span>file: </span>
<input type="file" name="files" multiple="multiple">
</p>
<p>
<span>name:</span>
<input type="text" name="name" value="mark">
</p>
<p>
<input type="submit" value="submit">
</p>
</form>
fields files
mapping to formidable.File
export default defineMock({
url: '/api/upload',
method: 'POST',
body(req) {
const body = req.body
return {
name: body.name,
files: body.files.map((file: any) => file.originalFilename),
}
},
})
exp: Graphql
import { buildSchema, graphql } from 'graphql'
const schema = buildSchema(`
type Query {
hello: String
}
`)
const rootValue = { hello: () => 'Hello world!' }
export default defineMock({
url: '/api/graphql',
method: 'POST',
body: async (request) => {
const source = request.body.source
const { data } = await graphql({ schema, rootValue, source })
return data
},
})
fetch('/api/graphql', {
method: 'POST',
body: JSON.stringify({ source: '{ hello }' })
})
exp: WebSocket Mock
export default defineMock({
url: '/socket.io',
ws: true,
setup(wss, { onCleanup }) {
const wsMap = new Map()
wss.on('connection', (ws, req) => {
const token = req.getCookie('token')
wsMap.set(token, ws)
ws.on('message', (raw) => {
const data = JSON.parse(String(raw))
if (data.type === 'ping')
return
for (const [_token, _ws] of wsMap.entires()) {
if (_token !== token)
_ws.send(raw)
}
})
})
wss.on('error', (err) => {
console.error(err)
})
onCleanup(() => wsMap.clear())
}
})
const ws = new WebSocket('ws://localhost:3000/socket.io')
ws.addEventListener('open', () => {
setInterval(() => {
ws.send(JSON.stringify({ type: 'ping' }))
}, 1000)
}, { once: true })
ws.addEventListener('message', (raw) => {
console.log(raw)
})
exp: EventSource Mock
import { createSSEStream, defineMock } from 'rspack-plugin-mock/helper'
export default defineMock({
url: '/api/sse',
response(req, res) {
const sse = createSSEStream(req, res)
let count = 0
const timer = setInterval(() => {
sse.write({
event: 'count',
data: { count: ++count },
})
if (count >= 10) {
sse.end()
clearInterval(timer)
}
}, 1000)
},
})
const es = new EventSource('/api/sse')
es.addEventListener('count', (e) => {
console.log(e.data)
})
Mock Services
In some scenarios, it may be necessary to use the data provided by mock services for display purposes, but the project may have already been packaged, built and deployed without support from rspack/rsbuild
and this plugin's mock service. Since this plugin supports importing various node modules in mock files at the design stage, the mock file cannot be inline into client build code.
The plugin support for builds a small independent mock service application that can be deployed to relevant environments during production build
. This can then be forwarded through other HTTP servers like Nginx to actual ports for mock support.
The default output is built into the directory dist/mockServer, generating files as follows:
./mockServer
├── index.js
├── mock-data.js
└── package.json
In this directory, execute npm install
to install dependencies, and then execute npm start to start the mock server.
The default port is 8080
.
You can access related mock interfaces through localhost:8080/
.
Links
License
rspack-plugin-mock is licensed under the MIT License