feat: add store module HMR (#4582)

* feat: add store module HMR

* fix: replace export with window.$nuxt

Thanks to @pi0 for the suggestion :)

* refactor: apply only in dev mode on client side

* test: make store module test more descriptive

* fix: clear modules to apply HMR

* fix: remove console.log

* fix: e2e tests

* refactor: use void
This commit is contained in:
Alexander Lichter 2018-12-19 15:22:00 +00:00 committed by Sébastien Chopin
parent 5b58272d1a
commit b2eee1772e
4 changed files with 82 additions and 62 deletions

View File

@ -3,73 +3,87 @@ import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex)
const files = require.context('@/<%= dir.store %>', true, /^\.\/(?!<%= ignorePrefix %>)[^.]+\.(<%= extensions %>)$/)
const filenames = files.keys()
// Store
let storeData = {} let storeData = {}
// Check if {dir.store}/index.js exists let files;
const indexFilename = filenames.find(filename => filename.includes('./index.'))
if (indexFilename) { void function updateModules() {
storeData = getModule(indexFilename) files = require.context('@/<%= dir.store %>', true, /^\.\/(?!<%= ignorePrefix %>)[^.]+\.(<%= extensions %>)$/)
} const filenames = files.keys()
// If store is not an exported method = modules store // Check if {dir.store}/index.js exists
if (typeof storeData !== 'function') { const indexFilename = filenames.find(filename => filename.includes('./index.'))
// Store modules
if (!storeData.modules) { if (indexFilename) {
storeData.modules = {} storeData = getModule(indexFilename)
} }
for (const filename of filenames) { // If store is not an exported method = modules store
let name = filename.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '') if (typeof storeData !== 'function') {
if (name === 'index') continue // Store modules
if (!storeData.modules || module.hot) {
const namePath = name.split(/\//) storeData.modules = {}
name = namePath[namePath.length - 1]
if (['state', 'getters', 'actions', 'mutations'].includes(name)) {
const module = getModuleNamespace(storeData, namePath, true)
appendModule(module, filename, name)
continue
} }
// If file is foo/index.js, it should be saved as foo for (const filename of filenames) {
const isIndex = (name === 'index') let name = filename.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '')
if (isIndex) { if (name === 'index') continue
namePath.pop()
}
const module = getModuleNamespace(storeData, namePath) const namePath = name.split(/\//)
const fileModule = getModule(filename)
name = namePath.pop() name = namePath[namePath.length - 1]
module[name] = module[name] || {} if (['state', 'getters', 'actions', 'mutations'].includes(name)) {
const module = getModuleNamespace(storeData, namePath, true)
// if file is foo.js, existing properties take priority appendModule(module, filename, name)
// because it's the least specific case continue
if (!isIndex) {
module[name] = Object.assign({}, fileModule, module[name])
module[name].namespaced = true
continue
}
// if file is foo/index.js we want to overwrite properties from foo.js
// but not from appended mods like foo/actions.js
const appendedMods = {}
if (module[name].appends) {
appendedMods.appends = module[name].appends
for (const append of module[name].appends) {
appendedMods[append] = module[name][append]
} }
}
module[name] = Object.assign({}, module[name], fileModule, appendedMods) // If file is foo/index.js, it should be saved as foo
module[name].namespaced = true const isIndex = (name === 'index')
if (isIndex) {
namePath.pop()
}
const module = getModuleNamespace(storeData, namePath)
const fileModule = getModule(filename)
name = namePath.pop()
module[name] = module[name] || {}
// if file is foo.js, existing properties take priority
// because it's the least specific case
if (!isIndex) {
module[name] = Object.assign({}, fileModule, module[name])
module[name].namespaced = true
continue
}
// if file is foo/index.js we want to overwrite properties from foo.js
// but not from appended mods like foo/actions.js
const appendedMods = {}
if (module[name].appends) {
appendedMods.appends = module[name].appends
for (const append of module[name].appends) {
appendedMods[append] = module[name][append]
}
}
module[name] = Object.assign({}, module[name], fileModule, appendedMods)
module[name].namespaced = true
}
// If the environment supports hot reloading...
<% if (isDev) { %>
if (process.client && module.hot) {
// Whenever any Vuex module is updated...
module.hot.accept(files.id, () => {
// Update `root.modules` with the latest definitions.
updateModules()
// Trigger a hot update in the store.
window.<%= globals.nuxt %>.$store.hotUpdate({ modules: storeData.modules })
})
}<% } %>
} }
} }()
// createStore // createStore
export const createStore = storeData instanceof Function ? storeData : () => { export const createStore = storeData instanceof Function ? storeData : () => {

View File

@ -78,8 +78,10 @@ describe('basic browser', () => {
test('/store', async () => { test('/store', async () => {
await page.nuxt.navigate('/store') await page.nuxt.navigate('/store')
expect(await page.$text('h1')).toBe('Vuex Nested Modules') expect(await page.$text('h1')).toBe('foo/bar/baz: Vuex Nested Modules')
expect(await page.$text('p')).toBe('1') expect(await page.$text('h2')).toBe('index/counter: 1')
expect(await page.$text('h3')).toBe('foo/blarg/getVal: 4')
expect(await page.$text('h4')).toBe('foo/bab/getBabVal: 10')
}) })
test('/head', async () => { test('/head', async () => {

View File

@ -1,12 +1,14 @@
<template> <template>
<div> <div>
<h1>{{ baz }}</h1> <h1>foo/bar/baz: {{ baz }}</h1>
<br> <br>
<p>{{ $store.state.counter }}</p> <h2>index/counter: {{ $store.state.counter }}</h2>
<br> <br>
<h2>{{ getVal }}</h2> <h3>foo/blarg/getVal: {{ getVal }}</h3>
<br> <br>
<h3>{{ getBabVal }}</h3> <h4>foo/bab/getBabVal: {{ getBabVal }}</h4>
<br>
<button @click="$store.commit('increment')">+1</button>
</div> </div>
</template> </template>

View File

@ -70,8 +70,10 @@ describe('basic ssr', () => {
test('/store', async () => { test('/store', async () => {
const { html } = await nuxt.server.renderRoute('/store') const { html } = await nuxt.server.renderRoute('/store')
expect(html).toContain('<h1>Vuex Nested Modules</h1>') expect(html).toContain('<h1>foo/bar/baz: Vuex Nested Modules</h1>')
expect(html).toContain('<p>1</p>') expect(html).toContain('<h2>index/counter: 1</h2>')
expect(html).toContain('<h3>foo/blarg/getVal: 4</h3>')
expect(html).toContain('<h4>foo/bab/getBabVal: 10</h4>')
}) })
test('/head', async () => { test('/head', async () => {