From 5aa17b482fe86cf974fd10d4ca41235af29e11e7 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 22 Jan 2025 22:36:05 +0800 Subject: [PATCH 1/3] feat: support custom pathToRegexpModule --- README.md | 7 +++++++ lib/layer.js | 31 +++++++++++++++++++++++++------ lib/router.js | 22 ++++++++++++++++------ package.json | 6 +++--- test/lib/egg_router.test.js | 25 +++++++++++++++++++++++++ test/lib/layer.test.js | 30 ++++++++++++++++++++++++++---- 6 files changed, 102 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0d20f67..75b2cd3 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Router core component for [Egg.js](https://github.com/eggjs). - [Router.url(path, params \[, options\]) ⇒ String](#routerurlpath-params--options--string) - [Tests](#tests) - [License](#license) + - [Contributors](#contributors) @@ -444,3 +445,9 @@ Run tests using `npm test`. ## License [MIT](LICENSE) + +## Contributors + +[![Contributors](https://contrib.rocks/image?repo=eggjs/router)](https://github.com/eggjs/router/graphs/contributors) + +Made with [contributors-img](https://contrib.rocks). diff --git a/lib/layer.js b/lib/layer.js index 4fe675b..626ff85 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -1,7 +1,8 @@ 'use strict'; const debug = require('util').debuglog('egg-router:layer'); -const pathToRegExp = require('path-to-regexp'); +const assert = require('assert'); +const pathToRegexpModule = require('path-to-regexp'); const uri = require('urijs'); const utility = require('utility'); @@ -16,10 +17,15 @@ module.exports = class Layer { * @param {String=} opts.name route name * @param {String=} opts.sensitive case sensitive (default: false) * @param {String=} opts.strict require the trailing slash (default: false) + * @param {Object=} opts.pathToRegexpModule custom path-to-regexp module * @private */ constructor(path, methods, middleware, opts) { this.opts = opts || {}; + this._pathToRegexpModule = this.opts.pathToRegexpModule || pathToRegexpModule; + // support path-to-regexp@8 and path-to-regexp@1 + this._pathToRegexp = this._pathToRegexpModule.pathToRegexp || this._pathToRegexpModule; + assert(typeof this._pathToRegexp === 'function', 'opts.pathToRegexpModule.pathToRegexp must be a function'); this.name = this.opts.name || null; this.methods = []; this.paramNames = []; @@ -44,11 +50,22 @@ module.exports = class Layer { }, this); this.path = path; - this.regexp = pathToRegExp(path, this.paramNames, this.opts); - + const { regexp, keys } = this._convertPathToRegexp(path, this.opts); + this.regexp = regexp; + this.paramNames = keys; debug('defined route %s %s', this.methods, this.opts.prefix + this.path); } + _convertPathToRegexp(path, options) { + let regexp = this._pathToRegexp(path, [], options); + const keys = regexp.keys; + if (regexp.regexp) { + // support path-to-regexp@8 + regexp = regexp.regexp; + } + return { regexp, keys }; + } + /** * Returns whether request `path` matches route. * @@ -112,7 +129,7 @@ module.exports = class Layer { url(params, options) { let args = params; const url = this.path.replace(/\(\.\*\)/g, ''); - const toPath = pathToRegExp.compile(url); + const toPath = this._pathToRegexpModule.compile(url); if (typeof params !== 'object') { args = Array.prototype.slice.call(arguments); @@ -122,7 +139,7 @@ module.exports = class Layer { } } - const tokens = pathToRegExp.parse(url); + const tokens = this._pathToRegexpModule.parse(url); let replace = {}; if (args instanceof Array) { @@ -209,7 +226,9 @@ module.exports = class Layer { if (this.path) { this.path = prefix + this.path; this.paramNames = []; - this.regexp = pathToRegExp(this.path, this.paramNames, this.opts); + const { regexp, keys } = this._convertPathToRegexp(this.path, this.opts); + this.regexp = regexp; + this.paramNames = keys; } return this; diff --git a/lib/router.js b/lib/router.js index 2597ae4..0e47891 100644 --- a/lib/router.js +++ b/lib/router.js @@ -8,6 +8,7 @@ const debug = require('util').debuglog('egg-router'); const compose = require('koa-compose'); const HttpError = require('http-errors'); const methods = require('methods'); +const pathToRegexpModule = require('path-to-regexp'); const Layer = require('./layer'); /** @@ -86,6 +87,7 @@ class Router { * @param {Function} middleware middleware function * @return {Router} router instance */ + use(/* path, middleware */) { const router = this; const middleware = Array.prototype.slice.call(arguments); @@ -172,7 +174,9 @@ class Router { ctx.router = router; - if (!matched.route) return next(); + if (!matched.route) { + return next(); + } const matchedLayers = matched.pathAndMethod; const layerChain = matchedLayers.reduce(function(memo, layer) { @@ -303,6 +307,7 @@ class Router { * @return {Router} router instance * @private */ + all(name, path/* , middleware */) { let middleware; @@ -394,6 +399,7 @@ class Router { strict: opts.strict || this.opts.strict || false, prefix: opts.prefix || this.opts.prefix || '', ignoreCaptures: opts.ignoreCaptures, + pathToRegexpModule: this.opts.pathToRegexpModule, }); if (this.opts.prefix) { @@ -462,6 +468,7 @@ class Router { * @param {Object|String} [options.query] query options * @return {String|Error} string or error instance */ + url(name/* , params */) { const route = this.route(name); @@ -497,14 +504,16 @@ class Router { for (let len = layers.length, i = 0; i < len; i++) { layer = layers[i]; - debug('test %s %s', layer.path, layer.regexp); + debug('test %s %o', layer.path, layer.regexp); if (layer.match(path)) { matched.path.push(layer); if (layer.methods.length === 0 || layer.methods.includes(method)) { matched.pathAndMethod.push(layer); - if (layer.methods.length) matched.route = true; + if (layer.methods.length) { + matched.route = true; + } } // if (layer.methods.length === 0) { // matched.pathAndMethod.push(layer); @@ -563,7 +572,7 @@ class Router { * Match URL patterns to callback functions or controller actions using `router.verb()`, * where **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`. * - * Additionaly, `router.all()` can be used to match against all methods. + * Additionally, `router.all()` can be used to match against all methods. * * ```javascript * router @@ -706,7 +715,7 @@ Router.prototype.del = Router.prototype.delete; * @example * * ```javascript - * var url = Router.url('/users/:id', {id: 1}); + * var url = Router.url('/users/:id', { id: 1 }); * // => "/users/1" * ``` * @@ -714,9 +723,10 @@ Router.prototype.del = Router.prototype.delete; * @param {Object} params url parameters * @return {String} url string */ + Router.url = function(path/* , params */) { const args = Array.prototype.slice.call(arguments, 1); - return Layer.prototype.url.apply({ path }, args); + return Layer.prototype.url.apply({ path, _pathToRegexpModule: pathToRegexpModule }, args); }; Router.prototype.middleware = Router.prototype.routes; diff --git a/package.json b/package.json index fecdf35..ae668d8 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "node": ">= 8.5.0" }, "publishConfig": { - "access": "public", - "tag": "release-2.x" + "access": "public" }, "description": "Router middleware for egg/koa. Provides RESTful resource routing.", "repository": { @@ -46,7 +45,8 @@ "koa": "^2.7.0", "mocha": "^2.0.1", "should": "^6.0.3", - "supertest": "^1.0.1" + "supertest": "^1.0.1", + "path-to-regexp-v8": "npm:path-to-regexp@8" }, "scripts": { "test-local": "egg-bin test", diff --git a/test/lib/egg_router.test.js b/test/lib/egg_router.test.js index b2e1e5a..daed612 100644 --- a/test/lib/egg_router.test.js +++ b/test/lib/egg_router.test.js @@ -186,4 +186,29 @@ describe('test/lib/egg_router.test.js', () => { assert(router.pathFor('fooo') === ''); assert(router.pathFor('hello') === '/hello/world'); }); + + it('should router.url work with pathToRegexpModule', () => { + const app = { + controller: { + async foo() { return; }, + hello: { + * world() { return; }, + }, + }, + }; + const router = new EggRouter({ + pathToRegexpModule: require('path-to-regexp-v8'), + }, app); + router.get('post', '/post/:id', app.controller.foo); + router.get('hello', '/hello/world', app.controller.hello.world); + + assert(router.url('post', { id: 1, foo: [ 1, 2 ], bar: 'bar' }) === '/post/1?foo=1&foo=2&bar=bar'); + assert(router.url('post', { foo: [ 1, 2 ], bar: 'bar' }) === '/post/:id?foo=1&foo=2&bar=bar'); + assert(router.url('fooo') === ''); + assert(router.url('hello') === '/hello/world'); + + assert(router.pathFor('post', { id: 1, foo: [ 1, 2 ], bar: 'bar' }) === '/post/1?foo=1&foo=2&bar=bar'); + assert(router.pathFor('fooo') === ''); + assert(router.pathFor('hello') === '/hello/world'); + }); }); diff --git a/test/lib/layer.test.js b/test/lib/layer.test.js index 536f9ec..2cda9f5 100644 --- a/test/lib/layer.test.js +++ b/test/lib/layer.test.js @@ -8,7 +8,7 @@ const Router = require('../../lib/router'); const Layer = require('../../lib/layer'); describe('test/lib/layer.test.js', function() { - it('composes multiple callbacks/middlware', function(done) { + it('composes multiple callbacks/middleware', function(done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); @@ -53,7 +53,29 @@ describe('test/lib/layer.test.js', function() { }); }); - it('return orginal path parameters when decodeURIComponent throw error', function(done) { + it('captures URL path parameters with pathToRegexpModule', function(done) { + const app = new Koa(); + const router = new Router({ + pathToRegexpModule: require('path-to-regexp-v8'), + }); + app.use(router.routes()); + router.get('/:category/:title', function(ctx) { + ctx.should.have.property('params'); + ctx.params.should.be.type('object'); + ctx.params.should.have.property('category', 'match'); + ctx.params.should.have.property('title', 'this'); + ctx.status = 204; + }); + request(http.createServer(app.callback())) + .get('/match/this') + .expect(204) + .end(function(err) { + if (err) return done(err); + done(); + }); + }); + + it('return original path parameters when decodeURIComponent throw error', function(done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); @@ -94,7 +116,7 @@ describe('test/lib/layer.test.js', function() { }); }); - it('return orginal ctx.captures when decodeURIComponent throw error', function(done) { + it('return original ctx.captures when decodeURIComponent throw error', function(done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); @@ -118,7 +140,7 @@ describe('test/lib/layer.test.js', function() { }); }); - it('populates ctx.captures with regexp captures include undefiend', function(done) { + it('populates ctx.captures with regexp captures include undefined', function(done) { const app = new Koa(); const router = new Router(); app.use(router.routes()); From c8af0f5487922745791763565d87bf5bed14e91d Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 22 Jan 2025 22:54:00 +0800 Subject: [PATCH 2/3] f --- test/lib/egg_router.test.js | 4 ++++ test/lib/layer.test.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/lib/egg_router.test.js b/test/lib/egg_router.test.js index daed612..58e082e 100644 --- a/test/lib/egg_router.test.js +++ b/test/lib/egg_router.test.js @@ -188,6 +188,10 @@ describe('test/lib/egg_router.test.js', () => { }); it('should router.url work with pathToRegexpModule', () => { + // Not working on Node.js v8 + // SyntaxError: Invalid regular expression: /^[$_\p{ID_Start}]$/: Invalid escape + if (process.version.startsWith('v8.')) return; + const app = { controller: { async foo() { return; }, diff --git a/test/lib/layer.test.js b/test/lib/layer.test.js index 2cda9f5..e0ac819 100644 --- a/test/lib/layer.test.js +++ b/test/lib/layer.test.js @@ -54,6 +54,10 @@ describe('test/lib/layer.test.js', function() { }); it('captures URL path parameters with pathToRegexpModule', function(done) { + // Not working on Node.js v8 + // SyntaxError: Invalid regular expression: /^[$_\p{ID_Start}]$/: Invalid escape + if (process.version.startsWith('v8.')) return; + const app = new Koa(); const router = new Router({ pathToRegexpModule: require('path-to-regexp-v8'), From 73f5514ccf888ad3c5b02d78c3cc0c55e554c8aa Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 22 Jan 2025 22:59:53 +0800 Subject: [PATCH 3/3] f --- test/lib/layer.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/layer.test.js b/test/lib/layer.test.js index e0ac819..edb4ad6 100644 --- a/test/lib/layer.test.js +++ b/test/lib/layer.test.js @@ -56,7 +56,7 @@ describe('test/lib/layer.test.js', function() { it('captures URL path parameters with pathToRegexpModule', function(done) { // Not working on Node.js v8 // SyntaxError: Invalid regular expression: /^[$_\p{ID_Start}]$/: Invalid escape - if (process.version.startsWith('v8.')) return; + if (process.version.startsWith('v8.')) return done(); const app = new Koa(); const router = new Router({