mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
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:
parent
c83bcb046a
commit
deadc48f19
@ -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
|
|
||||||
if (!storeData.modules) {
|
|
||||||
storeData.modules = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const filename of filenames) {
|
|
||||||
let name = filename.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '')
|
|
||||||
if (name === 'index') continue
|
|
||||||
|
|
||||||
const namePath = name.split(/\//)
|
|
||||||
|
|
||||||
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
|
|
||||||
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(storeData)
|
|
||||||
})
|
|
||||||
}<% } %>
|
|
||||||
} else {
|
|
||||||
const log = (process.server ? require('consola') : console)
|
const log = (process.server ? require('consola') : console)
|
||||||
log.warn('Classic mode for store/ is deprecated and will be removed in Nuxt 3.')
|
return log.warn('Classic mode for store/ is deprecated and will be removed in Nuxt 3.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce store modules
|
||||||
|
store.modules = store.modules || {}
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
// Remove store path + extension (./foo/index.js -> foo/index)
|
||||||
|
const namespace = path.replace(/^\.\//, '').replace(/\.(<%= extensions %>)$/, '')
|
||||||
|
|
||||||
|
// Ignore indexFile, handled before
|
||||||
|
if (namespace === 'index') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// If file is foo/index.js, it should be saved as foo
|
||||||
|
const isIndexModule = (moduleName === 'index')
|
||||||
|
if (isIndexModule) {
|
||||||
|
namespaces.pop()
|
||||||
|
moduleName = namespaces[namespaces.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const storeModule = getStoreModule(store, namespaces)
|
||||||
|
|
||||||
|
for (const property of VUEX_PROPERTIES) {
|
||||||
|
mergeProperty(storeModule, moduleData[property], property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the environment supports hot reloading...
|
||||||
|
<% if (isDev) { %>
|
||||||
|
if (process.client && module.hot) {
|
||||||
|
// Whenever any Vuex module is updated...
|
||||||
|
module.hot.accept(fileResolver.id, () => {
|
||||||
|
// Update `root.modules` with the latest definitions.
|
||||||
|
updateModules()
|
||||||
|
// Trigger a hot update in the store.
|
||||||
|
window.<%= globals.nuxt %>.$store.hotUpdate(store)
|
||||||
|
})
|
||||||
|
}<% } %>
|
||||||
})()
|
})()
|
||||||
|
|
||||||
// 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
|
|
||||||
|
if (isRoot && typeof moduleData !== 'function') {
|
||||||
|
// Avoid TypeError: setting a property that has only a getter when overwriting top level keys
|
||||||
|
const state = moduleData.state && typeof moduleData.state !== 'function' ? (() => state) : moduleData.state
|
||||||
|
return Object.assign({}, moduleData, { state })
|
||||||
|
}
|
||||||
|
return moduleData
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModuleNamespace(storeData, namePath, forAppend = false) {
|
function getStoreModule(storeModule, namespaces, { isProperty = false } = {}) {
|
||||||
if (namePath.length === 1) {
|
// If ./mutations.js
|
||||||
if (forAppend) {
|
if (!namespaces.length || (isProperty && namespaces.length === 1)) {
|
||||||
return storeData
|
return storeModule
|
||||||
}
|
|
||||||
return storeData.modules
|
|
||||||
}
|
}
|
||||||
const namespace = namePath.shift()
|
|
||||||
storeData.modules[namespace] = storeData.modules[namespace] || {}
|
const namespace = namespaces.shift()
|
||||||
storeData.modules[namespace].namespaced = true
|
|
||||||
storeData.modules[namespace].modules = storeData.modules[namespace].modules || {}
|
storeModule.modules[namespace] = storeModule.modules[namespace] || {}
|
||||||
return getModuleNamespace(storeData.modules[namespace], namePath, forAppend)
|
storeModule.modules[namespace].namespaced = true
|
||||||
|
storeModule.modules[namespace].modules = storeModule.modules[namespace].modules || {}
|
||||||
|
|
||||||
|
return getStoreModule(storeModule.modules[namespace], namespaces, { isProperty })
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendModule(module, filename, name) {
|
function mergeProperty(storeModule, moduleData, property) {
|
||||||
const file = files(filename)
|
if (!moduleData) return
|
||||||
module.appends = module.appends || []
|
|
||||||
module.appends.push(name)
|
if (property === 'state') {
|
||||||
module[name] = file.default || file
|
storeModule.state = moduleData || storeModule.state
|
||||||
|
} else {
|
||||||
|
storeModule[property] = Object.assign({}, storeModule[property], moduleData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user