Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pnpm docs:build

## Project Structure

```
```txt
vite-plugin-mock-server/
├── pnpm-workspace.yaml # Monorepo workspace config
├── vitest.config.ts # Test configuration (runs spec.ts in __tests__)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,9 @@ To address this, the plugin offers a `defineMockData` function, which allows usi
type defineMockData<T> = (
key: string, // key
initialData: T, // initial data
options?: {
persistOnHMR?: boolean // persist the data value on HMR
} // options
) => [getter, setter] & { value: T }
```

Expand Down
3 changes: 3 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,9 @@ type Response = http.ServerResponse<http.IncomingMessage> & {
type defineMockData<T> = (
key: string, // 数据唯一标识符
initialData: T, // 初始化数据
options?: {
persistOnHMR?: boolean // 是否在热更新时保持数据状态
} // 可选配置
) => [getter, setter] & { value: T }
```

Expand Down
26 changes: 21 additions & 5 deletions docs/en/api/define-mock-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ function defineMockData<T = any>(
- **Description**: Initial data value
- **Required**: Yes

### options

- **Type**: `{ persistOnHMR?: boolean }`
- **Description**: An options object used to configure the data sharing mechanism.
- **Optional**: Yes
- **Default Value**: `{}`
- **persistOnHMR**: Whether to preserve the initial data during HMR. The default value is `false`, meaning whether data will be reinitialized during HMR.

By default, the plugin internally determines whether data needs to be reinitialized by comparing whether the initial data has changed.
However, when using libraries such as `mockjs` or `faker`, since the data is randomly generated, the plugin cannot directly determine whether the data has changed.
Therefore, manual configuration of the `persistOnHMR` option is required.

## Return Value

- **Type**: `MockData<T>`
Expand Down Expand Up @@ -155,10 +167,14 @@ interface Todo {
createdAt: number
}

const todos = defineMockData<Todo[]>('todos', [
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
])
const todos = defineMockData<Todo[]>(
'todos',
[
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
],
{ persistOnHMR: true }, // Ensure data is not reset during HMR, preserving existing data state
)

export default defineMock([
// Get todo list (supports filtering)
Expand Down Expand Up @@ -231,7 +247,7 @@ export default defineMock([
`defineMockData` has built-in hot reload handling mechanism:

1. **Caching Mechanism**: Data is cached to ensure multiple Mock files access the same instance
2. **Hot Reload Protection**: Repeated compilations in a short time (such as using `mockjs` or `faker-js` to generate random data) will not cause data reset
2. **Hot Reload Protection**: During hot updates, the initial data will not be reset if it remains unchanged (however, if using `mockjs` or `faker-js` to generate random data, you need to manually configure `persistOnHMR` to `true`).
3. **Data Persistence**: Data state is preserved after file modification and recompilation

## Important Notes
Expand Down
2 changes: 1 addition & 1 deletion docs/en/guide/define-mock-data.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# defineMockData(key, initialData)
# defineMockData(key, initialData [, options])

Define shareable mock data.

Expand Down
31 changes: 25 additions & 6 deletions docs/zh/api/define-mock-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
```ts
function defineMockData<T = any>(
key: string,
initialData: T
initialData: T,
options?: {
persistOnHMR?: boolean // 是否在 HMR 时保持初始数据
}
): MockData<T>
```

Expand All @@ -31,6 +34,18 @@ function defineMockData<T = any>(
- **描述**: 初始数据值
- **必填**: 是

### options

- **类型**: `{ persistOnHMR?: boolean }`
- **描述**: 选项对象,用于配置数据共享机制
- **可选**: 是
- **默认值**: `{}`
- **persistOnHMR**: 是否在 HMR 时保持初始数据,默认值为 `false`, 即在 HMR 时会不会重新初始化数据。

插件内部默认会通过比对初始数据是否发生变化来判断是否需要重新初始化数据。
但是在使用 `mockjs` 或 `faker` 等库时,由于数据随机产生,插件无法直接判断数据是否发生变化,
因此需要手动配置 `persistOnHMR` 选项。

## 返回值

- **类型**: `MockData<T>`
Expand Down Expand Up @@ -155,10 +170,14 @@ interface Todo {
createdAt: number
}

const todos = defineMockData<Todo[]>('todos', [
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
])
const todos = defineMockData<Todo[]>(
'todos',
[
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
],
{ persistOnHMR: true }, // 热更新时确保数据不被重置,保留已有的数据状态
)

export default defineMock([
// 获取待办列表(支持筛选)
Expand Down Expand Up @@ -231,7 +250,7 @@ export default defineMock([
`defineMockData` 内置了热更新处理机制:

1. **缓存机制**: 数据会被缓存,确保多个 Mock 文件访问的是同一实例
2. **热更新保护**: 在短时间内的重复编译(如使用 `mockjs` 或 `faker-js` 生成随机数据)不会导致数据重置
2. **热更新保护**: 热更新时,初始数据未发生变化时,不会重置数据 (但如使用 `mockjs` 或 `faker-js` 生成随机数据,需要手动配置 `persistOnHMR` 为 `true`)
3. **数据持久**: 文件修改后的重新编译会保留已有的数据状态

## 注意事项
Expand Down
2 changes: 1 addition & 1 deletion docs/zh/guide/define-mock-data.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# defineMockData(key, initialData)
# defineMockData(key, initialData [, options])

定义可共享的 mock data,用于在多个 Mock 文件之间共享状态。

Expand Down
2 changes: 1 addition & 1 deletion example/mock/data/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ export default defineMockData<Record<string, User>>('users', {
username: mock.mock('@first()'),
age: 20,
},
})
}, { persistOnHMR: true })
1 change: 1 addition & 0 deletions vite-plugin-mock-dev-server/src/core/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function resolvePluginOptions({
const resolvedRecord = resolveRecordOptions(cwd, dir, record)

return {
enabled: true,
cwd,
dir,
include,
Expand Down
81 changes: 71 additions & 10 deletions vite-plugin-mock-dev-server/src/helpers/defineMockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
* 为此,插件提供了一种基于 memory 的数据共享机制。
*/

import { deepClone, deepEqual, isFunction } from '@pengzhanbo/utils'
import { createHash } from 'node:crypto'
import { deepClone, isFunction } from '@pengzhanbo/utils'

/**
* Mock data cache
Expand Down Expand Up @@ -55,11 +56,11 @@ class CacheImpl<T = any> {
value: T

/**
* Initial value backup, used to detect if initial data has changed
* Initial value hash, used to detect if initial data has changed
*
* 初始化数据的备份,用于判断传入的初始化数据是否发生变更
* 初始化数据的哈希值,用于判断传入的初始化数据是否发生变更
*/
#initialValue: T
#hash: string

/**
* Last update timestamp
Expand All @@ -68,17 +69,26 @@ class CacheImpl<T = any> {
*/
#lastUpdate: number

/**
* Whether to persist data on HMR
*
* 热更新时是否保持数据
*/
#persistOnHMR: boolean

/**
* Constructor
*
* 构造函数
*
* @param value - Initial value / 初始值
* @param persistOnHMR - Whether to persist data on HMR / 热更新时是否保持数据
*/
constructor(value: T) {
constructor(value: T, persistOnHMR?: boolean) {
this.value = value
this.#initialValue = deepClone(value)
this.#hash = getHash(value)
this.#lastUpdate = Date.now()
this.#persistOnHMR = persistOnHMR ?? false
}

/**
Expand All @@ -89,6 +99,12 @@ class CacheImpl<T = any> {
* @param value - New value / 新值
*/
hotUpdate(value: T) {
// If persistOnHMR is enabled, skip the update to preserve the current cached value.
// 如果启用了 persistOnHMR ,跳过更新以保留当前缓存值
if (this.#persistOnHMR) {
return
}

// Used for cases where repeated compilation generates random data via `mockjs` or `faker-js`,
// which may cause inconsistency in interface data across different mock files.
// Since compilation and loading time is necessarily much shorter than user modification intervals,
Expand All @@ -103,12 +119,43 @@ class CacheImpl<T = any> {
// reinitialize to the newly compiled data.
// 文件变更重新编译加载后,当两个初始化的数据不相等时,
// 重新初始化为新的编译后的数据
if (!deepEqual(value, this.#initialValue)) {
this.value = value
this.#initialValue = deepClone(value)
const hash = getHash(value)
if (this.#hash !== hash) {
this.value = deepClone(value)
this.#hash = hash
this.#lastUpdate = Date.now()
}
}

/**
* Set persistOnHMR
*
* 设置 persistOnHMR
*
* @param persistOnHMR - Whether to persist data on HMR / 热更新时是否保持数据
*/
setPersistOnHMR(persistOnHMR: boolean) {
// Once set to false, cannot be changed to true by subsequent calls
// 设置为 false 后,不能被后续调用改为 true
if (!this.#persistOnHMR)
this.#persistOnHMR = persistOnHMR
}
}

/**
* Options for defineMockData
*
* defineMockData 的选项
*/
export interface DefineMockDataOptions {
/**
* Whether to persist the data value on HMR (Hot Module Replacement).
*
* 热更新时是否保持数据值
*
* @default false
*/
persistOnHMR?: boolean
}

/**
Expand Down Expand Up @@ -152,15 +199,20 @@ export type MockData<T = any> = readonly [
* @template T - Type of mock data / Mock 数据的类型
* @param key - Unique key for mock data / Mock 数据的唯一键
* @param initialData - Initial data value / 初始数据值
* @param options - Options / 选项
* @returns MockData object with getter, setter, and value property / 带有 getter、setter 和 value 属性的 MockData 对象
*/
export function defineMockData<T = any>(
key: string,
initialData: T,
options?: DefineMockDataOptions,
): MockData<T> {
let cache = mockDataCache.get(key) as CacheImpl<T> | undefined
if (!cache) {
const newCache = new CacheImpl(initialData)
const newCache = new CacheImpl<T>(
initialData,
options?.persistOnHMR,
)
const existing = mockDataCache.get(key)
if (existing) {
cache = existing as CacheImpl<T>
Expand All @@ -170,6 +222,11 @@ export function defineMockData<T = any>(
cache = newCache
}
}
else {
// If cache already exists, update persistOnHMR if not already set
// 如果缓存已存在,且 persistOnHMR 尚未设置,则更新
cache.setPersistOnHMR(options?.persistOnHMR ?? false)
}

cache.hotUpdate(initialData)

Expand Down Expand Up @@ -200,3 +257,7 @@ export function defineMockData<T = any>(

return res
}

function getHash(data: any): string {
return createHash('sha256').update(JSON.stringify(data)).digest('hex')
}