]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat(nuxt): automatic HMR code (vite only) (#2954)
authorBobbie Goede <bobbiegoede@gmail.com>
Tue, 4 Nov 2025 14:47:10 +0000 (15:47 +0100)
committerGitHub <noreply@github.com>
Tue, 4 Nov 2025 14:47:10 +0000 (15:47 +0100)
Co-authored-by: Eduardo San Martin Morote <posva13@gmail.com>
packages/nuxt/playground/pages/index.vue
packages/nuxt/playground/stores/counter.ts
packages/nuxt/playground/stores/nested/some-store.ts
packages/nuxt/playground/stores/with-skip-hydrate.ts
packages/nuxt/src/auto-hmr-plugin.ts [new file with mode: 0644]
packages/nuxt/src/module.ts

index 77cbee7d12fe39b313956df9e20005d979a917a7..6ea4d583956ba920aec4f3bead5594c47d9b31ba 100644 (file)
@@ -16,8 +16,11 @@ if (import.meta.server) {
 
 <template>
   <div>
-    <p>Count: {{ counter.$state.count }}</p>
+    <p>Count: {{ counter.count }} x 2 = {{ counter.double }}</p>
     <button @click="counter.increment()">+</button>
+    <pre>{{ counter.$state }}</pre>
+
+    <hr />
 
     <p>Layer store: {{ layerStore.count }}</p>
   </div>
index 043de8610dbd805d5f740c869325900fe17570b7..13e26be1364d1fe88c2ac04201d4136fcfb31626 100644 (file)
@@ -17,10 +17,6 @@ export const useCounter = defineStore('counter', {
     },
   },
   getters: {
-    getCount: (state) => state.count,
+    double: (state) => state.count * 2,
   },
 })
-
-if (import.meta.hot) {
-  import.meta.hot.accept(acceptHMRUpdate(useCounter, import.meta.hot))
-}
index 9e40b949e8ff520ac80ab627adf015544025bad6..d680872ef39b8e318e72684a448706b4b032bf1a 100644 (file)
@@ -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))
-}
index 3cbbc31bc29f5d3f73a520aa778352161bda1964..46463ad70519dc661b13e45455fcb51a13367297 100644 (file)
@@ -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 (file)
index 0000000..10a7bf0
--- /dev/null
@@ -0,0 +1,64 @@
+import type { VariableDeclarator } from 'estree'
+import type { Nuxt } from 'nuxt/schema'
+import type { Plugin } from 'vite'
+
+function getStoreDeclaration(nodes?: VariableDeclarator[]) {
+  return nodes?.find(
+    (x) =>
+      x.init?.type === 'CallExpression' &&
+      x.init.callee.type === 'Identifier' &&
+      x.init.callee.name === 'defineStore'
+  )
+}
+
+function nameFromDeclaration(node?: VariableDeclarator) {
+  return node?.id.type === 'Identifier' && node.id.name
+}
+
+export function autoRegisterHMRPlugin(rootDir: string) {
+  return {
+    name: 'pinia:auto-hmr-registration',
+
+    transform(code, id) {
+      if (id.startsWith('\x00')) return
+      if (!id.startsWith(rootDir)) 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: [
+                `import { acceptHMRUpdate } from 'pinia'`,
+                code,
+                'if (import.meta.hot) {',
+                `  import.meta.hot.accept(acceptHMRUpdate(${storeName}, import.meta.hot))`,
+                '}',
+              ].join('\n'),
+            }
+          }
+        }
+      }
+    },
+  } satisfies Plugin
+}
index 8991050789e7b4804884827a0b3cfe5d2aa8c008..a04596ba0b6724644edf2fca70adf98e15651fdc 100644 (file)
@@ -8,9 +8,11 @@ import {
   createResolver,
   addImportsDir,
   getLayerDirectories,
+  addVitePlugin,
 } from '@nuxt/kit'
 import type { NuxtModule } from '@nuxt/schema'
 import { fileURLToPath } from 'node:url'
+import { autoRegisterHMRPlugin } from './auto-hmr-plugin'
 
 export interface ModuleOptions {
   /**
@@ -82,6 +84,11 @@ const module: NuxtModule<ModuleOptions> = defineNuxtModule<ModuleOptions>({
         }
       }
     }
+
+    // Register automatic hmr code plugin - dev mode only
+    if (nuxt.options.dev) {
+      addVitePlugin(autoRegisterHMRPlugin(resolve(nuxt.options.rootDir)))
+    }
   },
 })