rlite (v3)

Revision 3 of this benchmark created on


Description

Testing the performance of various router implementations

Preparation HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/Director/1.2.8/director.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.0/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/page.js/1.6.3/page.min.js"></script>
<!--Required for crossroads-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-signals/1.0.0/js-signals.min.js"></script>
<script 
src="https://cdnjs.cloudflare.com/ajax/libs/crossroads/0.12.1/crossroads.min.js"></script>
<script src="https://cdn.rawgit.com/dstillman/pathparser.js/master/pathparser.min.js"></script>
<script>
function Prev() {
  var routes = {},
      decode = decodeURIComponent;

  function noop(s) { return s; }

  function sanitize(url) {
    url.indexOf('/?') >= 0 && (url = url.replace('/?', '?'));
    url[0] == '/' && (url = url.slice(1));
    url[url.length - 1] == '/' && (url = url.slice(0, -1));

    return url;
  }

  function processUrl(url) {
    var esc = url.indexOf('%') >= 0 ? decode : noop,
        pieces = url.split('/'),
        rules = routes,
        params = {};

    for (var i = 0; i < pieces.length && rules; ++i) {
      var piece = esc(pieces[i]);
      rules = rules[piece.toLowerCase()] || rules[':'];
      rules && rules[':'] && (params[rules[':']] = piece);
    }

    return rules && {
      cb: rules['@'],
      params: params
    };
  }

  function processQuery(url, params) {
    if (url) {
      var hash = url.indexOf('#'),
          esc = url.indexOf('%') >= 0 ? decode : noop,
          query = hash >= 0
                  ? url.slice(0, hash).split('&')
                  : url.split('&');

      for (var i = 0; i < query.length; ++i) {
        var nameValue = query[i].split('=');

        params[nameValue[0]] = esc(nameValue[1]);
      }
    }

    return params;
  }

  return {
    add: function(route, handler) {
      var pieces = route.toLowerCase().split('/'),
          rules = routes;

      for (var i = 0; i < pieces.length; ++i) {
        var piece = pieces[i],
            name = piece[0] == ':' ? ':' : piece;

        rules = rules[name] || (rules[name] = {});

        name == ':' && (rules[':'] = piece.slice(1));
      }

      rules['@'] = handler;
    },

    run: function(url) {
      url = sanitize(url);

      var querySplit = url.split('?'),
          result = processUrl(querySplit[0]) || {};

      result.cb && processQuery(querySplit[1], result.params) && result.cb({
        url: url,
        params: result.params
      });

      return !!result.cb;
    }
  };
}


function Rlite() {
  var routes = {},
      decode = decodeURIComponent;

  function noop(s) { return s; }

  function sanitize(url) {
    url.indexOf('/?') >= 0 && (url = url.replace('/?', '?'));
    url[0] == '/' && (url = url.slice(1));
    url[url.length - 1] == '/' && (url = url.slice(0, -1));

    return url;
  }

  function processUrl(url) {
    var esc = url.indexOf('%') >= 0 ? decode : noop,
        pieces = url.split('/'),
        rules = routes,
        params = {};

    for (var i = 0; i < pieces.length && rules; ++i) {
      var piece = esc(pieces[i]);
      rules = rules[piece.toLowerCase()] || rules[':'];
      rules && rules[':'] && (params[rules[':']] = piece);
    }

    return rules && {
      cb: rules['@'],
      params: params
    };
  }

  function processQuery(url, ctx) {
    if (url) {
      var hash = url.indexOf('#'),
          esc = url.indexOf('%') >= 0 ? decode : noop,
          query = (hash < 0 ? url : url.slice(0, hash)).split('&');

      for (var i = 0; i < query.length; ++i) {
        var nameValue = query[i].split('=');

        ctx.params[nameValue[0]] = esc(nameValue[1]);
      }
    }

    return ctx;
  }

  function lookup(url) {
    var querySplit = sanitize(url).split('?');

    return processQuery(querySplit[1], processUrl(querySplit[0]) || {});
  }

  return {
    add: function(route, handler) {
      var pieces = route.toLowerCase().split('/'),
          rules = routes;

      for (var i = 0; i < pieces.length; ++i) {
        var piece = pieces[i],
            name = piece[0] == ':' ? ':' : piece;

        rules = rules[name] || (rules[name] = {});

        name == ':' && (rules[':'] = piece.slice(1));
      }

      rules['@'] = handler;
    },

    exists: function (url) {
      return !!lookup(url).cb;
    },

    run: function(url) {
      var result = lookup(url);

      result.cb && result.cb({
        url: url,
        params: result.params
      });

      return !!result.cb;
    }
  };
}

