Skip to content

Commit beaf44a

Browse files
authored
feat: add persistOnHMR options for defineMockData, close #144 (#145)
* feat: add `persistOnHMR` options for `defineMockData`, close #144 * fix: typo
1 parent 8f29a25 commit beaf44a

10 files changed

Lines changed: 128 additions & 25 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pnpm docs:build
7373

7474
## Project Structure
7575

76-
```
76+
```txt
7777
vite-plugin-mock-server/
7878
├── pnpm-workspace.yaml # Monorepo workspace config
7979
├── vitest.config.ts # Test configuration (runs spec.ts in __tests__)

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,9 @@ To address this, the plugin offers a `defineMockData` function, which allows usi
726726
type defineMockData<T> = (
727727
key: string, // key
728728
initialData: T, // initial data
729+
options?: {
730+
persistOnHMR?: boolean // persist the data value on HMR
731+
} // options
729732
) => [getter, setter] & { value: T }
730733
```
731734

README.zh-CN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,9 @@ type Response = http.ServerResponse<http.IncomingMessage> & {
727727
type defineMockData<T> = (
728728
key: string, // 数据唯一标识符
729729
initialData: T, // 初始化数据
730+
options?: {
731+
persistOnHMR?: boolean // 是否在热更新时保持数据状态
732+
} // 可选配置
730733
) => [getter, setter] & { value: T }
731734
```
732735

