router-tree
Version:
Create routes from directory structure
166 lines (139 loc) • 4.85 kB
JavaScript
/* --------------------
* router-tree module
* Load files in folder
* ------------------*/
;
// Imports
const Route = require('./route'),
{conformPath, resolvePath} = require('./paths'),
{findBefore} = require('./utils');
// Exports
/**
* Load route files and return array of routes.
*
* @param {string} loadPath - Folder path (without trailing '/')
* @param {Array} groups - Array of group objects (each of form `{ path: '/a', files: { route: 'js', react: 'jsx' } }`)
* @param {Object} options - Options object
* @param {Class} options.defaultRouteClass - Class to create empty routes from
* @returns {Array} - Array of grouped paths
* @throws {Error} - If more than 1 file found for a path of a certain type
*/
module.exports = function(loadPath, groups, options) {
// Get internal paths, convert files to full paths,
// and consolidate duplicate groups with same internal path
for (let index = 0; index < groups.length; index++) {
const group = groups[index],
{path, files} = group;
// Get internal path
const internalPath = getInternalPath(path);
group.internalPath = internalPath;
// Find duplicate group with same internal path
/* jshint loopfunc: true */
const groupDup = findBefore(groups, g => g.internalPath == internalPath, index);
if (!groupDup) continue;
// Consolidate files objects
const filesDup = groupDup.files;
for (let type in files) {
if (filesDup[type]) throw new Error(`Multiple ${type} files for path '${internalPath}': ${filesDup[type]} and ${files[type]}`);
filesDup[type] = files[type];
}
// Remove redundant group
groups.splice(index, 1);
index--;
}
// Load route files + process companions
const RouteClass = options.defaultRouteClass;
for (let group of groups) {
// Load route file
const sourcePath = group.files.route;
if (!sourcePath) continue;
let route = require(`${loadPath}${sourcePath}`);
route = makeRoute(route, RouteClass);
route.sourcePath = sourcePath;
group.route = route;
// Process companions
addCompanions(route, group.internalPath, groups, RouteClass);
}
// Add default routes and return array of routes
return groups.map(group => {
// If no route, make default route
let {route, internalPath} = group;
if (!route) {
route = new RouteClass();
route.sourcePath = null;
}
// Set route internalPath + name + files
route.internalPath = internalPath;
route.name = getNameFromPath(internalPath);
route.files = group.files;
// Return route
return route;
});
};
/**
* Process companions
* @param {Route} route - Route object
* @param {string} path - internalPath of route
* @param {Array} groups - groups array
* @param {Class} RouteClass - Class to use for creating new routes
* @returns {undefined}
*/
function addCompanions(route, path, groups, RouteClass) {
const {companions} = route;
if (!companions) return;
for (let companionPath in companions) {
// Conform path
const relativePath = conformPath(companionPath);
if (relativePath === false || relativePath == './') throw new Error(`Illegal companion '${companionPath}' at ${path}`);
// Resolve companion route internalPath
const internalPath = resolvePath(path, relativePath);
if (internalPath === false) throw new Error(`Illegal companion '${companionPath}' at ${path}`);
// Add to existing group or make new group
/* jshint loopfunc: true */
const existingGroup = groups.find(g => g.internalPath == internalPath);
// If existing route file, it takes precedence over route defined as companion
if (existingGroup && existingGroup.files.route) continue;
let route = companions[companionPath];
route = makeRoute(route, RouteClass);
route.sourcePath = null;
if (!existingGroup) {
groups.push({path: null, internalPath, route, files: {}});
} else {
existingGroup.route = route;
}
// Process companion route's companions
addCompanions(route, internalPath, groups, RouteClass);
}
}
/**
* Make route from object.
* If is a Route object already, return unchanged.
* Otherwise, make Route object.
*
* @param {*} route - Loaded route object
* @param {Class} RouteClass - Default route class
* @returns {Route}
*/
function makeRoute(route, RouteClass) {
if (route instanceof Route) return route;
return new RouteClass(route);
}
/**
* Remove '/index' from end of path
* @param {string} path - Full path (path to file without file ext)
* @returns {string} - Internal path
*/
function getInternalPath(path) {
if (path == '/index') return '/';
if (path.slice(-6) == '/index') return path.slice(0, -6);
return path;
}
/**
* Extract name from path
* @param {string} path - Route's internal path
* @returns {string} - Route name
*/
function getNameFromPath(path) {
if (path == '/') return 'root';
return path.slice(path.lastIndexOf('/') + 1);
}