├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── bower.json
├── package-lock.json
├── package.json
├── rlite.js
├── rlite.min.js
├── rlite.min.js.map
└── test
├── rlite.spec.js
└── rlite.test.html
/.gitignore:
--------------------------------------------------------------------------------
1 | Web.config
2 | *.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | example
3 | .gitignore
4 | node_modules
5 | bower_components
6 | README.md
7 | bower.json
8 | .travis.yml
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "7.2.0"
4 | before_script:
5 | - "npm i -g jasmine-node"
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rlite
2 |
3 | Tiny, [fast](http://jsperf.com/rlite/2), light-weight JavaScript routing with zero dependencies.
4 |
5 | - Order of route declaration doesn't matter: the most specific route wins
6 | - Zero dependencies
7 | - No performance drop as you add routes
8 | - Less than 700 bytes minified and gzipped
9 | - Parses query strings
10 | - Wildcard support
11 |
12 | [](https://travis-ci.org/chrisdavies/rlite)
13 |
14 | ## Usage
15 |
16 | Rlite does not come with any explicit tie into HTML5 push state or hash-change events, but these are easy enough to tie in based on your needs. Here's an example:
17 |
18 | ```javascript
19 | const route = rlite(notFound, {
20 | // Default route
21 | '': function () {
22 | return 'Home';
23 | },
24 |
25 | // #inbox
26 | 'inbox': function () {
27 | return 'Inbox';
28 | },
29 |
30 | // #sent?to=john -> r.params.to will equal 'john'
31 | 'sent': function ({to}) {
32 | return 'Sent to ' + to;
33 | },
34 |
35 | // #users/chris -> r.params.name will equal 'chris'
36 | 'users/:name': function ({name}) {
37 | return 'User ' + name;
38 | },
39 |
40 | // #users/foo/bar/baz -> r.params.path will equal 'foo/bar/baz'
41 | 'users/*path': function ({path}) {
42 | return 'Path = ' + path;
43 | },
44 |
45 | // #logout
46 | 'logout': function () {
47 | return 'Logout';
48 | }
49 | });
50 |
51 | function notFound() {
52 | return '
404 Not found :/ ';
53 | }
54 |
55 | // Hash-based routing
56 | function processHash() {
57 | const hash = location.hash || '#';
58 |
59 | // Do something useful with the result of the route
60 | document.body.textContent = route(hash.slice(1));
61 | }
62 |
63 | window.addEventListener('hashchange', processHash);
64 | processHash();
65 | ```
66 |
67 | The previous examples should be relatively self-explantatory. Simple, parameterized routes are supported. Only relative URLs are supported. (So, instead of passing: `'http://example.com/users/1'`, pass `'/users/1'`).
68 |
69 | Routes are not case sensitive, so `'Users/:name'` will resolve to `'users/:name'`
70 |
71 | ## Possible surprises
72 |
73 | If there is a query parameter with the same name as a route parameter, it will override the route parameter. So given the following route definition:
74 |
75 | /users/:name
76 |
77 | If you pass the following URL:
78 |
79 | /users/chris?name=joe
80 |
81 | The value of params.name will be 'joe', not 'chris'.
82 |
83 | Keywords/patterns need to immediately follow a slash. So, routes like the following will not be matched:
84 |
85 | /users/user-:id
86 |
87 | In this case, you'll need to either use a wildcard route `/users/*prefixedId` or else, you'd want to modify the URL to be in a format like this: `/users/user/:id`.
88 |
89 | ## Route handlers
90 |
91 | Route handlers ara functions that take three arguments and return a result and/or produce a side-effect.
92 |
93 | Here's an example handler:
94 |
95 | ```javascript
96 | const route = rlite(notFound, {
97 | 'users/:id': function (params, state, url) {
98 | // Do interesting stuff here...
99 | }
100 | });
101 | ```
102 |
103 | The first argument is `params`. It is an object representing the route parameters. So, if you were to
104 | run `route('users/33')`, params would be `{id: '33'}`.
105 |
106 | The second argument is `state`. It is an optional value that was passed into the route function. So,
107 | if you were to run `route('users/22', 'Hello')`, params would be `{id: '22'}` and state would be `'Hello'`.
108 |
109 | The third argument is `url`. It is the URL which was matched to the route. So, if you were to run
110 | `route('users/25')`, params would be `{id: '25'}`, state would be `undefined` and url would be `'users/25'`.
111 |
112 |
113 | ## Modules
114 |
115 | If you're using ES6, import rlite like so:
116 |
117 | ```javascript
118 | import rlite from 'rlite-router';
119 |
120 | const routes = rlite(notFound, {
121 | '': function () { }
122 | });
123 |
124 | // etc
125 | ```
126 |
127 | Or using [CommonJS](http://www.commonjs.org/) like so:
128 |
129 | ```javascript
130 | var Rlite = require('rlite-router');
131 | var routes = rlite(notFound, {
132 | '': function () { }
133 | });
134 |
135 | // etc
136 | ```
137 |
138 |
139 | ## Handling 404s
140 |
141 | The first parameter to rlite is the 404 handler. This function will be invoked when rlite
142 | is called with a URL that has no matching routes.
143 |
144 | In the following example, the body will end up with `404 NOT FOUND `.
145 |
146 | ```javascript
147 | const route = rlite(() => '404 NOT FOUND ', {
148 | 'hello': => 'WORLD '
149 | });
150 |
151 | document.body.innerHTML = route('/not/a/valid/url');
152 | ```
153 |
154 | ## Changes from 1.x
155 |
156 | - The parameters to route handlers have changed
157 | - The plugins have been dropped since 2.x is more functional in nature, it's easy to extend
158 | - rlite returns a function, rather than an object
159 | - You can pass optional state into your route handlers
160 | - The result of your route handler is returned by the router
161 |
162 | ## Installation
163 |
164 | Just download rlite.min.js, or use bower:
165 |
166 | bower install rlite
167 |
168 | Or use npm: https://www.npmjs.com/package/rlite-router
169 |
170 | npm install --save rlite-router
171 |
172 | ## Contributing
173 |
174 | Make your changes (and add tests), then run the tests:
175 |
176 | npm test
177 |
178 | If all is well, build your changes:
179 |
180 | npm run min
181 |
182 | This minifies rlite, and tells you the size. It's currently just under 700
183 | bytes, and I'd like to keep it that way!
184 |
185 | ## Status
186 |
187 | Rlite is being actively maintained, but is pretty much feature complete. Generally, I avoid repos that look stale (no recent activity), but in this case, the reason for inactivity is that library is stable and complete.
188 |
189 | ## Usage with React
190 |
191 | I've been using Rlite along with React and Redux. [Here's a write up on how that works.](https://github.com/chrisdavies/rlite/wiki/Using-with-React)
192 |
193 | ## License MIT
194 |
195 | Copyright (c) 2016 Chris Davies
196 |
197 | Permission is hereby granted, free of charge, to any person
198 | obtaining a copy of this software and associated documentation
199 | files (the "Software"), to deal in the Software without
200 | restriction, including without limitation the rights to use,
201 | copy, modify, merge, publish, distribute, sublicense, and/or sell
202 | copies of the Software, and to permit persons to whom the
203 | Software is furnished to do so, subject to the following
204 | conditions:
205 |
206 | The above copyright notice and this permission notice shall be
207 | included in all copies or substantial portions of the Software.
208 |
209 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
210 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
211 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
212 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
213 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
214 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
215 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
216 | OTHER DEALINGS IN THE SOFTWARE.
217 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rlite",
3 | "main": "rlite.js",
4 | "version": "2.0.3",
5 | "homepage": "https://github.com/chrisdavies/rlite",
6 | "authors": [
7 | "Chris Davies "
8 | ],
9 | "description": "A tiny, fast client-side router",
10 | "keywords": [
11 | "routing", "router"
12 | ],
13 | "license": "MIT",
14 | "ignore": [
15 | ".gitignore",
16 | "node_modules",
17 | "bower_components",
18 | "test",
19 | "tests",
20 | "README.md"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rlite-router",
3 | "version": "2.0.1",
4 | "lockfileVersion": 1,
5 | "dependencies": {
6 | "align-text": {
7 | "version": "0.1.4",
8 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
9 | "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
10 | "dev": true
11 | },
12 | "camelcase": {
13 | "version": "1.2.1",
14 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
15 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
16 | "dev": true
17 | },
18 | "center-align": {
19 | "version": "0.1.3",
20 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
21 | "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
22 | "dev": true
23 | },
24 | "cliui": {
25 | "version": "2.1.0",
26 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
27 | "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
28 | "dev": true
29 | },
30 | "coffee-script": {
31 | "version": "1.12.7",
32 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz",
33 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==",
34 | "dev": true
35 | },
36 | "decamelize": {
37 | "version": "1.2.0",
38 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
39 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
40 | "dev": true
41 | },
42 | "fileset": {
43 | "version": "0.1.8",
44 | "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz",
45 | "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=",
46 | "dev": true
47 | },
48 | "gaze": {
49 | "version": "0.3.4",
50 | "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz",
51 | "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=",
52 | "dev": true
53 | },
54 | "glob": {
55 | "version": "3.2.11",
56 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz",
57 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=",
58 | "dev": true,
59 | "dependencies": {
60 | "minimatch": {
61 | "version": "0.3.0",
62 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
63 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=",
64 | "dev": true
65 | }
66 | }
67 | },
68 | "growl": {
69 | "version": "1.7.0",
70 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz",
71 | "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=",
72 | "dev": true
73 | },
74 | "inherits": {
75 | "version": "2.0.3",
76 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
77 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
78 | "dev": true
79 | },
80 | "is-buffer": {
81 | "version": "1.1.5",
82 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
83 | "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
84 | "dev": true
85 | },
86 | "jasmine-growl-reporter": {
87 | "version": "0.0.3",
88 | "resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.0.3.tgz",
89 | "integrity": "sha1-uHrlUeNZ0orVIXdl6u9sB7dj9sg=",
90 | "dev": true
91 | },
92 | "jasmine-node": {
93 | "version": "1.14.5",
94 | "resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-1.14.5.tgz",
95 | "integrity": "sha1-GOg5e4VpJO53ADZmw3MbWupQw50=",
96 | "dev": true
97 | },
98 | "jasmine-reporters": {
99 | "version": "1.0.2",
100 | "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-1.0.2.tgz",
101 | "integrity": "sha1-q2E+1Zd9x0h+hbPBL2qOqNsq3jE=",
102 | "dev": true
103 | },
104 | "kind-of": {
105 | "version": "3.2.2",
106 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
107 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
108 | "dev": true
109 | },
110 | "lazy-cache": {
111 | "version": "1.0.4",
112 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
113 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
114 | "dev": true
115 | },
116 | "longest": {
117 | "version": "1.0.1",
118 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
119 | "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
120 | "dev": true
121 | },
122 | "lru-cache": {
123 | "version": "2.7.3",
124 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
125 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=",
126 | "dev": true
127 | },
128 | "minimatch": {
129 | "version": "0.2.14",
130 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
131 | "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=",
132 | "dev": true
133 | },
134 | "mkdirp": {
135 | "version": "0.3.5",
136 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
137 | "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=",
138 | "dev": true
139 | },
140 | "repeat-string": {
141 | "version": "1.6.1",
142 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
143 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
144 | "dev": true
145 | },
146 | "requirejs": {
147 | "version": "2.3.4",
148 | "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.4.tgz",
149 | "integrity": "sha512-qPD5gRrj6kGQ0ZySAJgqbArkaDqPMbQIT0zSZDkIv1mfA17w/tR2Faq5l2qqRf2CdQx6FWln9nUrgd2UDzb19A==",
150 | "dev": true
151 | },
152 | "right-align": {
153 | "version": "0.1.3",
154 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
155 | "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
156 | "dev": true
157 | },
158 | "sigmund": {
159 | "version": "1.0.1",
160 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
161 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
162 | "dev": true
163 | },
164 | "source-map": {
165 | "version": "0.5.6",
166 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
167 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
168 | "dev": true
169 | },
170 | "uglify-js": {
171 | "version": "2.8.29",
172 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
173 | "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
174 | "dev": true
175 | },
176 | "uglify-to-browserify": {
177 | "version": "1.0.2",
178 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
179 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
180 | "dev": true,
181 | "optional": true
182 | },
183 | "underscore": {
184 | "version": "1.8.3",
185 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
186 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
187 | "dev": true
188 | },
189 | "walkdir": {
190 | "version": "0.0.11",
191 | "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz",
192 | "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=",
193 | "dev": true
194 | },
195 | "window-size": {
196 | "version": "0.1.0",
197 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
198 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=",
199 | "dev": true
200 | },
201 | "wordwrap": {
202 | "version": "0.0.2",
203 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
204 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
205 | "dev": true
206 | },
207 | "yargs": {
208 | "version": "3.10.0",
209 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
210 | "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
211 | "dev": true
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rlite-router",
3 | "version": "2.0.3",
4 | "description": "A tiny, fast client-side router",
5 | "main": "rlite.js",
6 | "directories": {},
7 | "scripts": {
8 | "test": "jasmine-node test/*.spec.js",
9 | "min": "uglifyjs rlite.js --source-map rlite.min.js.map -m -c -o rlite.min.js && echo 'Minified size' && cat rlite.min.js | gzip -9f | wc -c"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/chrisdavies/rlite"
14 | },
15 | "keywords": [
16 | "routing",
17 | "router"
18 | ],
19 | "author": "Chris Davies ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/chrisdavies/rlite/issues"
23 | },
24 | "homepage": "https://github.com/chrisdavies/rlite",
25 | "devDependencies": {
26 | "jasmine-node": "^1.14.5",
27 | "uglify-js": "^2.7.5"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/rlite.js:
--------------------------------------------------------------------------------
1 | // This library started as an experiment to see how small I could make
2 | // a functional router. It has since been optimized (and thus grown).
3 | // The redundancy and inelegance here is for the sake of either size
4 | // or speed.
5 | //
6 | // That's why router params are marked with a single char: `~` and named params are denoted `:`
7 | (function (root, factory) {
8 | var define = root && root.define;
9 |
10 | if (define && define.amd) {
11 | define('rlite', [], factory);
12 | } else if (typeof module !== 'undefined' && module.exports) {
13 | module.exports = factory();
14 | } else {
15 | root.Rlite = factory();
16 | }
17 | }(this, function () {
18 | return function (notFound, routeDefinitions) {
19 | var routes = {};
20 | var decode = decodeURIComponent;
21 |
22 | init();
23 |
24 | return run;
25 |
26 | function init() {
27 | for (var key in routeDefinitions) {
28 | add(key, routeDefinitions[key]);
29 | }
30 | };
31 |
32 | function noop(s) { return s; }
33 |
34 | function sanitize(url) {
35 | ~url.indexOf('/?') && (url = url.replace('/?', '?'));
36 | url[0] == '/' && (url = url.slice(1));
37 | url[url.length - 1] == '/' && (url = url.slice(0, -1));
38 |
39 | return url;
40 | }
41 |
42 | // Recursively searches the route tree for a matching route
43 | // pieces: an array of url parts, ['users', '1', 'edit']
44 | // esc: the function used to url escape values
45 | // i: the index of the piece being processed
46 | // rules: the route tree
47 | // params: the computed route parameters (this is mutated), and is a stack since we don't have fast immutable datatypes
48 | //
49 | // This attempts to match the most specific route, but may end int a dead-end. We then attempt a less specific
50 | // route, following named route parameters. In searching this secondary branch, we need to make sure to clear
51 | // any route params that were generated during the search of the dead-end branch.
52 | function recurseUrl(pieces, esc, i, rules, params) {
53 | if (!rules) {
54 | return;
55 | }
56 |
57 | if (i >= pieces.length) {
58 | var cb = rules['@'];
59 | return cb && {
60 | cb: cb,
61 | params: params.reduce(function(h, kv) { h[kv[0]] = kv[1]; return h; }, {}),
62 | };
63 | }
64 |
65 | var piece = esc(pieces[i]);
66 | var paramLen = params.length;
67 | return recurseUrl(pieces, esc, i + 1, rules[piece.toLowerCase()], params)
68 | || recurseNamedUrl(pieces, esc, i + 1, rules, ':', piece, params, paramLen)
69 | || recurseNamedUrl(pieces, esc, pieces.length, rules, '*', pieces.slice(i).join('/'), params, paramLen);
70 | }
71 |
72 | // Recurses for a named route, where the name is looked up via key and associated with val
73 | function recurseNamedUrl(pieces, esc, i, rules, key, val, params, paramLen) {
74 | params.length = paramLen; // Reset any params generated in the unsuccessful search branch
75 | var subRules = rules[key];
76 | subRules && params.push([subRules['~'], val]);
77 | return recurseUrl(pieces, esc, i, subRules, params);
78 | }
79 |
80 | function processQuery(url, ctx, esc) {
81 | if (url && ctx.cb) {
82 | var hash = url.indexOf('#'),
83 | query = (hash < 0 ? url : url.slice(0, hash)).split('&');
84 |
85 | for (var i = 0; i < query.length; ++i) {
86 | var nameValue = query[i].split('=');
87 |
88 | ctx.params[nameValue[0]] = esc(nameValue[1]);
89 | }
90 | }
91 |
92 | return ctx;
93 | }
94 |
95 | function lookup(url) {
96 | var querySplit = sanitize(url).split('?');
97 | var esc = ~url.indexOf('%') ? decode : noop;
98 |
99 | return processQuery(querySplit[1], recurseUrl(querySplit[0].split('/'), esc, 0, routes, []) || {}, esc);
100 | }
101 |
102 | function add(route, handler) {
103 | var pieces = route.split('/');
104 | var rules = routes;
105 |
106 | for (var i = +(route[0] === '/'); i < pieces.length; ++i) {
107 | var piece = pieces[i];
108 | var name = piece[0] == ':' ? ':' : piece[0] == '*' ? '*' : piece.toLowerCase();
109 |
110 | rules = rules[name] || (rules[name] = {});
111 |
112 | (name == ':' || name == '*') && (rules['~'] = piece.slice(1));
113 | }
114 |
115 | rules['@'] = handler;
116 | }
117 |
118 | function run(url, arg) {
119 | var result = lookup(url);
120 |
121 | return (result.cb || notFound)(result.params, arg, url);
122 | };
123 | };
124 | }));
125 |
--------------------------------------------------------------------------------
/rlite.min.js:
--------------------------------------------------------------------------------
1 | !function(n,e){var r=n&&n.define;r&&r.amd?r("rlite",[],e):"undefined"!=typeof module&&module.exports?module.exports=e():n.Rlite=e()}(this,function(){return function(n,e){function r(n){return n}function t(n){return~n.indexOf("/?")&&(n=n.replace("/?","?")),"/"==n[0]&&(n=n.slice(1)),"/"==n[n.length-1]&&(n=n.slice(0,-1)),n}function i(n,e,r,t,u){if(t){if(r>=n.length){var f=t["@"];return f&&{cb:f,params:u.reduce(function(n,e){return n[e[0]]=e[1],n},{})}}var c=e(n[r]),l=u.length;return i(n,e,r+1,t[c.toLowerCase()],u)||o(n,e,r+1,t,":",c,u,l)||o(n,e,n.length,t,"*",n.slice(r).join("/"),u,l)}}function o(n,e,r,t,o,u,f,c){f.length=c;var l=t[o];return l&&f.push([l["~"],u]),i(n,e,r,l,f)}function u(n,e,r){if(n&&e.cb)for(var t=n.indexOf("#"),i=(t<0?n:n.slice(0,t)).split("&"),o=0;o expect(name).toEqual('value')});
6 |
7 | route('stuff?name=value#baz');
8 | });
9 |
10 | it('Has empty params for parameterless routes', function () {
11 | const route = rlite(noop, {
12 | stuff: (params) => expect(Object.keys(params).length).toEqual(0)
13 | });
14 |
15 | route('stuff');
16 | });
17 |
18 | it('Returns the result of the route', function () {
19 | const route = rlite(noop, {
20 | hi: () => 'Hello bob',
21 | bye: () => 'Bye bob',
22 | });
23 |
24 | expect(route('hi')).toEqual('Hello bob');
25 | expect(route('bye')).toEqual('Bye bob');
26 | });
27 |
28 | it('It handles leading and trailing slashes and 404s', function () {
29 | const route = rlite(() => 'Nope!', {
30 | stuff: () => 'Yep!'
31 | });
32 |
33 | expect(route('/stuff/')).toEqual('Yep!');
34 | expect(route('stuff/')).toEqual('Yep!');
35 | expect(route('/stuff')).toEqual('Yep!');
36 | expect(route('stuff')).toEqual('Yep!');
37 | expect(route('nopes')).toEqual('Nope!');
38 | });
39 |
40 | it('It handles deep conflicting routes', function () {
41 | const route = rlite(() => '404', {
42 | 'foo/:bar/baz': ({bar}) => `Hi, ${bar}`,
43 | 'foo/:bar/:boo': ({bar, boo}) => `Bar=${bar}, Boo=${boo}`,
44 | 'foo/bar/bing': () => 'BING',
45 | });
46 |
47 | expect(route('/foo/bar/baz/')).toEqual('Hi, bar');
48 | expect(route('/foo/x/y/')).toEqual('Bar=x, Boo=y');
49 | expect(route('/foo/bar/bing/')).toEqual('BING');
50 | });
51 |
52 | it('Handles route params', function() {
53 | const route = rlite(noop, {
54 | 'hey/:name': ({name}) => expect(name).toEqual('chris')
55 | });
56 |
57 | route('hey/chris');
58 | });
59 |
60 | it('Handles different cases', function() {
61 | let count = 0;
62 | const route = rlite(noop, {
63 | 'Hey/:name': ({name}) => {
64 | expect(name).toEqual('chris');
65 | ++count;
66 | },
67 | 'hello/:firstName': ({firstName}) => {
68 | expect(firstName).toEqual('jane');
69 | ++count;
70 | },
71 | 'hoi/:FirstName/:LastName': ({FirstName, LastName}) => {
72 | expect(FirstName).toEqual('Joe');
73 | expect(LastName).toEqual('Smith');
74 | ++count;
75 | }
76 | });
77 |
78 | route('hey/chris');
79 | route('hello/jane');
80 | route('hoi/Joe/Smith');
81 | expect(count).toBe(3);
82 | });
83 |
84 | it('Passes the argument and url through', function() {
85 | const route = rlite(noop, {
86 | 'hey/:name': ({name}, arg, url) => {
87 | expect(arg).toEqual('Wut');
88 | expect(name).toEqual('You');
89 | expect(url).toEqual('hey/You');
90 | }
91 | });
92 |
93 | route('hey/You', 'Wut');
94 | });
95 |
96 | it('Matches root routes correctly', function() {
97 | const route = rlite(noop, {
98 | 'hey/:name/new': () => {throw new Error('New called');},
99 | 'hey/:name': ({name}) => expect(name).toEqual('chris'),
100 | 'hey/:name/edit': () => {throw new Error('Edit called');},
101 | });
102 |
103 | route('hey/chris');
104 | });
105 |
106 | it('Understands specificity', function() {
107 | const route = rlite(noop, {
108 | 'hey/joe': (_1, _2, url) => expect(url).toEqual('hey/joe'),
109 | 'hey/:name': () => {throw new Error('Name called')},
110 | 'hey/jane': (_1, _2, url) => expect(url).toEqual('hey/jane'),
111 | });
112 |
113 | route('hey/joe');
114 | route('hey/jane');
115 | });
116 |
117 | it('Handles complex routes', function() {
118 | const route = rlite(noop, {
119 | 'hey/:name/new': () => {throw new Error('New called');},
120 | 'hey/:name': () => {throw new Error('Name called');},
121 | 'hey/:name/last/:last': () => ({name, last}) => {
122 | expect(name).toEqual('chris');
123 | expect(last).toEqual('davies');
124 | }
125 | });
126 |
127 | route('hey/chris/last/davies');
128 | });
129 |
130 | it('Overrides params with query string values', function() {
131 | const route = rlite(noop, {
132 | 'hey/:name/new': () => {throw new Error('New called');},
133 | 'hey/:name': () => {throw new Error('Name called');},
134 | 'hey/:name/last/:last': function({name, last}) {
135 | expect(name).toEqual('ham');
136 | expect(last).toEqual('mayo');
137 | return name + ' ' + last;
138 | }
139 | });
140 |
141 | expect(route('hey/chris/last/davies?last=mayo&name=ham')).toEqual('ham mayo');
142 | });
143 |
144 | it('Handles not founds', function() {
145 | const route = rlite(() => '404', {
146 | 'hey/:name': () => {throw new Error('Name called');}
147 | });
148 |
149 | expect(route('hey?hi=there')).toEqual('404');
150 | });
151 |
152 | it('Handles default urls', function() {
153 | const route = rlite(noop, {
154 | '': () => 'HOME'
155 | });
156 |
157 | expect(route('')).toEqual('HOME');
158 | });
159 |
160 | it('Handles multiple params in a row', function() {
161 | const route = rlite(noop, {
162 | 'hey/:hello/:world': ({hello, world}) => {
163 | expect(hello).toEqual('a');
164 | expect(world).toEqual('b');
165 | }
166 | });
167 |
168 | route('hey/a/b');
169 | });
170 |
171 | it('Handles trailing slash with query', function() {
172 | const route = rlite(noop, {
173 | 'hoi': ({there}) => {
174 | expect(there).toEqual('yup');
175 | return 'Yeppers';
176 | }
177 | });
178 |
179 | expect(route('hoi/?there=yup')).toEqual('Yeppers');
180 | });
181 |
182 | it('Handles leading slashes in defs', function() {
183 | const route = rlite(noop, {
184 | '/hoi': () => 'GOT IT'
185 | });
186 |
187 | expect(route('hoi')).toEqual('GOT IT');
188 | });
189 |
190 | it('Handles wildcard routes', function() {
191 | const route = rlite(() => 'NOT FOUND', {
192 | '/users/:name/baz': ({name}) => `Hi, ${name}`,
193 | '/users/*name': ({name}) => `Wild, ${name}`,
194 | '/foo/:baz/qux': ({baz}) => `BAZ ${baz}`,
195 | '/foo/*bar': ({bar}) => `GOT ${bar}`,
196 | });
197 |
198 | expect(route('hoi')).toEqual('NOT FOUND');
199 | expect(route('users/chris/baz')).toEqual('Hi, chris');
200 | expect(route('users/chris/bar')).toEqual('Wild, chris/bar');
201 | expect(route('foo/something')).toEqual('GOT something');
202 | expect(route('foo/something/qux')).toEqual('BAZ something');
203 | });
204 |
205 | it('Encodes params', function() {
206 | const route = rlite(noop, {
207 | '': ({hey}) => {
208 | expect(hey).toEqual('/what/now');
209 | return 'HOME';
210 | },
211 |
212 | ':hey': ({hey}) => {
213 | expect(hey).toEqual('/hoi/hai?hui');
214 | return ':hey';
215 | },
216 |
217 | 'more-complex/:hey': ({hey, hui}) => {
218 | expect(hey).toEqual('/hoi/hai?hui');
219 | expect(hui).toEqual('/hoi/hai');
220 | return 'LAST';
221 | },
222 | });
223 |
224 | expect(route(encodeURIComponent('/hoi/hai?hui'))).toEqual(':hey');
225 | expect(route('/?hey=' + encodeURIComponent('/what/now'))).toEqual('HOME');
226 | expect(route('/more-complex/' + encodeURIComponent('/hoi/hai?hui') + '?hui=' + encodeURIComponent('/hoi/hai'))).toEqual('LAST');
227 | });
228 | });
229 |
230 | function noop() { }
231 |
232 | })(this.Rlite || require('../rlite'));
233 |
--------------------------------------------------------------------------------
/test/rlite.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Rlite tests
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------