router-tree
Version:
Create routes from directory structure
101 lines (81 loc) • 2.98 kB
JavaScript
/* --------------------
* router-tree module
* Convert array of routes to tree
* ------------------*/
;
// Modules
const sortRoutePaths = require('sort-route-paths');
// Imports
const {conformPath, resolveParentPath} = require('./paths'),
{traverse, values, isString} = require('./utils');
// Exports
/**
* Convert array of routes to tree
*
* @param {Array} routes - Array of route objects
* @param {*} [context] - Object to pass to routes' `.init()` methods
* @returns {Route} - Route tree
* @throws {Error} - If more than 1 file found for a path of a certain type,
* or no root, or more than 1 root
*/
module.exports = function(routes, context) {
// Init empty children object for each route
for (let route of routes) {
route.children = {};
}
// Get parent for each route
const orphans = [];
for (let route of routes) {
route.parentPath = getParentPath(route);
const parentPath = resolveParentPath(route.internalPath, route.parentPath);
if (parentPath === false) throw new Error(`Illegal parentPath at ${route.internalPath}`);
if (!parentPath) {
route.parent = null;
orphans.push(route);
continue;
}
// Find parent
/* jshint loopfunc: true */
const parent = routes.find(parent => parent.internalPath == parentPath);
if (!parent) throw new Error(`Cannot find parent ${parentPath} at ${route.internalPath}`);
route.parent = parent;
parent.children[route.name] = route;
}
// Check only one route with no parent
if (orphans.length == 0) throw new Error('No root route found');
if (orphans.length > 1) throw new Error(`${orphans.length} root routes found: ${orphans.map(r => r.internalPath).join(', ')}`);
const tree = orphans[0];
// Run `.init()` method on each route
traverse(tree, route => route.init(context));
// Get paths for each route - run `.initPath()` method on each route where not manually defined
traverse(tree, route => {
if (route.path == null) route.path = route.initPath();
if (!isString(route.path)) throw new Error(`path must be a string if defined at ${route.internalPath}`);
});
// Sort children of each route by path
traverse(tree, route => {
let childrenArr = values(route.children);
childrenArr = sortRoutePaths(childrenArr, 'path');
const children = {};
childrenArr.forEach(child => children[child.name] = child);
route.children = children;
});
// Return route tree
return tree;
};
function getParentPath(route) {
let {parentPath} = route;
// If set as `null` exit
if (parentPath === null) return null;
// If not set, set as default
if (parentPath === undefined) {
if (route.internalPath == '/') return null;
return './';
}
// Check is string
if (!isString(parentPath)) throw new Error(`parentPath must be a non-empty string if provided at ${route.internalPath}`);
// Conform path
parentPath = conformPath(parentPath);
if (parentPath === false) throw new Error(`Illegal parentPath at ${route.internalPath}`);
return parentPath;
}