diff --git a/src/auto-hmr/ast.ts b/src/auto-hmr/ast.ts new file mode 100644 index 000000000..8617fa7d4 --- /dev/null +++ b/src/auto-hmr/ast.ts @@ -0,0 +1,75 @@ +import type { AstNode } from 'rollup' +import type { VariableDeclarator, ImportDeclaration } from 'estree' + +export function nameFromDeclaration(node?: VariableDeclarator) { + return node?.id.type === 'Identifier' ? node.id.name : '' +} + +export function getRouterDeclaration(nodes?: VariableDeclarator[]) { + return nodes?.find( + (x) => + x.init?.type === 'CallExpression' && + x.init.callee.type === 'Identifier' && + (x.init.callee.name === 'createRouter' || + x.init.callee.name === 'experimental_createRouter') + ) +} + +export function getHandleHotUpdateDeclaration(node?: ImportDeclaration, modulePath?: string) { + return ( + node?.type === 'ImportDeclaration' && + node.source.value === modulePath && + node.specifiers.some( + (x) => + x.type === 'ImportSpecifier' && + x.imported.type === 'Identifier' && + x.imported.name === 'handleHotUpdate' + ) + ) +} + +export function hasHandleHotUpdateCall(ast: AstNode) { + const visited = new WeakSet() + + function traverse(node: any) { + if (!node || typeof node !== 'object' || visited.has(node)) return false + visited.add(node) + + if ( + node.type === 'CallExpression' && + node.callee.type === 'Identifier' && + node.callee.name === 'handleHotUpdate' + ) { + return true + } + + // e.g.: autoRouter.handleHotUpdate() + if ( + node.type === 'CallExpression' && + node.callee.type === 'MemberExpression' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'handleHotUpdate' + ) { + return true + } + + if (typeof node !== 'object') return false + + for (const key in node) { + if (key === 'type' || key === 'loc' || key === 'range') continue + + const child = node[key] + if (Array.isArray(child)) { + for (const item of child) { + if (traverse(item)) return true + } + } else if (typeof child === 'object' && child !== null) { + if (traverse(child)) return true + } + } + + return false + } + + return traverse(ast); +} diff --git a/src/auto-hmr/auto-hmr.spec.ts b/src/auto-hmr/auto-hmr.spec.ts new file mode 100644 index 000000000..3c6593ca0 --- /dev/null +++ b/src/auto-hmr/auto-hmr.spec.ts @@ -0,0 +1,754 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest' +import { createAutoHmrPlugin, DEFAULT_AUTO_HMR_OPTIONS } from './index' + +// Mock the parse function +const mockParse = vi.fn() + +function createMockTransformContext() { + return { + parse: mockParse, + } as any +} + +function callTransform( + plugin: ReturnType, + code: string, + id: string, + ctx: ReturnType +) { + const transform = plugin.transform as any + return transform.call(ctx, code, id) +} + +describe('auto-hmr', () => { + beforeEach(() => { + mockParse.mockClear() + }) + describe('file filtering', () => { + it('should process files matching default filter pattern', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const id1 = '/path/to/router.ts' + const id2 = '/path/to/router/index.ts' + const id3 = '/path/to/router.js' + const id4 = '/path/to/router/index.js' + + // Should match router.ts + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result1 = callTransform(plugin, code, id1, ctx) + expect(result1).toBeDefined() + + const result2 = callTransform(plugin, code, id2, ctx) + expect(result2).toBeDefined() + + const result3 = callTransform(plugin, code, id3, ctx) + expect(result3).toBeDefined() + + const result4 = callTransform(plugin, code, id4, ctx) + expect(result4).toBeDefined() + }) + + it('should not process files not matching filter pattern', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const id = '/path/to/other.ts' + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeUndefined() + }) + + it('should respect custom filter include pattern', () => { + const plugin = createAutoHmrPlugin({ + filter: { + include: ['**/custom-router.{js,ts}'], + exclude: [], + }, + }) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const matchingId = '/path/to/custom-router.ts' + const nonMatchingId = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result1 = callTransform(plugin, code, matchingId, ctx) + expect(result1).toBeDefined() + + const result2 = callTransform(plugin, code, nonMatchingId, ctx) + expect(result2).toBeUndefined() + }) + + it('should respect custom filter exclude pattern', () => { + const plugin = createAutoHmrPlugin({ + filter: { + include: ['**/router.{js,ts}'], + exclude: ['**/router.test.{js,ts}'], + }, + }) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const excludedId = '/path/to/router.test.ts' + const includedId = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result1 = callTransform(plugin, code, excludedId, ctx) + expect(result1).toBeUndefined() + + const result2 = callTransform(plugin, code, includedId, ctx) + expect(result2).toBeDefined() + }) + + it('should skip virtual modules (starting with \\x00)', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const virtualId = '\x00virtual-module' + + const result = callTransform(plugin, code, virtualId, ctx) + expect(result).toBeUndefined() + expect(mockParse).not.toHaveBeenCalled() + }) + }) + + describe('createRouter detection', () => { + it('should process files with createRouter call', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('handleHotUpdate') + }) + + it('should process files with experimental_createRouter call', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = experimental_createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'experimental_createRouter', + }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('handleHotUpdate') + }) + + it('should not process files without createRouter call', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + // Use a file that matches the filter but doesn't have createRouter + const code = `const router = somethingElse({})` + const id = '/path/to/router.ts' + + const result = callTransform(plugin, code, id, ctx) + // Should return undefined because regex doesn't match createRouter + expect(result).toBeUndefined() + // Parse should not be called because regex check happens before parse + expect(mockParse).not.toHaveBeenCalled() + }) + }) + + describe('router declaration detection', () => { + it('should detect router from const declaration', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('handleHotUpdate(router)') + }) + + it('should detect router from export const declaration', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `export const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'ExportNamedDeclaration', + declaration: { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('handleHotUpdate(router)') + }) + + it('should handle multiple declarations and find router', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const other = 1; const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'other' }, + init: { type: 'Literal', value: 1 }, + }, + ], + }, + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('handleHotUpdate(router)') + }) + + it('should not process if no router declaration found', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const something = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'something' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + // This should still work because the regex matches createRouter + // but we need to check if routerName is found + const result = callTransform(plugin, code, id, ctx) + // The code has createRouter but the variable name is 'something', not 'router' + // So it should still add handleHotUpdate but with 'something' + expect(result).toBeDefined() + }) + }) + + describe('import injection', () => { + it('should add handleHotUpdate import if not present', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain( + `import { handleHotUpdate } from '${DEFAULT_AUTO_HMR_OPTIONS.modulePath}'` + ) + expect(result?.code).toContain('handleHotUpdate(router)') + }) + + it('should not add import if already present', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `import { handleHotUpdate } from 'vue-router/auto-routes' +const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'ImportDeclaration', + source: { value: 'vue-router/auto-routes' }, + specifiers: [ + { + type: 'ImportSpecifier', + imported: { type: 'Identifier', name: 'handleHotUpdate' }, + }, + ], + }, + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + // Should not duplicate the import + const importCount = (result?.code.match(/import.*handleHotUpdate/g) || []) + .length + expect(importCount).toBe(1) + }) + + it('should use custom modulePath when provided', () => { + const customModulePath = 'custom-module-path' + const plugin = createAutoHmrPlugin({ modulePath: customModulePath }) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain( + `import { handleHotUpdate } from '${customModulePath}'` + ) + }) + }) + + describe('handleHotUpdate call injection', () => { + it('should add handleHotUpdate call if not present', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('handleHotUpdate(router)') + }) + + it('should not add call if handleHotUpdate is already called', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({}) +handleHotUpdate(router)` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'handleHotUpdate' }, + }, + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + // Should not duplicate the call + const callCount = (result?.code.match(/handleHotUpdate\(router\)/g) || []) + .length + expect(callCount).toBe(1) + }) + + it('should detect handleHotUpdate call as member expression', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const router = createRouter({}) +autoRouter.handleHotUpdate(router)` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { type: 'Identifier', name: 'autoRouter' }, + property: { type: 'Identifier', name: 'handleHotUpdate' }, + }, + }, + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + // Should not add another call + const callCount = (result?.code.match(/handleHotUpdate/g) || []).length + // Should have the import and the existing call + expect(callCount).toBeGreaterThanOrEqual(1) + }) + }) + + describe('complete transformation', () => { + it('should transform complete router file correctly', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `import { createRouter, createWebHistory } from 'vue-router' +import { routes } from 'vue-router/auto-routes' + +export const router = createRouter({ + history: createWebHistory(), + routes, +})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'ImportDeclaration', + source: { value: 'vue-router' }, + specifiers: [], + }, + { + type: 'ImportDeclaration', + source: { value: 'vue-router/auto-routes' }, + specifiers: [], + }, + { + type: 'ExportNamedDeclaration', + declaration: { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain( + `import { handleHotUpdate } from '${DEFAULT_AUTO_HMR_OPTIONS.modulePath}'` + ) + expect(result?.code).toContain('handleHotUpdate(router)') + expect(result?.code).toContain( + "import { createRouter, createWebHistory } from 'vue-router'" + ) + }) + + it('should preserve original code structure', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const originalCode = `const router = createRouter({}) +const other = 123` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'router' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'other' }, + init: { type: 'Literal', value: 123 }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, originalCode, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('const router = createRouter({})') + expect(result?.code).toContain('const other = 123') + }) + }) + + describe('edge cases', () => { + it('should handle empty code', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `` + const id = '/path/to/router.ts' + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeUndefined() + }) + + it('should handle code without createRouter', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + // Use a file that matches the filter but doesn't have createRouter + const code = `const something = 123` + const id = '/path/to/router.ts' + + const result = callTransform(plugin, code, id, ctx) + // Should return undefined because regex doesn't match createRouter + expect(result).toBeUndefined() + // Parse should not be called because regex check happens before parse + expect(mockParse).not.toHaveBeenCalled() + }) + + it('should handle different router variable names', () => { + const plugin = createAutoHmrPlugin({}) + const ctx = createMockTransformContext() + + const code = `const myRouter = createRouter({})` + const id = '/path/to/router.ts' + + mockParse.mockReturnValue({ + body: [ + { + type: 'VariableDeclaration', + declarations: [ + { + id: { type: 'Identifier', name: 'myRouter' }, + init: { + type: 'CallExpression', + callee: { type: 'Identifier', name: 'createRouter' }, + }, + }, + ], + }, + ], + }) + + const result = callTransform(plugin, code, id, ctx) + expect(result).toBeDefined() + expect(result?.code).toContain('handleHotUpdate(myRouter)') + }) + }) +}) diff --git a/src/auto-hmr/index.ts b/src/auto-hmr/index.ts new file mode 100644 index 000000000..d791da30d --- /dev/null +++ b/src/auto-hmr/index.ts @@ -0,0 +1,116 @@ +import type { VariableDeclarator } from 'estree' +import { + nameFromDeclaration, + getRouterDeclaration, + getHandleHotUpdateDeclaration, + hasHandleHotUpdateCall, +} from './ast' +import { StringFilter, FilterPattern, type UnpluginOptions } from 'unplugin' +import { createFilter } from 'unplugin-utils' + +export interface AutoHmrOptions { + /** + * Whether to enable auto HMR for Vue Router. + * @default `true` + */ + enabled?: boolean + + /** + * Filter to determine which files to process. + */ + filter?: Exclude + + /** + * Name of the module to import the handleHotUpdate function from. + * @default `'vue-router/auto-routes'` + */ + modulePath?: string +} + +export const DEFAULT_AUTO_HMR_OPTIONS = { + enabled: true, + modulePath: 'vue-router/auto-routes', + filter: { + include: ['**/router.{js,ts}', '**/router/index.{js,ts}'], + exclude: [], + }, +} satisfies AutoHmrOptions + +export function createAutoHmrPlugin({ + filter, + modulePath = DEFAULT_AUTO_HMR_OPTIONS.modulePath, +}: AutoHmrOptions): UnpluginOptions { + const hasCreateRouterFnCallRegex = + /\w+\s*=\s*(?:experimental_)?createRouter\(/ + + const shouldProcessId = createFilter( + filter?.include ?? DEFAULT_AUTO_HMR_OPTIONS.filter.include, + filter?.exclude ?? DEFAULT_AUTO_HMR_OPTIONS.filter.exclude + ) + + return { + name: 'unplugin-vue-router-auto-hmr', + enforce: 'post', + transform(code, id) { + if (id.startsWith('\x00')) return + + if (!shouldProcessId(id)) return + + if (!hasCreateRouterFnCallRegex.test(code)) { + return + } + + const ast = this.parse(code) + + let isImported = false + let routerName: string | undefined + let routerDeclaration: VariableDeclarator | undefined + + // @ts-expect-error + for (const node of ast.body) { + if ( + node.type === 'ExportNamedDeclaration' || + node.type === 'VariableDeclaration' + ) { + if (!routerName) { + routerDeclaration = getRouterDeclaration( + node.type === 'VariableDeclaration' + ? node.declarations + : node.declaration?.type === 'VariableDeclaration' + ? node.declaration?.declarations + : undefined + ) + + routerName = nameFromDeclaration(routerDeclaration) + } + } else if (node.type === 'ImportDeclaration') { + isImported ||= getHandleHotUpdateDeclaration(node, modulePath) + } + } + + if (routerName) { + const isCalledHandleHotUpdate = hasHandleHotUpdateCall(ast) + + const handleHotUpdateCode = [code] + + // add import if not imported + if (!isImported) { + handleHotUpdateCode.unshift( + `import { handleHotUpdate } from '${modulePath}'` + ) + } + + // add handleHotUpdate call if not called + if (!isCalledHandleHotUpdate) { + handleHotUpdateCode.push(`handleHotUpdate(${routerName})`) + } + + return { + code: handleHotUpdateCode.join('\n'), + } + } + + return undefined + }, + } +} diff --git a/src/index.ts b/src/index.ts index 262b3b089..893f4b1da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ import { createViteContext } from './core/vite' import { join } from 'pathe' import { appendExtensionListToPattern } from './core/utils' import { createAutoExportPlugin } from './data-loaders/auto-exports' +import { createAutoHmrPlugin } from './auto-hmr' export type * from './types' @@ -168,6 +169,12 @@ export default createUnplugin((opt = {}, _meta) => { ) } + // If the autoHmr configuration item is not configured or only filter is set, + // it will also be regarded as enabled. + if (options.autoHmr?.enabled ?? true) { + plugins.push(createAutoHmrPlugin(options.autoHmr)) + } + return plugins }) diff --git a/src/options.ts b/src/options.ts index cbb46691f..8aba83ee8 100644 --- a/src/options.ts +++ b/src/options.ts @@ -5,6 +5,7 @@ import { resolve } from 'pathe' import { EditableTreeNode } from './core/extendRoutes' import { type ParseSegmentOptions } from './core/treeNodeValue' import { type _Awaitable } from './utils' +import { DEFAULT_AUTO_HMR_OPTIONS, type AutoHmrOptions } from './auto-hmr' /** * Options for a routes folder. @@ -215,6 +216,11 @@ export interface Options { */ watch?: boolean + /** + * Whether to enable auto HMR for Vue Router. + */ + autoHmr?: AutoHmrOptions + /** * Experimental options. **Warning**: these can change or be removed at any time, even it patch releases. Keep an eye * on the Changelog. @@ -266,6 +272,7 @@ export const DEFAULT_OPTIONS = { }, watch: !process.env.CI, experimental: {}, + autoHmr: DEFAULT_AUTO_HMR_OPTIONS, } satisfies Options export interface ServerContext {