fix(vue-app): Fix Vuex HMR & refactor for better modules usage (#4791)

* fix(vue-app): Refactor store for better modules usage

* Update packages/vue-app/template/store.js

Co-Authored-By: Atinux <seb@orion.sh>

* fix: tests and code style
This commit is contained in:
Sébastien Chopin 2019-01-18 12:28:34 +01:00 committed by GitHub
parent c83bcb046a
commit deadc48f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -3,130 +3,138 @@ import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex)
let storeData = {} const VUEX_PROPERTIES = ['state', 'getters', 'actions', 'mutations']
let store = {}
let files let fileResolver
void (function updateModules() { void (function updateModules() {
files = require.context('@/<%= dir.store %>', true, /^\.\/(?!<%= ignorePrefix %>)[^.]+\.(<%= extensions %>)$/) fileResolver = require.context('@/<%= dir.store %>', true, /^\.\/(?!<%= ignorePrefix %>)[^.]+\.(<%= extensions %>)$/)
const filenames = files.keys()
// Paths are sorted from low to high priority (for overwriting properties)
const paths = fileResolver.keys().sort((p1, p2) => {
let res = p1.split('/').length - p2.split('/').length
if (res === 0 && p1.includes('/index.')) {
res = -1
} else if (res === 0 && p2.includes('/index.')) {
res = 1
}
return res
})
// Check if {dir.store}/index.js exists // Check if {dir.store}/index.js exists
const indexFilename = filenames.find(filename => filename.includes('./index.')) const indexPath = paths.find(path => path.includes('./index.'))
if (indexFilename) { if (indexPath) {
storeData = getModule(indexFilename) store = requireModule(indexPath, { isRoot: true })
} }
// If store is not an exported method = modules store // If store is an exported method = classic mode (deprecated)
if (typeof storeData !== 'function') { if (typeof store === 'function') {
// Store modules const log = (process.server ? require('consola') : console)
if (!storeData.modules) { return log.warn('Classic mode for store/ is deprecated and will be removed in Nuxt 3.')
storeData.modules = {}
} }
for (const filename of filenames) { // Enforce store modules
let name = filename.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '') store.modules = store.modules || {}
if (name === 'index') continue
const namePath = name.split(/\//) for (const path of paths) {
// Remove store path + extension (./foo/index.js -> foo/index)
const namespace = path.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '')
name = namePath[namePath.length - 1] // Ignore indexFile, handled before
if (['state', 'getters', 'actions', 'mutations'].includes(name)) { if (namespace === 'index') {
const module = getModuleNamespace(storeData, namePath, true) continue
appendModule(module, filename, name) }
const namespaces = namespace.split('/')
let moduleName = namespaces[namespaces.length - 1]
const moduleData = requireModule(path, { isState: moduleName === 'state' })
// If path is a known Vuex property
if (VUEX_PROPERTIES.includes(moduleName)) {
const property = moduleName
const storeModule = getStoreModule(store, namespaces, { isProperty: true })
// Replace state since it's a function
mergeProperty(storeModule, moduleData, property)
continue continue
} }
// If file is foo/index.js, it should be saved as foo // If file is foo/index.js, it should be saved as foo
const isIndex = (name === 'index') const isIndexModule = (moduleName === 'index')
if (isIndex) { if (isIndexModule) {
namePath.pop() namespaces.pop()
moduleName = namespaces[namespaces.length - 1]
} }
const module = getModuleNamespace(storeData, namePath) const storeModule = getStoreModule(store, namespaces)
const fileModule = getModule(filename)
name = namePath.pop() for (const property of VUEX_PROPERTIES) {
module[name] = module[name] || {} mergeProperty(storeModule, moduleData[property], property)
// 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 the environment supports hot reloading...
<% if (isDev) { %> <% if (isDev) { %>
if (process.client && module.hot) { if (process.client && module.hot) {
// Whenever any Vuex module is updated... // Whenever any Vuex module is updated...
module.hot.accept(files.id, () => { module.hot.accept(fileResolver.id, () => {
// Update `root.modules` with the latest definitions. // Update `root.modules` with the latest definitions.
updateModules() updateModules()
// Trigger a hot update in the store. // Trigger a hot update in the store.
window.<%= globals.nuxt %>.$store.hotUpdate(storeData) window.<%= globals.nuxt %>.$store.hotUpdate(store)
}) })
}<% } %> }<% } %>
} else {
const log = (process.server ? require('consola') : console)
log.warn('Classic mode for store/ is deprecated and will be removed in Nuxt 3.')
}
})() })()
// createStore // createStore
export const createStore = storeData instanceof Function ? storeData : () => { export const createStore = store instanceof Function ? store : () => {
return new Vuex.Store(Object.assign({ return new Vuex.Store(Object.assign({
strict: (process.env.NODE_ENV !== 'production') strict: (process.env.NODE_ENV !== 'production')
}, storeData, { }, store))
state: storeData.state instanceof Function ? storeData.state() : {}
}))
} }
// Dynamically require module // Dynamically require module
function getModule(filename) { function requireModule(path, { isRoot = false, isState = false } = {}) {
const file = files(filename) const file = fileResolver(path)
const module = file.default || file const moduleData = file.default || file
if (module.commit) {
throw new Error('[nuxt] <%= dir.store %>/' + filename.replace('./', '') + ' should export a method which returns a Vuex instance.') if (isState && typeof moduleData !== 'function') {
return () => moduleData
} }
if (module.state && typeof module.state !== 'function') { if (isRoot && moduleData.commit) {
throw new Error('[nuxt] state should be a function in <%= dir.store %>/' + filename.replace('./', '')) throw new Error('[nuxt] <%= dir.store %>/' + path.replace('./', '') + ' should export a method which returns a Vuex instance.')
}
return module
} }
function getModuleNamespace(storeData, namePath, forAppend = false) { if (isRoot && typeof moduleData !== 'function') {
if (namePath.length === 1) { // Avoid TypeError: setting a property that has only a getter when overwriting top level keys
if (forAppend) { const state = moduleData.state && typeof moduleData.state !== 'function' ? (() => state) : moduleData.state
return storeData return Object.assign({}, moduleData, { state })
} }
return storeData.modules return moduleData
}
const namespace = namePath.shift()
storeData.modules[namespace] = storeData.modules[namespace] || {}
storeData.modules[namespace].namespaced = true
storeData.modules[namespace].modules = storeData.modules[namespace].modules || {}
return getModuleNamespace(storeData.modules[namespace], namePath, forAppend)
} }
function appendModule(module, filename, name) { function getStoreModule(storeModule, namespaces, { isProperty = false } = {}) {
const file = files(filename) // If ./mutations.js
module.appends = module.appends || [] if (!namespaces.length || (isProperty && namespaces.length === 1)) {
module.appends.push(name) return storeModule
module[name] = file.default || file }
const namespace = namespaces.shift()
storeModule.modules[namespace] = storeModule.modules[namespace] || {}
storeModule.modules[namespace].namespaced = true
storeModule.modules[namespace].modules = storeModule.modules[namespace].modules || {}
return getStoreModule(storeModule.modules[namespace], namespaces, { isProperty })
}
function mergeProperty(storeModule, moduleData, property) {
if (!moduleData) return
if (property === 'state') {
storeModule.state = moduleData || storeModule.state
} else {
storeModule[property] = Object.assign({}, storeModule[property], moduleData)
}
} }