From a11b51fc91e98c287b30088664f022fdcb8356f7 Mon Sep 17 00:00:00 2001
From: Bobbie Goede <bobbiegoede@gmail.com>
Date: Sun, 23 Mar 2025 22:13:02 +0100
Subject: [PATCH 1/2] feat: add auto hmr registration plugin to nuxt module

---
 packages/nuxt/src/auto-hmr-plugin.ts | 66 ++++++++++++++++++++++++++++
 packages/nuxt/src/module.ts          |  7 +++
 2 files changed, 73 insertions(+)
 create mode 100644 packages/nuxt/src/auto-hmr-plugin.ts

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<ModuleOptions> = defineNuxtModule<ModuleOptions>({
         addImportsDir(resolve(nuxt.options.rootDir, storeDir))
       }
     }
+
+    // Register automatic hmr code plugin - dev mode only
+    if (nuxt.options.dev) {
+      addVitePlugin(autoRegisterHMRPlugin(nuxt, { resolve }))
+    }
   },
 })
 

From 36f8a4b4c94c945c517cffdac2da5ec93b47f54d Mon Sep 17 00:00:00 2001
From: Bobbie Goede <bobbiegoede@gmail.com>
Date: Sun, 23 Mar 2025 22:13:26 +0100
Subject: [PATCH 2/2] chore: update playground to demonstrate plugin works

---
 packages/nuxt/playground/stores/counter.ts           | 4 ----
 packages/nuxt/playground/stores/nested/some-store.ts | 4 ----
 packages/nuxt/playground/stores/with-skip-hydrate.ts | 6 ------
 3 files changed, 14 deletions(-)

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)
-  )
-}