I’m not sure if it’s the same thing as you want, but this is what I have done on an app that I’m working on to make it easier to share development. Might give you some ideas.
TL;DR
I only load up subapps that are enabled, and all the individual subapps manage their own routing/components/store and are loaded asynchronously, and bundled into the same js file.
Structure:
apps
|-- users
|-- components
|-- features
|-- billing
|-- components
|-- ListScreen.vue
|-- CreateScreen.vue
|-- routes.js
|-- store
|-- mutations.js
|-- actions.js
|-- index.js
|-- services
|-- jobs
|-- config.js
|-- App2
|-- App3
|-- ...
|-- index.js
root
|-- Header.vue
|-- Navigation.vue
|-- ...
...
So each subapp has it’s own shared components, and then the individual parts of that app are broken down into features which control their own store and routing.
app/users/config.js
export default {
// Is this app active?
enabled: true,
// App namespace
ns: 'billing',
// The name of this app
name: 'Bills',
// Alternative title
title: 'My Bills',
// Icon to use to identify this app
icon: 'money',
};
apps/users/features/billing/routes.js
const ListScreen = () =>
import(/* webpackChunkName: "users-billing" */ './components/ListScreen');
const CreateScreen = () =>
import(/* webpackChunkName: "users-billing" */ './components/CreateScreen');
export default [
{
nav: true, // Is this route shown in the main nav
icon: 'money',
label: 'Bills',
name: 'billing_list',
path: 'bills',
component: ListScreen,
},
{
name: 'billing_create',
path: 'billing',
component: CreateScreen,
},
];
The main part is the index.js in apps that loads up any enabled apps and creates the routing etc for the whole spa. This way I can disable any individual app by changing the value in the config.js, and no routes or components are loaded for it.
apps/index.js
let appRoutes = []; // eslint-disable-line
let appNavigation = []; // eslint-disable-line
const getAppRouting = () => appRoutes;
const getPrimaryNavigation = () => appNavigation;
const configRequire = require.context('apps', true, /\.\/[^/]+\/config\.js$/);
const routesRequire = require.context('apps', true, /\.\/[^/]+\/features\/[^/]+\/routes\.js$/);
configRequire.keys().forEach((configPath) => {
const appPath = configPath.split('/')[1];
const appConfig = configRequire(configPath).default;
// Bail out if we have turned off for some reason
if (!appConfig.enabled) return;
const routes = [];
const navigation = {
ns: appConfig.ns,
name: appConfig.name,
title: appConfig.title,
icon: appConfig.icon,
children: [],
};
routesRequire.keys().forEach((routePath) => {
const routeAppPath = routePath.split('/')[1];
// Bail if not belonging to app
if (appPath !== routeAppPath) return;
routesRequire(routePath).default.forEach((route) => {
// Update the name and path with the app namespace prefix
routes.push({
...route,
name: `${appConfig.ns}__${route.name}`,
path: `/${appConfig.ns}/${route.path}`,
});
// Should this be shown in the nav?
if (route.nav) {
navigation.children.push({
route: `${appConfig.ns}__${route.name}`,
label: route.label,
icon: route.icon,
});
}
});
});
appRoutes = appRoutes.concat(routes);
if (navigation) {
// Make the app features alphabetical for nicencess
navigation.children.sort((a, b) => (a.label > b.label) ? 1 : 0);
appNavigation.push(navigation);
}
});
// Make the apps alphabetical for niceness
appNavigation.sort((a, b) => (a.label > b.label) ? 1 : 0);
export {
getAppRouting,
getPrimaryNavigation,
};
Then I use the routes like this:
import { getAppRouting } from 'apps';
const router = new Router({
mode: 'history',
base: 'webapp',
routes: [
...getAppRouting(),
{ path: '*', component: NotFoundComponent },
],
});