@kitmi/jacaranda
Version:
JavaScript application framework
132 lines (111 loc) • 4.48 kB
JavaScript
/**
* Enable routing web requests to a child app.
* @module Feature_AppRouting
*
* @example
*
* 'appRouting': {
* '<mounting point>': {
* name: 'app name',
* npmModule: false, // whether is a npm module
* options: { // module options
* },
* settings: { // can override module defined settings
* },
* middlewares: { // can override middlewares
* }
* }
* }
*/
import path from 'node:path';
import { _, batchAsync_, eachAsync_, pushIntoBucket } from '@kitmi/utils';
import { InvalidConfiguration, ServerError } from '@kitmi/types';
import { TopoSort } from '@kitmi/algo';
import Feature from '../Feature';
import WebModule from '../WebModule';
export default {
/**
* This feature is loaded at plugin stage.
* @member {string}
*/
stage: Feature.PLUGIN,
/**
* Load the feature.
* @param {WebServer} server - The web server module object.
* @param {object} routes - Routes and configuration.
* @returns {Promise.<*>}
*/
load_: async (server, routes) => {
const topoSort = new TopoSort();
const modules = {};
await batchAsync_(routes, async (config, baseRoute) => {
if (!config.name) {
throw new InvalidConfiguration('Missing app name.', app, `appRouting.${baseRoute}.name`);
}
let options = {
configType: server.options.configType,
logLevel: server.options.logLevel,
traceMiddlewares: server.options.traceMiddlewares,
logMiddlewareRegistry: server.options.logMiddlewareRegistry,
sourcePath: server.options.sourcePath,
...config.options,
};
let { appPath, moduleMeta } = await server.tryLoadModule_(
config,
config.name,
server.appModulesPath,
`appRouting.[${baseRoute}]`
);
if (!moduleMeta) {
moduleMeta = {};
}
let app = new WebModule(server, config.name, baseRoute, appPath, {
...options,
...moduleMeta.options,
registry: moduleMeta.registry,
});
app.once('configLoaded', () => {
if (!_.isEmpty(config.overrides)) {
Object.assign(app.config, config.overrides);
server.log('verbose', 'App config is overrided.');
}
if (!_.isEmpty(config.settings)) {
app.config.settings = Object.assign({}, app.config.settings, config.settings);
server.log('verbose', `App settings of [${app.name}] is overrided.`);
}
if (!_.isEmpty(config.middlewares)) {
let middlewaresToAppend = app.config.middlewares;
app.config.middlewares = { ...config.middlewares };
_.defaults(app.config.middlewares, middlewaresToAppend);
}
});
pushIntoBucket(modules, config.name, { appPath, app });
if (config.depends) {
topoSort.depends(config.name, config.depends);
} else if (moduleMeta.depends) {
topoSort.depends(config.name, moduleMeta.depends);
} else {
topoSort.add(config.name);
}
});
const sorted = topoSort.sort();
server.once('after:' + Feature.PLUGIN, async () => {
await eachAsync_(sorted, async (name) => {
const bucket = modules[name];
if (bucket == null) {
throw new ServerError(`App [${name}] not exist.`);
}
return batchAsync_(bucket, async ({ appPath, app }) => {
let relativePath = path.relative(server.workingPath, appPath);
server.log('verbose', `Loading app [${app.name}] from "${relativePath}" ...`);
await app.start_();
server.log('verbose', `App [${app.name}] is loaded.`);
//delayed the app routes mounting after all plugins of the server are loaded
server.on('before:' + Feature.FINAL, () => {
server.mountApp(app);
});
});
});
});
},
};