Skip to content

Feature: allow same paradigm for lazy loading modules #839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/en/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,12 @@ const store = new Vuex.Store({ ...options })

- **`registerModule(path: string | Array<string>, module: Module)`**

**`registerModule(path: string | Array<string>, module: () => Promise<Module>): Promise<void>`**

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<string>)`**

Unregister a dynamic module. [Details](modules.md#dynamic-module-registration)
Expand Down
52 changes: 52 additions & 0 deletions docs/en/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
13 changes: 9 additions & 4 deletions src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
30 changes: 30 additions & 0 deletions test/unit/modules.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export declare class Store<S> {
subscribe<P extends Payload>(fn: (mutation: P, state: S) => any): () => void;
watch<T>(getter: (state: S) => T, cb: (value: T, oldValue: T) => void, options?: WatchOptions): void;

registerModule<T>(path: string, module: () => Promise<Module<T, S>>): Promise<void>;
registerModule<T>(path: string[], module: () => Promise<Module<T, S>>): Promise<void>;
registerModule<T>(path: string, module: Module<T, S>): void;
registerModule<T>(path: string[], module: Module<T, S>): void;

Expand Down
5 changes: 5 additions & 0 deletions types/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down