/// Adding routes is not perf-critical, but running them is
var rliteNew = Rlite();
var rliteOld = Prev();

rliteNew.add('hello/:id', function (ctx) {
  return ctx.params.id.length;
});
rliteNew.add('hello/world', function (ctx) {
  return ctx;
});
rliteNew.add('hello/jones', function (ctx) {
  return ctx;
});
rliteNew.add('hello/worlds', function (ctx) {
  return ctx;
});
rliteNew.add('by/:h3', function (ctx) {
  return ctx.params.h3.length;
});
rliteNew.add('by/h43', function (ctx) {
  return ctx;
});

rliteOld.add('hello/:id', function (ctx) {
  return ctx.params.id.length;
});
rliteOld.add('hello/world', function (ctx) {
  return ctx;
});

// Director
var dir = Router().init();

dir.on('/hello/:id', function (ctx) {
  return ctx.length;
});
dir.on('/hello/world', function (ctx) {
  return ctx;
});
dir.on('/hello/jones', function (ctx) {
  return ctx;
});
dir.on('/hello/worlds', function (ctx) {
  return ctx;
});
dir.on('/by/:h3', function (ctx) {
  return ctx.length;
});
dir.on('/by/h43', function (ctx) {
  return ctx;
});

// Backbone
var bb = new Backbone.Router();
Backbone.history.start();

bb.route('hello/:id', 'hello');
bb.on('hello', function (ctx) {
  return ctx.length;
});

bb.route('hello/world', 'world');
bb.on('world', function (ctx) {
  return ctx;
});

bb.route('hello/jones', 'jones');
bb.on('jonew', function (ctx) {
  return ctx;
});

bb.route('hello/worlds', 'worlds');
bb.on('worlds', function (ctx) {
  return ctx;
});

bb.route('by/:h3', 'h3');
bb.on('h3', function (ctx) {
  return ctx;
});

bb.route('by/h43', 'h43');
bb.on('h43', function (ctx) {
  return ctx;
});

//Page
page('/hello/:id', function (ctx) {
  return ctx.params.id;
});
page('/hello/world', function (ctx) {
  return ctx;
});
page('/hello/jones', function (ctx) {
  return ctx;
});
page('/hello/worlds', function (ctx) {
  return ctx;
});
page('/by/:h3', function (ctx) {
  return ctx.params.h3;
});
page('/by/h43', function (ctx) {
  return ctx;
});
page('*', function () {});

//Crossroads
crossroads.addRoute('/hello/{id}', function (id) {
  return id;
});
crossroads.addRoute('/hello/world', function (ctx) {
  return ctx;
});
crossroads.addRoute('/hello/jones', function (ctx) {
  return ctx;
});
crossroads.addRoute('/hello/worlds', function (ctx) {
  return ctx;
});
crossroads.addRoute('/by/{h3}', function (h3) {
  return h3;
});
crossroads.addRoute('/by/h43', function (ctx) {
  return ctx;
});

//Pathparser
var pp = new PathParser();
pp.add('hello/:id', function (ctx) {
  return this.id;
});
pp.add('hello/world', function (ctx) {
  return ctx;
});
pp.add('hello/jones', function (ctx) {
  return ctx;
});
pp.add('hello/worlds', function (ctx) {
  return ctx;
});
pp.add('by/:h3', function (ctx) {
  return ctx.params.h3.length;
});
pp.add('by/h43', function (ctx) {
  return ctx;
});
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
Rlite New
rliteNew.run('hello/james');
rliteNew.run('hello/world');
rliteNew.run('hellos/s');
ready
Rlite Old
rliteOld.run('hello/james');
rliteOld.run('hello/world');
rliteOld.run('hellos/s');
ready
Director
dir.dispatch('on', '/hello/james');
dir.dispatch('on', '/hello/world');
dir.dispatch('on', '/hellos/s');
 
ready
Backbone
Backbone.history.loadUrl('hello/james');
Backbone.history.loadUrl('hello/world');
Backbone.history.loadUrl('hellos/s');
ready
Pagejs
page.show('/hello/james', undefined, true, false);
page.show('/hello/world', undefined, true, false);
page.show('/hellos/s', undefined, true, false);
 
ready
Crossroads
crossroads.parse('/hello/james');
crossroads.parse('/hello/world');
crossroads.parse('/hellos/s');
 
ready
PathParser
pp.run('hello/james');
pp.run('hello/world');
pp.run('hellos/s');
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.