UNPKG

express-routemagic

Version:

A simple and fast, fire-and-forget module that all Nodejs+Express app should have, to automatically require all your express routes without bloating your code with `app.use('i/will/repeat/this', require('./i/will/repeat/this')`. 把 Express 路由图给自动化。

230 lines (194 loc) 7.28 kB
/*! * Express Route-Magic v2.0.8 * (c) 2021 Calvin Tan * Released under the MIT License. */ 'use strict' // modules const fs = require('fs') const path = require('path') const hlp = require('./lib/helpers.js') // defaults const Magic = { _moduleName: 'express-routemagic', _routesFolder: 'routes', _allowSameName: false, _debug: console.log, _logMapping: false, _invokerPath: path.join(__dirname, './../../') } // properties that require getters and setters Object.defineProperties(Magic, { invokerPath: { get() { return this._invokerPath }, set(val) { let fail = hlp.argFail('string', val, 'invokerPath', 'The path of where you invoked magic must be a valid `string`. Typically it is `__dirname`.') if (fail) throw new Error(fail) this._invokerPath = val } }, routesFolder: { get() { return this._routesFolder }, set(val) { let fail = hlp.argFail('string', val, 'routesFolder', 'This value defaults to \'routes\'. If you change your folder structure to follow you won\'t need this option.') if (fail) throw new Error(fail) if (val[val.length - 1] === '/' || val[val.length - 1] === '\\') val = val.substring(0, val.length - 1) this._routesFolder = val } }, ignoreSuffix: { get() { return this._ignoreSuffix }, set(val) { let fail = hlp.argFail(['string', 'array'], val, 'ignoreSuffix') if (fail) throw new Error(fail) this._ignoreSuffix = Array.isArray(val) ? val : [val] } }, allowSameName: { get() { return this._allowSameName }, set(val) { let fail = hlp.argFail('boolean', val, 'allowSameName') if (fail) throw new Error(fail) this._allowSameName = val } }, logMapping: { get() { return this._logMapping }, set(val) { let fail = hlp.argFail('boolean', val, 'logMapping') if (fail) throw new Error(fail) this._logMapping = val } }, debug: { get() { return this._debug }, set(val) { let fail = hlp.argFail('function', val, 'debug') if (fail) throw new Error(fail) this._debug = val } } }) // methods Magic.use = function(app, relativeRoutesFolderOrOptions) { if (!app) throw new Error('Invalid argument: Express `app` instance must be passed in as 1st argument.') this.app = app if (!hlp.argFail('string', relativeRoutesFolderOrOptions)) { this.routesFolder = path.normalize(relativeRoutesFolderOrOptions) } else if (!hlp.argFail('object', relativeRoutesFolderOrOptions)) { let options = relativeRoutesFolderOrOptions // may need debugging module downstream, so assign first. if (options.debug) this.debug = options.debug if (relativeRoutesFolderOrOptions.routesFolder) relativeRoutesFolderOrOptions.routesFolder = path.normalize(relativeRoutesFolderOrOptions.routesFolder) if (relativeRoutesFolderOrOptions.invokerPath) relativeRoutesFolderOrOptions.invokerPath = path.normalize(relativeRoutesFolderOrOptions.invokerPath) hlp.applyOpts(this, options, [ 'routesFolder', 'ignoreSuffix', 'allowSameName', 'debug', 'logMapping', 'invokerPath' ]) } this.scan(this.absolutePathToRoutesFolder()) } Magic.scan = function(directory) { let _folders = [] let _files = [] if (!fs.existsSync(directory)) throw new Error(`Routes folder not found in: ${directory}`) fs.readdirSync(directory).filter(file => { // ignore hidden file if (file.indexOf('.') === 0) return false // directory if (fs.lstatSync(path.join(directory, '/', file)).isDirectory()) { this.push(_folders, file, true) return false } // js files return ( (file.indexOf('.js') === file.length - '.js'.length) || (file.indexOf('.ts') === file.length - '.ts'.length) ) }).forEach(file => { if (['index.js', 'index.ts'].indexOf(file) > -1) { _files.unshift(file) } else { this.push(_files, file) } }) this.checkConflict(_files, _folders, directory) // require this.require(directory, _files) // scan folders _folders.forEach(folder => { this.scan(path.join(directory, '/', folder)) }) } Magic.push = function(array, payload, isDirectory) { if (!this.toIgnore(payload, isDirectory)) array.push(payload) } Magic.toIgnore = function(payload, isDirectory) { if (!isDirectory) payload = payload.substring(0, payload.length - 3) // remove the extension let toIgnore = false if (this.ignoreSuffix) { this.ignoreSuffix.forEach(suffix => { if (payload.indexOf(suffix) !== -1 && payload.indexOf(suffix) === payload.length - suffix.length) { toIgnore = true return null } }) } return toIgnore } Magic.checkConflict = function(files, folders, directory) { if (this.allowSameName) return false files.forEach(file => { if (folders.indexOf(file.substring(0, file.length - 3)) !== -1) throw new Error(`Folder and file with conflict name: \`${file.substring(0, file.length - 3)}\` in directory: \`${directory}\`.`) }) } Magic.require = function(dir, files) { let apiDirectory = this.apiDirectory(dir) files.forEach(file => { let apiPath = this.apiPath(file, apiDirectory) let route = require(this.absolutePathFile(dir, file)) // ES6 export defauly compatibility if (typeof route !== 'function') route = route.default this.app.use(apiPath, route) if (this.logMapping) this.debug(apiPath + ' => .' + this.pathRelativeToInvoker(dir, file)) }) } Magic.apiDirectory = function(dir) { let apiDir = path.relative(this.absolutePathToRoutesFolder(), dir) return (apiDir.length === 0) ? path.normalize('/') : path.normalize(path.join('/', apiDir)) } Magic.apiPath = function(file, apiDir) { apiDir = path.normalize(apiDir) // TODO: To support passing array to // have to check whether the apiDir have any commas. if yes can indicate a ['/route1', '/route2'] kind. // also need to check if file have any commans. if yes can indicate a ['/route1/filename1', '/route2/filename1', '/route1/filename2', '/route2/filename2'] kind of situation. let apiPath = (['index.js', 'index.ts'].indexOf(file) > -1) ? apiDir : path.join(apiDir, file.substring(0, file.length - 3)) apiPath = apiPath.replace(/\\/g, '/') return apiPath } Magic.absolutePathToRoutesFolder = function() { return path.join(this.invokerPath, this.routesFolder) } Magic.absolutePathFile = function(dir, file) { return path.join(dir, file) } Magic.pathRelativeToInvoker = function (dir, file) { return path.join(path.relative(this.invokerPath, dir), file) } module.exports = Object.create(Magic)