docs/en/api/define-mock-data.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ function defineMockData<T = any>(
3131
- **Description**: Initial data value
3232
- **Required**: Yes
3333

34+
### options
35+
36+
- **Type**: `{ persistOnHMR?: boolean }`
37+
- **Description**: An options object used to configure the data sharing mechanism.
38+
- **Optional**: Yes
39+
- **Default Value**: `{}`
40+
- **persistOnHMR**: Whether to preserve the initial data during HMR. The default value is `false`, meaning whether data will be reinitialized during HMR.
41+
42+
By default, the plugin internally determines whether data needs to be reinitialized by comparing whether the initial data has changed.
43+
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.
44+
Therefore, manual configuration of the `persistOnHMR` option is required.
45+
3446
## Return Value
3547

3648
- **Type**: `MockData<T>`
@@ -155,10 +167,14 @@ interface Todo {
155167
createdAt: number
156168
}
157169
158-
const todos = defineMockData<Todo[]>('todos', [
159-
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
160-
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
161-
])
170+
const todos = defineMockData<Todo[]>(
171+
'todos',
172+
[
173+
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
174+
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
175+
],
176+
{ persistOnHMR: true }, // Ensure data is not reset during HMR, preserving existing data state
177+
)
162178
163179
export default defineMock([
164180
// Get todo list (supports filtering)
@@ -231,7 +247,7 @@ export default defineMock([
231247
`defineMockData` has built-in hot reload handling mechanism:
232248

233249
1. **Caching Mechanism**: Data is cached to ensure multiple Mock files access the same instance
234-
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
250+
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`).
235251
3. **Data Persistence**: Data state is preserved after file modification and recompilation
236252

237253
## Important Notes

docs/en/guide/define-mock-data.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# defineMockData(key, initialData)
1+
# defineMockData(key, initialData [, options])
22

33
Define shareable mock data.
44

docs/zh/api/define-mock-data.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
```ts
1414
function defineMockData<T = any>(
1515
key: string,
16-
initialData: T
16+
initialData: T,
17+
options?: {
18+
persistOnHMR?: boolean // 是否在 HMR 时保持初始数据
19+
}
1720
): MockData<T>
1821
```
1922

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

37+
### options
38+
39+
- **类型**: `{ persistOnHMR?: boolean }`
40+
- **描述**: 选项对象,用于配置数据共享机制
41+
- **可选**: 是
42+
- **默认值**: `{}`
43+
- **persistOnHMR**: 是否在 HMR 时保持初始数据,默认值为 `false`, 即在 HMR 时会不会重新初始化数据。
44+
45+
插件内部默认会通过比对初始数据是否发生变化来判断是否需要重新初始化数据。
46+
但是在使用 `mockjs``faker` 等库时,由于数据随机产生,插件无法直接判断数据是否发生变化,
47+
因此需要手动配置 `persistOnHMR` 选项。
48+
3449
## 返回值
3550

3651
- **类型**: `MockData<T>`
@@ -155,10 +170,14 @@ interface Todo {
155170
createdAt: number
156171
}
157172
158-
const todos = defineMockData<Todo[]>('todos', [
159-
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
160-
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
161-
])
173+
const todos = defineMockData<Todo[]>(
174+
'todos',
175+
[
176+
{ id: 1, text: 'Learn Vite', completed: false, createdAt: Date.now() },
177+
{ id: 2, text: 'Build Mock API', completed: true, createdAt: Date.now() }
178+
],
179+
{ persistOnHMR: true }, // 热更新时确保数据不被重置,保留已有的数据状态
180+
)
162181
163182
export default defineMock([
164183
// 获取待办列表(支持筛选)
@@ -231,7 +250,7 @@ export default defineMock([
231250
`defineMockData` 内置了热更新处理机制:
232251

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

237256
## 注意事项

docs/zh/guide/define-mock-data.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# defineMockData(key, initialData)
1+
# defineMockData(key, initialData [, options])
22

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

example/mock/data/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ export default defineMockData<Record<string, User>>('users', {
2727
username: mock.mock('@first()'),
2828
age: 20,
2929
},
30-
})
30+
}, { persistOnHMR: true })

vite-plugin-mock-dev-server/src/core/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export function resolvePluginOptions({
9090
const resolvedRecord = resolveRecordOptions(cwd, dir, record)
9191

9292
return {
93+
enabled: true,
9394
cwd,
9495
dir,
9596
include,

vite-plugin-mock-dev-server/src/helpers/defineMockData.ts

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
* 为此,插件提供了一种基于 memory 的数据共享机制。
1717
*/
1818

19-
import { deepClone, deepEqual, isFunction } from '@pengzhanbo/utils'
19+
import { createHash } from 'node:crypto'
20+
import { deepClone, isFunction } from '@pengzhanbo/utils'
2021

2122
/**
2223
* Mock data cache
@@ -55,11 +56,11 @@ class CacheImpl<T = any> {
5556
value: T
5657

5758
/**
58-
* Initial value backup, used to detect if initial data has changed
59+
* Initial value hash, used to detect if initial data has changed
5960
*
60-
* 初始化数据的备份,用于判断传入的初始化数据是否发生变更
61+
* 初始化数据的哈希值,用于判断传入的初始化数据是否发生变更
6162
*/
62-
#initialValue: T
63+
#hash: string
6364

6465
/**
6566
* Last update timestamp
@@ -68,17 +69,26 @@ class CacheImpl<T = any> {
6869
*/
6970
#lastUpdate: number
7071

72+
/**
73+
* Whether to persist data on HMR
74+
*
75+
* 热更新时是否保持数据
76+
*/
77+
#persistOnHMR: boolean
78+
7179
/**
7280
* Constructor
7381
*
7482
* 构造函数
7583
*
7684
* @param value - Initial value / 初始值
85+
* @param persistOnHMR - Whether to persist data on HMR / 热更新时是否保持数据
7786
*/
78-
constructor(value: T) {
87+
constructor(value: T, persistOnHMR?: boolean) {
7988
this.value = value
80-
this.#initialValue = deepClone(value)
89+
this.#hash = getHash(value)
8190
this.#lastUpdate = Date.now()
91+
this.#persistOnHMR = persistOnHMR ?? false
8292
}
8393

8494
/**
@@ -89,6 +99,12 @@ class CacheImpl<T = any> {
8999
* @param value - New value / 新值
90100
*/
91101
hotUpdate(value: T) {
102+
// If persistOnHMR is enabled, skip the update to preserve the current cached value.
103+
// 如果启用了 persistOnHMR ,跳过更新以保留当前缓存值
104+
if (this.#persistOnHMR) {
105+
return
106+
}
107+
92108
// Used for cases where repeated compilation generates random data via `mockjs` or `faker-js`,
93109
// which may cause inconsistency in interface data across different mock files.
94110
// Since compilation and loading time is necessarily much shorter than user modification intervals,
@@ -103,12 +119,43 @@ class CacheImpl<T = any> {
103119
// reinitialize to the newly compiled data.
104120
// 文件变更重新编译加载后,当两个初始化的数据不相等时,
105121
// 重新初始化为新的编译后的数据
106-
if (!deepEqual(value, this.#initialValue)) {
107-
this.value = value
108-
this.#initialValue = deepClone(value)
122+
const hash = getHash(value)
123+
if (this.#hash !== hash) {
124+
this.value = deepClone(value)
125+
this.#hash = hash
109126
this.#lastUpdate = Date.now()
110127
}
111128
}
129+
130+
/**
131+
* Set persistOnHMR
132+
*
133+
* 设置 persistOnHMR
134+
*
135+
* @param persistOnHMR - Whether to persist data on HMR / 热更新时是否保持数据
136+
*/
137+
setPersistOnHMR(persistOnHMR: boolean) {
138+
// Once set to false, cannot be changed to true by subsequent calls
139+
// 设置为 false 后,不能被后续调用改为 true
140+
if (!this.#persistOnHMR)
141+
this.#persistOnHMR = persistOnHMR
142+
}
143+
}
144+
145+
/**
146+
* Options for defineMockData
147+
*
148+
* defineMockData 的选项
149+
*/
150+
export interface DefineMockDataOptions {
151+
/**
152+
* Whether to persist the data value on HMR (Hot Module Replacement).
153+
*
154+
* 热更新时是否保持数据值
155+
*
156+
* @default false
157+
*/
158+
persistOnHMR?: boolean
112159
}
113160

114161
/**
@@ -152,15 +199,20 @@ export type MockData<T = any> = readonly [
152199
* @template T - Type of mock data / Mock 数据的类型
153200
* @param key - Unique key for mock data / Mock 数据的唯一键
154201
* @param initialData - Initial data value / 初始数据值
202+
* @param options - Options / 选项
155203
* @returns MockData object with getter, setter, and value property / 带有 getter、setter 和 value 属性的 MockData 对象
156204
*/
157205
export function defineMockData<T = any>(
158206
key: string,
159207
initialData: T,
208+
options?: DefineMockDataOptions,
160209
): MockData<T> {
161210
let cache = mockDataCache.get(key) as CacheImpl<T> | undefined
162211
if (!cache) {
163-
const newCache = new CacheImpl(initialData)
212+
const newCache = new CacheImpl<T>(
213+
initialData,
214+
options?.persistOnHMR,
215+
)
164216
const existing = mockDataCache.get(key)
165217
if (existing) {
166218
cache = existing as CacheImpl<T>
@@ -170,6 +222,11 @@ export function defineMockData<T = any>(
170222
cache = newCache
171223
}
172224
}
225+
else {
226+
// If cache already exists, update persistOnHMR if not already set
227+
// 如果缓存已存在,且 persistOnHMR 尚未设置,则更新
228+
cache.setPersistOnHMR(options?.persistOnHMR ?? false)
229+
}
173230

174231
cache.hotUpdate(initialData)
175232

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

201258
return res
202259
}
260+
261+
function getHash(data: any): string {
262+
return createHash('sha256').update(JSON.stringify(data)).digest('hex')
263+
}

0 commit comments

Comments
 (0)