diff --git a/packages/nuxt/playground/stores/counter.ts b/packages/nuxt/playground/stores/counter.ts index 043de8610d..08103e71e9 100644 --- a/packages/nuxt/playground/stores/counter.ts +++ b/packages/nuxt/playground/stores/counter.ts @@ -20,7 +20,3 @@ export const useCounter = defineStore('counter', { getCount: (state) => state.count, }, }) - -if (import.meta.hot) { - import.meta.hot.accept(acceptHMRUpdate(useCounter, import.meta.hot)) -} diff --git a/packages/nuxt/playground/stores/nested/some-store.ts b/packages/nuxt/playground/stores/nested/some-store.ts index b5cf2c1c9b..9636258f18 100644 --- a/packages/nuxt/playground/stores/nested/some-store.ts +++ b/packages/nuxt/playground/stores/nested/some-store.ts @@ -2,7 +2,3 @@ export const useSomeStoreStore = defineStore('some-store', () => { console.log('I was defined within a nested store directory') return {} }) - -if (import.meta.hot) { - import.meta.hot.accept(acceptHMRUpdate(useSomeStoreStore, import.meta.hot)) -} diff --git a/packages/nuxt/playground/stores/with-skip-hydrate.ts b/packages/nuxt/playground/stores/with-skip-hydrate.ts index 3cbbc31bc2..46463ad705 100644 --- a/packages/nuxt/playground/stores/with-skip-hydrate.ts +++ b/packages/nuxt/playground/stores/with-skip-hydrate.ts @@ -8,9 +8,3 @@ export const useWithSkipHydrateStore = defineStore('with-skip-hydrate', () => { ) return { skipped } }) - -if (import.meta.hot) { - import.meta.hot.accept( - acceptHMRUpdate(useWithSkipHydrateStore, import.meta.hot) - ) -} diff --git a/packages/nuxt/src/auto-hmr-plugin.ts b/packages/nuxt/src/auto-hmr-plugin.ts new file mode 100644 index 0000000000..5231ee31ff --- /dev/null +++ b/packages/nuxt/src/auto-hmr-plugin.ts @@ -0,0 +1,66 @@ +import type { Nuxt } from 'nuxt/schema' + +function getStoreDeclaration(nodes?: import('estree').VariableDeclarator[]) { + return nodes?.find( + (x) => + x.init?.type === 'CallExpression' && + x.init.callee.type === 'Identifier' && + x.init.callee.name === 'defineStore' + ) +} + +function nameFromDeclaration(node?: import('estree').VariableDeclarator) { + return node?.id.type === 'Identifier' && node.id.name +} + +export function autoRegisterHMRPlugin( + nuxt: Nuxt, + { resolve }: { resolve: (...path: string[]) => string } +) { + const projectBasePath = resolve(nuxt.options.rootDir) + + return { + name: 'pinia:auto-hmr-registration', + + transform(code, id) { + if (id.startsWith('\x00')) return + if (!id.startsWith(projectBasePath)) return + if (!code.includes('defineStore') || code.includes('acceptHMRUpdate')) { + return + } + + const ast = this.parse(code) + + // walk top-level nodes + for (const n of ast.body) { + if ( + n.type === 'VariableDeclaration' || + n.type === 'ExportNamedDeclaration' + ) { + // find export or variable declaration that uses `defineStore` + const storeDeclaration = getStoreDeclaration( + n.type === 'VariableDeclaration' + ? n.declarations + : n.declaration?.type === 'VariableDeclaration' + ? n.declaration?.declarations + : undefined + ) + + // retrieve the variable name + const storeName = nameFromDeclaration(storeDeclaration) + if (storeName) { + // append HMR code + return { + code: [ + code, + 'if (import.meta.hot) {', + ` import.meta.hot.accept(acceptHMRUpdate(${storeName}, import.meta.hot))`, + '}', + ].join('\n'), + } + } + } + } + }, + } satisfies import('vite').Plugin +} diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index f81f9779ef..38faada489 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -7,9 +7,11 @@ import { addImports, createResolver, addImportsDir, + addVitePlugin, } from '@nuxt/kit' import type { NuxtModule } from '@nuxt/schema' import { fileURLToPath } from 'node:url' +import { autoRegisterHMRPlugin } from './auto-hmr-plugin' export interface ModuleOptions { /** @@ -76,6 +78,11 @@ const module: NuxtModule = defineNuxtModule({ addImportsDir(resolve(nuxt.options.rootDir, storeDir)) } } + + // Register automatic hmr code plugin - dev mode only + if (nuxt.options.dev) { + addVitePlugin(autoRegisterHMRPlugin(nuxt, { resolve })) + } }, })