diff --git a/CLAUDE.md b/CLAUDE.md index 4e3493e..cbf9a23 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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__) diff --git a/README.md b/README.md index 19d6b43..5e927f7 100644 --- a/README.md +++ b/README.md @@ -726,6 +726,9 @@ To address this, the plugin offers a `defineMockData` function, which allows usi type defineMockData = ( key: string, // key initialData: T, // initial data + options?: { + persistOnHMR?: boolean // persist the data value on HMR + } // options ) => [getter, setter] & { value: T } ``` diff --git a/README.zh-CN.md b/README.zh-CN.md index 41e97c9..3f3fbc9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -727,6 +727,9 @@ type Response = http.ServerResponse & { type defineMockData = ( key: string, // 数据唯一标识符 initialData: T, // 初始化数据 + options?: { + persistOnHMR?: boolean // 是否在热更新时保持数据状态 + } // 可选配置 ) => [getter, setter] & { value: T } ``` diff --git a/docs/en/api/define-mock-data.md b/docs/en/api/define-mock-data.md index a60943f..0df83b8 100644 --- a/docs/en/api/define-mock-data.md +++ b/docs/en/api/define-mock-data.md @@ -31,6 +31,18 @@ function defineMockData( - **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` @@ -155,10 +167,14 @@ interface Todo { createdAt: number } -const todos = defineMockData('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( + '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) @@ -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 diff --git a/docs/en/guide/define-mock-data.md b/docs/en/guide/define-mock-data.md index 1d543de..03a611c 100644 --- a/docs/en/guide/define-mock-data.md +++ b/docs/en/guide/define-mock-data.md @@ -1,4 +1,4 @@ -# defineMockData(key, initialData) +# defineMockData(key, initialData [, options]) Define shareable mock data. diff --git a/docs/zh/api/define-mock-data.md b/docs/zh/api/define-mock-data.md index 5e3bf84..d7d09d2 100644 --- a/docs/zh/api/define-mock-data.md +++ b/docs/zh/api/define-mock-data.md @@ -13,7 +13,10 @@ ```ts function defineMockData( key: string, - initialData: T + initialData: T, + options?: { + persistOnHMR?: boolean // 是否在 HMR 时保持初始数据 + } ): MockData ``` @@ -31,6 +34,18 @@ function defineMockData( - **描述**: 初始数据值 - **必填**: 是 +### options + +- **类型**: `{ persistOnHMR?: boolean }` +- **描述**: 选项对象,用于配置数据共享机制 +- **可选**: 是 +- **默认值**: `{}` + - **persistOnHMR**: 是否在 HMR 时保持初始数据,默认值为 `false`, 即在 HMR 时会不会重新初始化数据。 + +插件内部默认会通过比对初始数据是否发生变化来判断是否需要重新初始化数据。 +但是在使用 `mockjs` 或 `faker` 等库时,由于数据随机产生,插件无法直接判断数据是否发生变化, +因此需要手动配置 `persistOnHMR` 选项。 + ## 返回值 - **类型**: `MockData` @@ -155,10 +170,14 @@ interface Todo { createdAt: number } -const todos = defineMockData('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( + '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([ // 获取待办列表(支持筛选) @@ -231,7 +250,7 @@ export default defineMock([ `defineMockData` 内置了热更新处理机制: 1. **缓存机制**: 数据会被缓存,确保多个 Mock 文件访问的是同一实例 -2. **热更新保护**: 在短时间内的重复编译(如使用 `mockjs` 或 `faker-js` 生成随机数据)不会导致数据重置 +2. **热更新保护**: 热更新时,初始数据未发生变化时,不会重置数据 (但如使用 `mockjs` 或 `faker-js` 生成随机数据,需要手动配置 `persistOnHMR` 为 `true`) 3. **数据持久**: 文件修改后的重新编译会保留已有的数据状态 ## 注意事项 diff --git a/docs/zh/guide/define-mock-data.md b/docs/zh/guide/define-mock-data.md index f40ce34..19a0f01 100644 --- a/docs/zh/guide/define-mock-data.md +++ b/docs/zh/guide/define-mock-data.md @@ -1,4 +1,4 @@ -# defineMockData(key, initialData) +# defineMockData(key, initialData [, options]) 定义可共享的 mock data,用于在多个 Mock 文件之间共享状态。 diff --git a/example/mock/data/user.ts b/example/mock/data/user.ts index 1b81663..4e359e9 100644 --- a/example/mock/data/user.ts +++ b/example/mock/data/user.ts @@ -27,4 +27,4 @@ export default defineMockData>('users', { username: mock.mock('@first()'), age: 20, }, -}) +}, { persistOnHMR: true }) diff --git a/vite-plugin-mock-dev-server/src/core/options.ts b/vite-plugin-mock-dev-server/src/core/options.ts index e35064d..e1386e9 100644 --- a/vite-plugin-mock-dev-server/src/core/options.ts +++ b/vite-plugin-mock-dev-server/src/core/options.ts @@ -90,6 +90,7 @@ export function resolvePluginOptions({ const resolvedRecord = resolveRecordOptions(cwd, dir, record) return { + enabled: true, cwd, dir, include, diff --git a/vite-plugin-mock-dev-server/src/helpers/defineMockData.ts b/vite-plugin-mock-dev-server/src/helpers/defineMockData.ts index f84df40..ab835f4 100644 --- a/vite-plugin-mock-dev-server/src/helpers/defineMockData.ts +++ b/vite-plugin-mock-dev-server/src/helpers/defineMockData.ts @@ -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 @@ -55,11 +56,11 @@ class CacheImpl { 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 @@ -68,17 +69,26 @@ class CacheImpl { */ #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 } /** @@ -89,6 +99,12 @@ class CacheImpl { * @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, @@ -103,12 +119,43 @@ class CacheImpl { // 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 } /** @@ -152,15 +199,20 @@ export type MockData = 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( key: string, initialData: T, + options?: DefineMockDataOptions, ): MockData { let cache = mockDataCache.get(key) as CacheImpl | undefined if (!cache) { - const newCache = new CacheImpl(initialData) + const newCache = new CacheImpl( + initialData, + options?.persistOnHMR, + ) const existing = mockDataCache.get(key) if (existing) { cache = existing as CacheImpl @@ -170,6 +222,11 @@ export function defineMockData( cache = newCache } } + else { + // If cache already exists, update persistOnHMR if not already set + // 如果缓存已存在,且 persistOnHMR 尚未设置,则更新 + cache.setPersistOnHMR(options?.persistOnHMR ?? false) + } cache.hotUpdate(initialData) @@ -200,3 +257,7 @@ export function defineMockData( return res } + +function getHash(data: any): string { + return createHash('sha256').update(JSON.stringify(data)).digest('hex') +}