Repository: ktsn/vuex-smart-module (ts)

Issue:
Issues with HMR in Nuxt
Hello, thank you for this great project!

I was trying to implement v0.4.0 in a Nuxt.js project using the store in module mode. Following your instructions, I was able to make it work perfectly. Anyway, I found an issue with HMR where, I believe, the accessors created for getters, mutations, and actions lose their `this` reference.

I created a small demo with a module and root store here: https://github.com/dwightjack/nuxt-vsm-demo

### Implementation

I have created a [`demo` module](https://github.com/dwightjack/nuxt-vsm-demo/blob/master/store/demo.ts), registered it on the root store and then mapped one of its actions in a [page component](https://github.com/dwightjack/nuxt-vsm-demo/blob/master/pages/index.vue#L28).

### Step to reproduce

1. run the development env: `npm i && npm run dev` 
1. on the homepage of the application write something in the input text. Everything works as expected
1. edit either the store index or the module file and save
1. HMR reloads the store
1. write again in the input box: I receive the following error:

![image](https://user-images.githubusercontent.com/104721/85257905-8148fd00-b4a1-11ea-8c56-1551ce8b5d35.png)

### Notes

Looking at vuex-smart-module custom HMR function I guess that the problem is that Nuxt default implementation does not re-run the store plugins after reloading which is needed by this library to define the getters, mutations and actions accessors. But I don't know if I can implement a different HMR strategy since that logic is implemented in a `store.js` file inside the `.nuxt` folder.

Any hint on where could I look to find a fix for this problem?

Thanks in advance!

Pull request description:
fix: inject store instance when `hotUpdate` options from `getStoreOptions`
fix #239 

As Nuxt automatically call `Store#hotUpdate`, we need to provide a way to do hot update without using `hotUpdate` from vuex-smart-module.

To do that, this PR patches `Store#hotUpdate` method in the plugin returned from `getStoreOptions` and the patched method tries to inject store instance on every call for `hotUpdate`.

Patch:
diff --git a/src/index.ts b/src/index.ts
index cdec7f8..ebe7360 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -12,12 +12,12 @@ export function createStore(
   rootModule: Module<any, any, any, any, any>,
   options: StoreOptions<any> = {}
 ): Store<any> {
-  const rootModuleOptions = rootModule.getStoreOptions()
+  const { options: rootModuleOptions, injectStore } = rootModule.create([], '')
 
   const store: Store<any> = new Store({
     ...rootModuleOptions,
     ...options,
-    plugins: rootModuleOptions.plugins.concat(options.plugins || []),
+    plugins: [injectStore].concat(options.plugins || []),
   })
 
   return store
diff --git a/src/module.ts b/src/module.ts
index 8480645..78384c4 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -133,10 +133,29 @@ export class Module<
   }
 
   getStoreOptions() {
+    const injectStoreActionName = 'vuex-smart-module/injectStore'
     const { options, injectStore } = this.create([], '')
+
+    if (!options.actions) {
+      options.actions = {}
+    }
+    options.actions[injectStoreActionName] = function () {
+      injectStore(this)
+    }
+
+    const plugin = (store: Store<any>) => {
+      store.dispatch(injectStoreActionName)
+
+      const originalHotUpdate = store.hotUpdate
+      store.hotUpdate = (options) => {
+        originalHotUpdate.call(store, options)
+        store.dispatch(injectStoreActionName)
+      }
+    }
+
     return {
       ...options,
-      plugins: [injectStore],
+      plugins: [plugin],
     }
   }
 


Test patch:
diff --git a/test/hot-update.spec.ts b/test/hot-update.spec.ts
index c428dc4..8df8cfa 100644
--- a/test/hot-update.spec.ts
+++ b/test/hot-update.spec.ts
@@ -43,99 +43,127 @@ describe('hotUpdate', () => {
     actions: FooActions,
   })
 
-  let store: Store<any>
-  beforeEach(() => {
-    store = createStore(foo)
-  })
+  describe('hotUpdate function', () => {
+    let store: Store<any>
+    beforeEach(() => {
+      store = createStore(foo)
+    })
 
-  it('does not update state', () => {
-    expect(store.state.value).toBe(1)
+    it('does not update state', () => {
+      expect(store.state.value).toBe(1)
 
-    class NewState {
-      value = 100
-    }
+      class NewState {
+        value = 100
+      }
 
-    const newFoo = new Module({
-      state: NewState,
-      getters: FooGetters,
-      mutations: FooMutations,
-      actions: FooActions,
-    })
+      const newFoo = new Module({
+        state: NewState,
+        getters: FooGetters,
+        mutations: FooMutations,
+        actions: FooActions,
+      })
 
-    hotUpdate(store, newFoo)
+      hotUpdate(store, newFoo)
 
-    expect(store.state.value).toBe(1)
-  })
+      expect(store.state.value).toBe(1)
+    })
 
-  it('updates a getter', () => {
-    expect(store.getters.double).toBe(2)
+    it('updates a getter', () => {
+      expect(store.getters.double).toBe(2)
 
-    class NewGetters extends Getters<FooState> {
-      get double() {
-        return this.state.value * 20
+      class NewGetters extends Getters<FooState> {
+        get double() {
+          return this.state.value * 20
+        }
       }
-    }
 
-    const newFoo = new Module({
-      state: FooState,
-      getters: NewGetters,
-      mutations: FooMutations,
-      actions: FooActions,
-    })
+      const newFoo = new Module({
+        state: FooState,
+        getters: NewGetters,
+        mutations: FooMutations,
+        actions: FooActions,
+      })
 
-    hotUpdate(store, newFoo)
+      hotUpdate(store, newFoo)
 
-    expect(store.getters.double).toBe(20)
-  })
+      expect(store.getters.double).toBe(20)
+    })
 
-  it('updates a mutation', () => {
-    store.commit('inc', 1)
-    expect(store.state.value).toBe(2)
+    it('updates a mutation', () => {
+      store.commit('inc', 1)
+      expect(store.state.value).toBe(2)
 
-    class NewMutations extends Mutations<FooState> {
-      inc(amount: number) {
-        this.state.value += amount * 10
+      class NewMutations extends Mutations<FooState> {
+        inc(amount: number) {
+          this.state.value += amount * 10
+        }
       }
-    }
 
-    const newFoo = new Module({
-      state: FooState,
-      getters: FooGetters,
-      mutations: NewMutations,
-      actions: FooActions,
-    })
+      const newFoo = new Module({
+        state: FooState,
+        getters: FooGetters,
+        mutations: NewMutations,
+        actions: FooActions,
+      })
 
-    hotUpdate(store, newFoo)
+      hotUpdate(store, newFoo)
 
-    store.commit('inc', 1)
-    expect(store.state.value).toBe(12)
-  })
+      store.commit('inc', 1)
+      expect(store.state.value).toBe(12)
+    })
 
-  it('updates an action', () => {
-    store.dispatch('inc', 1)
-    expect(store.state.value).toBe(2)
-
-    class NewActions extends Actions<
-      FooState,
-      FooGetters,
-      FooMutations,
-      FooActions
-    > {
-      inc(amount: number) {
-        this.commit('inc', amount * 10)
+    it('updates an action', () => {
+      store.dispatch('inc', 1)
+      expect(store.state.value).toBe(2)
+
+      class NewActions extends Actions<
+        FooState,
+        FooGetters,
+        FooMutations,
+        FooActions
+      > {
+        inc(amount: number) {
+          this.commit('inc', amount * 10)
+        }
       }
-    }
 
-    const newFoo = new Module({
-      state: FooState,
-      getters: FooGetters,
-      mutations: FooMutations,
-      actions: NewActions,
+      const newFoo = new Module({
+        state: FooState,
+        getters: FooGetters,
+        mutations: FooMutations,
+        actions: NewActions,
+      })
+
+      hotUpdate(store, newFoo)
+
+      store.dispatch('inc', 1)
+      expect(store.state.value).toBe(12)
     })
+  })
+
+  describe('via getStoreOptions', () => {
+    it('can use store#hotUpdate', () => {
+      const store = new Store(foo.getStoreOptions())
+
+      store.commit('inc', 1)
+      expect(store.state.value).toBe(2)
 
-    hotUpdate(store, newFoo)
+      class NewMutations extends Mutations<FooState> {
+        inc(amount: number) {
+          this.state.value += amount * 10
+        }
+      }
+
+      const newFoo = new Module({
+        state: FooState,
+        getters: FooGetters,
+        mutations: NewMutations,
+        actions: FooActions,
+      })
 
-    store.dispatch('inc', 1)
-    expect(store.state.value).toBe(12)
+      store.hotUpdate(newFoo.getStoreOptions())
+      store.commit('inc', 1)
+      expect(store.state.value).toBe(12)
+    })
   })
 })