trek-router
Advanced tools
Comparing version 0.1.4 to 0.1.5
@@ -12,10 +12,10 @@ 'use strict'; | ||
const METHODS = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']; | ||
const min = Math.min; | ||
const METHODS = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']; | ||
const STAR = 42; // '*' | ||
const SLASH = 47; // '/' | ||
const COLON = 58; // ':' | ||
const SNODE = 1; // Static node | ||
const PNODE = 2; // Param node | ||
const CNODE = 3; // Catch-all node | ||
/** | ||
@@ -27,9 +27,8 @@ * Node | ||
* @param {String} path | ||
* @param {Number} has | ||
* @param {Array} [children] | ||
* @param {Function|GeneratorFunction} handler | ||
* @param {Array} [edges] | ||
*/ | ||
var Node = (function () { | ||
function Node(prefix, has, handler, edges) { | ||
function Node(prefix, children, handler) { | ||
_classCallCheck(this, Node); | ||
@@ -39,9 +38,8 @@ | ||
this.prefix = prefix; | ||
this.has = has; | ||
this.children = children || []; | ||
this.handler = handler; | ||
this.edges = edges || []; | ||
} | ||
/** | ||
* Find edge by charCode | ||
* Find child by charCode | ||
* | ||
@@ -52,9 +50,9 @@ * @param {Number} char code | ||
Node.prototype.findEdge = function findEdge(c) { | ||
Node.prototype.findChild = function findChild(c) { | ||
var i = 0; | ||
var l = this.edges.length; | ||
var l = this.children.length; | ||
var e = undefined; | ||
for (; i < l; ++i) { | ||
e = this.edges[i]; | ||
e = this.children[i]; | ||
// Compare charCode | ||
@@ -85,3 +83,3 @@ if (e.label === c) return e; | ||
// Start from '/' | ||
_this.trees[m.toUpperCase()] = new Node('/', null, null, []); | ||
_this.trees[m.toUpperCase()] = new Node('/'); | ||
}); | ||
@@ -100,4 +98,2 @@ } | ||
Router.prototype.add = function add(method, path, handler) { | ||
// Count params | ||
var count = -1; | ||
// Store param keys | ||
@@ -109,37 +105,33 @@ var keys = []; | ||
var l = path.length; | ||
var ch = undefined; | ||
var ch = undefined, | ||
j = undefined; | ||
for (; i < l; ++i) { | ||
ch = path.charCodeAt(i); | ||
if (ch === 58 /*':'*/) { | ||
if (ch === COLON) { | ||
// Param start index | ||
var j = i + 1; | ||
count++; | ||
j = i + 1; | ||
this.insert(method, path.substring(0, i), null, PNODE); | ||
for (; i < l && path.charCodeAt(i) !== 47 /*'/'*/; ++i) {} | ||
this.insert(method, path.substring(0, i)); | ||
for (; i < l && path.charCodeAt(i) !== SLASH; ++i) {} | ||
// Create param key `$n` | ||
var param = '$' + count; | ||
var prefix = path.substring(0, j) + param; | ||
// Store original param key | ||
keys.push(path.substring(j, i)); | ||
// Override path | ||
path = prefix + path.substring(i); | ||
path = path.substring(0, j) + path.substring(i); | ||
// Override i, l | ||
i = prefix.length; | ||
i = j; | ||
l = path.length; | ||
if (i === l) { | ||
this.insert(method, path.substring(0, i), handler, 0); | ||
this.insert(method, path.substring(0, i), handler); | ||
return; | ||
} | ||
this.insert(method, path.substring(0, i), null, 0); | ||
} else if (ch === 42 /*'*'*/) { | ||
this.insert(method, path.substring(0, i), null, CNODE); | ||
this.insert(method, path.substring(0, l), handler, 0); | ||
this.insert(method, path.substring(0, i)); | ||
} else if (ch === STAR) { | ||
this.insert(method, path.substring(0, i)); | ||
this.insert(method, path.substring(0, l), handler); | ||
} | ||
} | ||
// unshift node | ||
this.insert(method, path, handler, SNODE, true); | ||
this.insert(method, path, handler); | ||
}; | ||
@@ -155,16 +147,17 @@ | ||
* @param {Function|GeneratorFunction} handler | ||
* @param {Number} has | ||
* @param {Boolean} [bool=false] if bool is true, unshift it to edges | ||
*/ | ||
Router.prototype.insert = function insert(method, path, handler, has) { | ||
var bool = arguments[4] === undefined ? false : arguments[4]; | ||
Router.prototype.insert = function insert(method, path, handler) { | ||
var cn = this.trees[method]; // Current node as root | ||
var search = path; | ||
var sl = undefined, | ||
pl = undefined, | ||
l = undefined, | ||
n = undefined, | ||
e = undefined; | ||
while (true) { | ||
var sl = search.length; | ||
var pl = cn.prefix.length; | ||
var l = lcp(search, cn.prefix); | ||
sl = search.length; | ||
pl = cn.prefix.length; | ||
l = lcp(search, cn.prefix); | ||
@@ -175,3 +168,2 @@ if (l === 0) { | ||
cn.prefix = search; | ||
cn.has = has; | ||
if (handler) { | ||
@@ -182,4 +174,4 @@ cn.handler = handler; | ||
// Split node | ||
var n = new Node(cn.prefix.substring(l), cn.has, cn.handler, cn.edges); | ||
cn.edges = [n]; // Add to parent | ||
n = new Node(cn.prefix.substring(l), cn.children, cn.handler); | ||
cn.children = [n]; // Add to parent | ||
@@ -189,4 +181,3 @@ // Reset parent node | ||
cn.prefix = cn.prefix.substring(0, l); | ||
cn.has = 0; | ||
cn.handler = null; | ||
cn.handler = undefined; | ||
@@ -197,9 +188,9 @@ if (l === sl) { | ||
} else { | ||
// Need to fork a node | ||
var _n = new Node(search.substring(l), has, handler); | ||
cn.edges[bool ? 'unshift' : 'push'](_n); | ||
// Create child node | ||
n = new Node(search.substring(l), [], handler); | ||
cn.children.push(n); | ||
} | ||
} else if (l < sl) { | ||
search = search.substring(l); | ||
var e = cn.findEdge(search.charCodeAt(0)); | ||
e = cn.findChild(search.charCodeAt(0)); | ||
if (e !== undefined) { | ||
@@ -211,4 +202,4 @@ // Go deeper | ||
// Create child node | ||
var n = new Node(search, has, handler); | ||
cn.edges[bool ? 'unshift' : 'push'](n); | ||
n = new Node(search, [], handler); | ||
cn.children.push(n); | ||
} else { | ||
@@ -235,80 +226,88 @@ // Node already exists | ||
Router.prototype.find = function find(method, path) { | ||
var cn = this.trees[method]; // Current node as root | ||
Router.prototype.find = function find(method, path, cn, n, result) { | ||
n = n || 0; // Param count | ||
cn = cn || this.trees[method]; // Current node as root | ||
result = result || [undefined, []]; | ||
// let n = 0; // Param count | ||
// let cn = this.trees[method]; // Current node as root | ||
// let result = [undefined, []]; | ||
var search = path; | ||
var n = 0; // Param count | ||
var result = Array(2); | ||
var params = result[1] = []; | ||
var params = result[1]; | ||
var pl = undefined, | ||
l = undefined, | ||
leq = undefined, | ||
e = undefined; | ||
var preSearch = undefined; // Pre search | ||
while (true) { | ||
// search ==== '' | ||
if (search.length === 0 || search === cn.prefix) { | ||
// Found | ||
result[0] = cn.handler; | ||
result[1] = params; | ||
if (cn.handler) { | ||
var keys = cn.handler.keys; | ||
for (var _i = 0, _l = keys.length; _i < _l; ++_i) { | ||
params[_i].name = keys[_i]; | ||
} | ||
if (search.length === 0 || search === cn.prefix) { | ||
// Found | ||
result[0] = cn.handler; | ||
result[1] = params; | ||
if (cn.handler !== undefined) { | ||
var keys = cn.handler.keys; | ||
var _i = 0, | ||
_l = keys.length; | ||
for (; _i < _l; ++_i) { | ||
params[_i].name = keys[_i]; | ||
} | ||
return result; | ||
} | ||
return result; | ||
} | ||
var pl = cn.prefix.length; | ||
var l = lcp(search, cn.prefix); | ||
var leq = l === pl; | ||
var e = undefined; | ||
pl = cn.prefix.length; | ||
l = lcp(search, cn.prefix); | ||
leq = l === pl; | ||
if (leq) { | ||
search = search.substring(l); | ||
} | ||
if (leq) { | ||
search = search.substring(l); | ||
} | ||
preSearch = search; | ||
// Search SNODE | ||
e = cn.findEdge(search.charCodeAt(0)); | ||
if (e !== undefined) { | ||
cn = e; | ||
continue; | ||
} | ||
// Static node | ||
e = cn.findChild(search.charCodeAt(0)); | ||
if (e !== undefined) { | ||
this.find(method, search, e, n, result); | ||
if (result[0] !== undefined) return result; | ||
search = preSearch; | ||
} | ||
// Not found SNODE, should return | ||
if (!leq) { | ||
return result; | ||
} | ||
// Not found static node | ||
if (!leq) { | ||
return result; | ||
} | ||
// Search PNODE | ||
e = cn.findEdge(58 /*':'*/); | ||
if (e !== undefined) { | ||
cn = e; | ||
l = search.length; | ||
for (var i = 0; i < l && search.charCodeAt(i) !== 47 /*'/'*/; i++) {} | ||
e = cn.findChild(COLON); | ||
if (e !== undefined) { | ||
l = search.length; | ||
for (var i = 0; i < l && search.charCodeAt(i) !== SLASH; i++) {} | ||
params[n] = { | ||
name: e.prefix.substring(1), | ||
value: search.substring(0, i) | ||
}; | ||
n++; | ||
params[n] = { | ||
name: e.prefix.substring(1), | ||
value: search.substring(0, i) | ||
}; | ||
search = search.substring(i); | ||
continue; | ||
} | ||
n++; | ||
preSearch = search; | ||
search = search.substring(i); | ||
// Search CNODE | ||
e = cn.findEdge(42 /*'*'*/); | ||
if (e !== undefined) { | ||
cn = e; | ||
// Catch-all node | ||
params[n] = { | ||
name: '_name', | ||
value: search | ||
}; | ||
search = ''; // End search | ||
} | ||
this.find(method, search, e, n, result); | ||
if (result[0] !== undefined) return result; | ||
if (search.length === 0) { | ||
continue; | ||
} | ||
n--; | ||
params.shift(); | ||
search = preSearch; | ||
} | ||
return result; | ||
// Catch-all node | ||
e = cn.findChild(STAR); | ||
if (e !== undefined) { | ||
params[n] = { | ||
name: '_name', | ||
value: search | ||
}; | ||
search = ''; // End search | ||
this.find(method, search, e, n, result); | ||
} | ||
return result; | ||
}; | ||
@@ -315,0 +314,0 @@ |
{ | ||
"name": "trek-router", | ||
"version": "0.1.4", | ||
"version": "0.1.5", | ||
"description": "A fast HTTP router", | ||
@@ -24,3 +24,3 @@ "repository": "trekjs/router", | ||
"devDependencies": { | ||
"babel": "^5.0.12", | ||
"babel": "^5.1.9", | ||
"benchmark": "^1.0.0", | ||
@@ -27,0 +27,0 @@ "isparta": "^3.x", |
@@ -26,13 +26,27 @@ # trek-router | ||
trek-router x 5,236 ops/sec ±0.37% (102 runs sampled) | ||
memoryUsage: { rss: 39653376, heapTotal: 31446272, heapUsed: 12252760 } | ||
path-to-regexp x 434 ops/sec ±3.19% (90 runs sampled) | ||
memoryUsage: { rss: 59383808, heapTotal: 50021120, heapUsed: 19588840 } | ||
route-recognizer x 336 ops/sec ±2.33% (92 runs sampled) | ||
memoryUsage: { rss: 62365696, heapTotal: 53104896, heapUsed: 20683920 } | ||
route-trie x 1,105 ops/sec ±4.55% (86 runs sampled) | ||
memoryUsage: { rss: 64360448, heapTotal: 54136832, heapUsed: 28538568 } | ||
routington x 1,237 ops/sec ±0.54% (100 runs sampled) | ||
memoryUsage: { rss: 67698688, heapTotal: 58252544, heapUsed: 18660040 } | ||
GitHub API, 203 routes: | ||
trek-router x 4,592 ops/sec ±3.45% (91 runs sampled) | ||
memoryUsage: { rss: 41283584, heapTotal: 32478208, heapUsed: 15547912 } | ||
path-to-regexp x 327 ops/sec ±5.95% (71 runs sampled) | ||
memoryUsage: { rss: 60223488, heapTotal: 50021120, heapUsed: 23526096 } | ||
route-recognizer x 274 ops/sec ±7.98% (76 runs sampled) | ||
memoryUsage: { rss: 62676992, heapTotal: 53104896, heapUsed: 20703720 } | ||
route-trie x 675 ops/sec ±5.15% (58 runs sampled) | ||
memoryUsage: { rss: 63909888, heapTotal: 54136832, heapUsed: 22530784 } | ||
routington x 675 ops/sec ±15.40% (56 runs sampled) | ||
memoryUsage: { rss: 65392640, heapTotal: 55168768, heapUsed: 27596440 } | ||
Fastest is trek-router | ||
Discourse API, 359 routes: | ||
trek-router x 2,566 ops/sec ±4.28% (71 runs sampled) | ||
memoryUsage: { rss: 66920448, heapTotal: 56200704, heapUsed: 23280456 } | ||
path-to-regexp x 44.88 ops/sec ±2.98% (60 runs sampled) | ||
memoryUsage: { rss: 68005888, heapTotal: 58252544, heapUsed: 38887896 } | ||
route-recognizer x 122 ops/sec ±5.24% (60 runs sampled) | ||
memoryUsage: { rss: 70610944, heapTotal: 61336320, heapUsed: 41447384 } | ||
route-trie x 746 ops/sec ±5.59% (68 runs sampled) | ||
memoryUsage: { rss: 72040448, heapTotal: 62368256, heapUsed: 40931128 } | ||
routington x 642 ops/sec ±7.77% (66 runs sampled) | ||
memoryUsage: { rss: 73596928, heapTotal: 63400192, heapUsed: 43505976 } | ||
Fastest is trek-router | ||
``` | ||
@@ -60,3 +74,3 @@ | ||
// => [handler, params] | ||
// => [null, []] | ||
// => [undefined, []] | ||
``` | ||
@@ -63,0 +77,0 @@ |
12790
95
267