Skip to content

Commit

Permalink
Support vuex module arrays on components (#98)
Browse files Browse the repository at this point in the history
* Add support for multiple Vuex Modules per route component

* Bump v6.0.0

* Fix order of removals
  • Loading branch information
brophdawg11 committed May 24, 2021
1 parent bab8c34 commit 5bd47a6
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 74 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-ssr-build",
"version": "5.2.1",
"version": "6.0.0",
"description": "Vue.js SSR Build Helper",
"author": "matt@brophy.org",
"license": "MIT",
Expand Down
110 changes: 48 additions & 62 deletions src/entry-client.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { get, find, isEqual, isFunction, isString } from 'lodash-es';
import { get, isEqual, isFunction, isString, sortBy, uniq } from 'lodash-es';

import { getModuleName, safelyRegisterModule } from './utils';

/**
* Determine if we should run our middlewares and fetchData for a given routing
Expand Down Expand Up @@ -119,7 +121,6 @@ export default function initializeClient(createApp, clientOpts) {
initialState: null,
initialStateMetaTag: 'initial-state',
vuexModules: true,
maxVuexModules: 2,
middleware: () => Promise.resolve(),
globalFetchData: () => Promise.resolve(),
postMiddleware: () => Promise.resolve(),
Expand Down Expand Up @@ -157,20 +158,7 @@ export default function initializeClient(createApp, clientOpts) {
});

if (opts.vuexModules) {
// This is a temporary workaround for us to use to prevent re-registering
// dynamic modules until Vuex implements a hasModule() type method. See:
// https://github.com/vuejs/vuex/issues/833
// https://github.com/vuejs/vuex/pull/834
const registeredModules = [];
let moduleIndex = 0;

// Allow a function to be passed that can generate a route-aware
// module name
const getModuleName = (c, route) => (
isFunction(c.vuex.moduleName) ?
c.vuex.moduleName({ $route: route }) :
c.vuex.moduleName
);
const queuedRemovalModules = [];

// Before routing, register any dynamic Vuex modules for new components
router.beforeResolve((to, from, next) => {
Expand All @@ -179,26 +167,10 @@ export default function initializeClient(createApp, clientOpts) {
router.getMatchedComponents(to)
.filter(c => 'vuex' in c)
.filter(c => shouldProcessRouteUpdate(c, fetchDataArgs, spruDefaults))
.forEach((c) => {
const name = getModuleName(c, to);
const existingModule = find(registeredModules, { name });
if (existingModule) {
// We already have this module registered, update the
// index to mark it as recent
opts.logger.info('Skipping duplicate Vuex module registration:', name);
existingModule.index = moduleIndex++;
} else {
opts.logger.info('Registering dynamic Vuex module:', name);
// This module may have been registered outside of the
// routing flow, so only register it with Vuex if needed -
// but add it to our tracking of registeredModules regardless
if (get(store, `_modulesNamespaceMap.${name}/`) == null) {
store.registerModule(name, c.vuex.module, {
preserveState: store.state[name] != null,
});
}
registeredModules.push({ name, index: moduleIndex++ });
}
.flatMap(c => c.vuex)
.forEach((vuexModuleDef) => {
const name = getModuleName(vuexModuleDef, to);
safelyRegisterModule(store, name, vuexModuleDef.module, opts.logger);
});

next();
Expand All @@ -212,35 +184,49 @@ export default function initializeClient(createApp, clientOpts) {
// After routing, unregister any dynamic Vuex modules from prior components
router.afterEach((to, from) => {
const fetchDataArgs = { app, route: to, router, store, from };
const shouldProcess = router.getMatchedComponents(to)
.filter(c => shouldProcessRouteUpdate(c, fetchDataArgs, spruDefaults))
.length > 0;

if (!shouldProcess) {
return;
}

// Determine "active" modules from the outgoing and incoming routes
const toModuleNames = router.getMatchedComponents(to)
.filter(c => 'vuex' in c)
.map(c => getModuleName(c, to));
router.getMatchedComponents(from)
.flatMap(c => c.vuex)
.map(vuexModuleDef => getModuleName(vuexModuleDef, to));
const fromModuleNames = router.getMatchedComponents(from)
.filter(c => 'vuex' in c)
.filter(c => shouldProcessRouteUpdate(c, fetchDataArgs, spruDefaults))
.forEach((c) => {
const fromModuleName = getModuleName(c, from);

// After every routing operation, perform available cleanup
// of registered modules, keeping around up to a specified
// maximum
const minIndex = Math.max(moduleIndex - opts.maxVuexModules, 0);
registeredModules.forEach((m, idx) => {
if (m.index < minIndex) {
if (!toModuleNames.includes(m.name) && m.name !== fromModuleName) {
opts.logger.info('Unregistering dynamic Vuex module:', m.name);
store.unregisterModule(m.name);
registeredModules.splice(idx, 1);
} else {
// Not ready to be removed yet, still actively used
opts.logger.info(
'Skipping deregistration for active Vuex module:',
m.name,
);
}
}
});
});
.flatMap(c => c.vuex)
.map(vuexModuleDef => getModuleName(vuexModuleDef, from));

// Unregister any modules we queued for removal on the previous route
const requeueModules = [];
const { logger } = opts;
while (queuedRemovalModules.length > 0) {
// Unregister from the end of the queue, so we go upwards from child
// components to parent components in nested route scenarios
const name = queuedRemovalModules.pop();
const nameArr = name.split('/');
if ([...toModuleNames, ...fromModuleNames].includes(name)) {
// Can't remove yet - still actively used. Queue up for the next route
logger.info(`Skipping deregistration for active dynamic Vuex module: ${name}`);
requeueModules.push(name);
} else if (store.hasModule(nameArr)) {
logger.info(`Unregistering dynamic Vuex module: ${name}`);
store.unregisterModule(nameArr);
} else {
logger.info(`No existing dynamic module to unregister: ${name}`);
}
}

// Queue up the prior route modules for removal on the next route
const nextRouteRemovals = uniq([...requeueModules, ...fromModuleNames]);
// Sort by depth, so that we remove deeper modules first using .pop()
const sortedRouteRemovals = sortBy(nextRouteRemovals, [m => m.split('/').length]);
queuedRemovalModules.push(...sortedRouteRemovals);
});
}

Expand Down
16 changes: 6 additions & 10 deletions src/entry-server.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getModuleName, safelyRegisterModule } from './utils';

// Server side data loading approach based on:
// https://ssr.vuejs.org/en/data.html#client-data-fetching

Expand Down Expand Up @@ -37,16 +39,10 @@ export default function initializeServer(createApp, serverOpts) {
// bundle
components
.filter(c => 'vuex' in c)
.forEach((c) => {
// Allow a function to be passed that can generate a route-aware
// module name
const moduleName = typeof c.vuex.moduleName === 'function' ?
c.vuex.moduleName({ $route: router.currentRoute }) :
c.vuex.moduleName;
opts.logger.info('Registering dynamic Vuex module:', moduleName);
store.registerModule(moduleName, c.vuex.module, {
preserveState: store.state[moduleName] != null,
});
.flatMap(c => c.vuex)
.forEach((vuexModuleDef) => {
const name = getModuleName(vuexModuleDef, router.currentRoute);
safelyRegisterModule(store, name, vuexModuleDef.module, opts.logger);
});
}

Expand Down
24 changes: 24 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Allow a function to be passed that can generate a route-aware module name
export function getModuleName(vuexModuleDef, route) {
const name = typeof vuexModuleDef.moduleName === 'function' ?
vuexModuleDef.moduleName({ $route: route }) :
vuexModuleDef.moduleName;
return name;
}

// Return the namespaced module state
function getModuleState(store, nameArr) {
return nameArr.reduce((acc, k) => (acc ? acc[k] : null), store.state);
}

export function safelyRegisterModule(store, name, vuexModule, logger) {
const nameArr = name.split('/');
if (store.hasModule(nameArr)) {
logger.info(`Skipping duplicate dynamic Vuex module registration: ${name}`);
} else {
logger.info(`Registering dynamic Vuex module: ${name}`);
store.registerModule(nameArr, vuexModule, {
preserveState: getModuleState(store, nameArr) != null,
});
}
}

0 comments on commit 5bd47a6

Please sign in to comment.