diff --git a/docs/en/api.md b/docs/en/api.md index e7ac2320e..77a94a24e 100644 --- a/docs/en/api.md +++ b/docs/en/api.md @@ -158,8 +158,12 @@ const store = new Vuex.Store({ ...options }) - **`registerModule(path: string | Array, module: Module)`** + **`registerModule(path: string | Array, module: () => Promise): Promise`** + Register a dynamic module. [Details](modules.md#dynamic-module-registration) + Lazy load a dynamic module. [Details](modules.md#lazy-load-dynamic-modules) + - **`unregisterModule(path: string | Array)`** Unregister a dynamic module. [Details](modules.md#dynamic-module-registration) diff --git a/docs/en/modules.md b/docs/en/modules.md index 77061def2..0f21aefb4 100644 --- a/docs/en/modules.md +++ b/docs/en/modules.md @@ -244,6 +244,58 @@ Dynamic module registration makes it possible for other Vue plugins to also leve You can also remove a dynamically registered module with `store.unregisterModule(moduleName)`. Note you cannot remove static modules (declared at store creation) with this method. +### Lazy Load Dynamic Modules + +You can code split you Vuex store and dynamically register store modules. + +``` js +const myModule = () => import('path/to/module') + +store.registerModule('myModule', myModule) +``` + +This is simple enough, however you will need to ensure that modules are registered before you can use them. Attempting to use a module before it is registered will not work and will cause errors. + +For example, with vue-router, you can ensure that routing does not resolve until the required modules have been loaded. You can find further [implementation details here](https://ssr.vuejs.org/en/data.html) under the **Client Data Fetching** section. Using this pattern will ensure that we can register modules before use within components. + +Overall this pattern will require more awareness and management of the store module registration, however if well managed it can be a very poweful feature and significantly reduce the initial bundle size of your application. + +``` js +import { mapGetters } from 'vuex' + +const myModule = () => import('path/to/module') + +export default { + asyncData(store) { + return store.registerModule('myModule', myModule) + .then(() => store.dispatch('myModule/getData')) + }, + + computed: { + ...mapGetters('myModule', ['a']) + } +} +``` + +Using async and await this becomes less verbose for the registering of a module: + +``` js +import { mapGetters } from 'vuex' + +const myModule = () => import('path/to/module') + +export default { + async asyncData(store) { + await store.registerModule('myModule', myModule) + await store.dispatch('myModule/getData') + }, + + computed: { + ...mapGetters('myModule', ['a']) + } +} +``` + ### Module Reuse Sometimes we may need to create multiple instances of a module, for example: diff --git a/src/store.js b/src/store.js index 3a4f1bab7..a2baaea77 100644 --- a/src/store.js +++ b/src/store.js @@ -158,10 +158,15 @@ export class Store { assert(path.length > 0, 'cannot register the root module by using registerModule.') } - this._modules.register(path, rawModule) - installModule(this, this.state, path, this._modules.get(path)) - // reset store to update getters... - resetStoreVM(this, this.state) + const moduleResolved = module => { + this._modules.register(path, module.default || module) + installModule(this, this.state, path, this._modules.get(path)) + // reset store to update getters... + resetStoreVM(this, this.state) + } + + if (typeof rawModule === 'function') return rawModule().then(moduleResolved) + moduleResolved(rawModule) } unregisterModule (path) { diff --git a/test/unit/modules.spec.js b/test/unit/modules.spec.js index 1d9c8d688..2bed73def 100644 --- a/test/unit/modules.spec.js +++ b/test/unit/modules.spec.js @@ -82,6 +82,36 @@ describe('Modules', () => { }) }) + it('dynamic module registration with promise (dynamic import)', () => { + const store = new Vuex.Store({ + modules: { + a: { + namespaced: true + } + } + }) + const actionSpy = jasmine.createSpy() + const mutationSpy = jasmine.createSpy() + const module = () => Promise.resolve({ + state: { value: 1 }, + getters: { foo: state => state.value }, + actions: { foo: actionSpy }, + mutations: { foo: mutationSpy } + }) + + store.registerModule(['a', 'b'], module) + .then(() => { + expect(store.state.a.b.value).toBe(1) + expect(store.getters['a/foo']).toBe(1) + + store.dispatch('a/foo') + expect(actionSpy).toHaveBeenCalled() + + store.commit('a/foo') + expect(mutationSpy).toHaveBeenCalled() + }) + }) + // #524 it('should not fire an unrelated watcher', done => { const spy = jasmine.createSpy() diff --git a/types/index.d.ts b/types/index.d.ts index 89d4b88ca..b65faea02 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -20,6 +20,8 @@ export declare class Store { subscribe

(fn: (mutation: P, state: S) => any): () => void; watch(getter: (state: S) => T, cb: (value: T, oldValue: T) => void, options?: WatchOptions): void; + registerModule(path: string, module: () => Promise>): Promise; + registerModule(path: string[], module: () => Promise>): Promise; registerModule(path: string, module: Module): void; registerModule(path: string[], module: Module): void; diff --git a/types/test/index.ts b/types/test/index.ts index c034f932b..2e156c366 100644 --- a/types/test/index.ts +++ b/types/test/index.ts @@ -199,6 +199,11 @@ namespace RegisterModule { state: { value: 2 } }); + const asyncModule = () => Promise.resolve({ state: { value: 1 }}) + store.registerModule("a", asyncModule) + .then(() => {}) + .catch(error => {}) + store.unregisterModule(["a", "b"]); store.unregisterModule("a"); }