UNPKG

en-route

Version:

Routing for static site generators, build systems and task runners, heavily based on express.js routes but works with file objects. Used by Assemble, Verb, and Template.

171 lines (148 loc) 3.85 kB
'use strict'; const Emitter = require('events'); const Layer = require('./layer'); /** * Create a new `Route` with the given pattern, handler functions and options. * * ```js * const fn = file => file.count++; * const Route = require('en-route').Route; * const route = new Route('/(.*)', [fn, fn, fn]); * const file = { path: '/foo', count: 0 }; * * route.handle(file) * .then(file => { * console.log(file.count); // 3 * }); * ``` * @name Route * @extends {Class} EventEmitter * @param {string|regex} `pattern` * @param {function|array} `fns` One or more middleware functions. * @param {object} `options` * @api public */ class Route extends Emitter { constructor(pattern, fns, options = {}) { super(); this.pattern = pattern; this.options = options; this.stack = []; this.layers(this.pattern, fns, this.options); if (this.options.sync) { this.handle = handle.bind(null, this); } } /** * Register one or more handler functions to be called on all * layers on the route. * * ```js * route.all(function(file) { * file.data.title = 'Home'; * }); * route.all([ * function(file) {}, * function(file) {} * ]); * ``` * @name .all * @param {function|array} `fns` Handler function or array of handler functions. * @return {object} Route instance for chaining * @api public */ all(fns) { return this.layers('/', fns); } /** * Run a middleware stack on the given `file`. * * ```js * route.handle(file) * .then(file => console.log('File:', file)) * .catch(console.error); * ``` * @name .handle * @param {object} `file` File object * @return {object} Callback that exposes `err` and `file` * @return {object} Returns a promise with the file object. * @api public */ async handle(file) { this.status = 'starting'; this.emit('handle', file); let pending = []; for (let layer of this.stack) { this.emit('layer', layer, file); if (this.options.parallel) { pending.push(layer.handle(file)); } else { await layer.handle(file); } } if (this.options.parallel) await Promise.all(pending); this.status = 'finished'; this.emit('handle', file); return file; } /** * Push a layer onto the stack for a middleware functions. * * ```js * route.layer(/foo/, file => { * // do stuff to file * file.layout = 'default'; * }); * ``` * @name .layer * @param {string|regex} `pattern` The pattern to use for matching files to determin if they should be handled. * @param {function|array} `fn` Middleware functions * @return {object} Route instance for chaining * @api public */ layer(pattern, fn) { this.stack.push(new Layer(pattern, fn, this.options)); return this; } /** * Push a layer onto the stack for one or more middleware functions. * * ```js * route.layers(/foo/, function); * route.layers(/bar/, [function, function]); * ``` * @name .layers * @param {string|regex} `pattern` * @param {function|array} `fns` One or more middleware functions * @return {object} Route instance for chaining * @api public */ layers(pattern, fns) { for (const fn of arrayify(fns)) this.layer(pattern, fn); return this; } } /** * Sync method, used when options.sync is true */ function handle(route, file) { route.status = 'starting'; route.emit('handle', file); for (let layer of route.stack) { route.emit('layer', layer, file); layer.handle(file); } route.status = 'finished'; route.emit('handle', file); return file; } /** * Cast `val` to an array */ function arrayify(val) { return val ? (Array.isArray(val) ? val : [val]) : []; } /** * Expose `Route` */ module.exports = Route;