]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat(hmr): skip arrays
authorEduardo San Martin Morote <posva13@gmail.com>
Mon, 19 Jul 2021 14:50:49 +0000 (16:50 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Mon, 19 Jul 2021 14:50:49 +0000 (16:50 +0200)
__tests__/hmr.spec.ts
src/hmr.ts
src/store.ts

index 68f083901290f262dfcc38734bac58ba8e17723e..b87136a00aab51326c29ea03cb0d7f821787b739 100644 (file)
@@ -18,6 +18,13 @@ describe('HMR', () => {
     id: 'main',
     state: () => ({
       n: 0,
+      arr: [],
+      nestedArr: {
+        arr: [],
+      },
+      nested: {
+        a: 'a',
+      },
     }),
 
     actions: {
@@ -63,25 +70,62 @@ describe('HMR', () => {
       expect(store.other).toBe('new')
     })
 
-    it('adds new state properties', () => {
+    it('patches nested objects', () => {
       const useStore = defineStore(baseOptions)
       const store: any = useStore()
-      store.n++
 
       // simulate a hmr
       defineStore({
         ...baseOptions,
-        state: () => ({ newOne: 'hey', n: 0 }),
+        state: () => ({ nested: { a: 'b', b: 'b' } }),
       })(null, store)
 
-      expect(store.$state).toEqual({ n: 1, newOne: 'hey' })
-      expect(store.n).toBe(1)
-      expect(store.newOne).toBe('hey')
+      expect(store.$state).toEqual({ nested: { a: 'a', b: 'b' } })
 
       defineStore({
         ...baseOptions,
-        state: () => ({ other: 'new', n: 0 }),
+        state: () => ({ nested: { b: 'c' } }),
+      })(null, store)
+      // removes the nested a
+      expect(store.$state).toEqual({ nested: { b: 'b' } })
+    })
+
+    it('skips arrays', () => {
+      const useStore = defineStore(baseOptions)
+      const store: any = useStore()
+
+      // simulate a hmr
+      defineStore({
+        ...baseOptions,
+        state: () => ({ arr: [2] }),
+      })(null, store)
+
+      expect(store.$state).toEqual({ arr: [] })
+
+      defineStore({
+        ...baseOptions,
+        state: () => ({ arr: [1] }),
+      })(null, store)
+      expect(store.$state).toEqual({ arr: [] })
+    })
+
+    it('skips nested arrays', () => {
+      const useStore = defineStore(baseOptions)
+      const store: any = useStore()
+
+      // simulate a hmr
+      defineStore({
+        ...baseOptions,
+        state: () => ({ nestedArr: { arr: [2] } }),
+      })(null, store)
+
+      expect(store.$state).toEqual({ nestedArr: { arr: [] } })
+
+      defineStore({
+        ...baseOptions,
+        state: () => ({ nestedArr: { arr: [1] } }),
       })(null, store)
+      expect(store.$state).toEqual({ nestedArr: { arr: [] } })
     })
 
     it('keeps state reactive', () => {
index ecd1cf1ad2041e6ba920f0f725bab79e0dd9b3e0..68b37b97ab90f1420366fbafacefb9cd2029bd68 100644 (file)
@@ -1,10 +1,42 @@
+import { isRef, isReactive } from 'vue'
 import { Pinia } from './rootStore'
-import { Store, StoreDefinition, _Method } from './types'
+import { isPlainObject, Store, StoreDefinition, _Method } from './types'
 
 export const isUseStore = (fn: any): fn is StoreDefinition => {
   return typeof fn === 'function' && typeof fn.$id === 'string'
 }
 
+export function patchObject(
+  newState: Record<string, any>,
+  oldState: Record<string, any>
+): Record<string, any> {
+  // no need to go through symbols because they cannot be serialized anyway
+  for (const key in oldState) {
+    const subPatch = oldState[key]
+
+    // skip the whole sub tree
+    if (!(key in newState)) {
+      continue
+    }
+
+    const targetValue = newState[key]
+    if (
+      isPlainObject(targetValue) &&
+      isPlainObject(subPatch) &&
+      !isRef(subPatch) &&
+      !isReactive(subPatch)
+    ) {
+      newState[key] = patchObject(targetValue, subPatch)
+    } else {
+      // objects are either a bit more complex (e.g. refs) or primitives, so we
+      // just set the whole thing
+      newState[key] = subPatch
+    }
+  }
+
+  return newState
+}
+
 export function acceptHMRUpdate(
   initialUseStore: StoreDefinition<string, any, any, any>,
   hot: any
index 069374067658ed10890251cb4affd9f3d5503570..51f3999b031b692ae887a31e0d2b8605e45f33f3 100644 (file)
@@ -45,6 +45,7 @@ import {
   activePinia,
 } from './rootStore'
 import { IS_CLIENT } from './env'
+import { patchObject } from './hmr'
 
 function innerPatch<T extends StateTree>(
   target: T,
@@ -462,11 +463,20 @@ function createSetupStore<
       newStore._hmrPayload.state.forEach((stateKey) => {
         if (stateKey in store.$state) {
           // @ts-expect-error
-          // transfer the ref
-          newStore.$state[stateKey] =
-            // ---
+          const newStateTarget = newStore.$state[stateKey]
+          // @ts-expect-error
+          const oldStateSource = store.$state[stateKey]
+          if (
+            typeof newStateTarget === 'object' &&
+            isPlainObject(newStateTarget) &&
+            isPlainObject(oldStateSource)
+          ) {
+            patchObject(newStateTarget, oldStateSource)
+          } else {
             // @ts-expect-error
-            store.$state[stateKey]
+            // transfer the ref
+            newStore.$state[stateKey] = oldStateSource
+          }
         }
         // patch direct access properties to allow store.stateProperty to work as
         // store.$state.stateProperty