Intro
Router for byte-conscious isomorphic applications designed for use with Browserify.
After using several different routers I decided none of them were quite what I wanted. So, I wrote my own.
This router is loosly inspired by the Express router, react-router, and page.js.
Tested on Node.js 0.12.
Features
kingfish has the following featureset:
- Isomorphic: The route definitions and handlers should be identical on the client and server. Server-specific code can easily be removed with browserify transformations. This makes it easier to maintain your application.
- Small size: Minified this is 5kB, gzipped it is smaller, although I expect it to grow a little bit more. This makes it load quickly and leaves more space for your application code.
- Small API: The route handler API has only four methods, and the router itself has only two (one for client and one for server). This makes it easy to learn and use.
- Sub-routers: These are inspired by express.Router to allow for modularity in routes, and to allow you to plug another routing system into a piece of your application.
- Partial page changes: A rendered page should not be completely thrown away when the URL changes. If only the URL parameters change, you should be able to update to reflect that. If part of the URL changes but the first part stays the same, only part of the page should be thrown away; this is easy with sub-routers.
- Helpful errors: The router should catch most things you might do wrong and explain exactly how to fix it. React does this, and I believe every library should.
Planned features:
- Asynchronous: Rendering should be asynchronous on the server to allow you to load data. I plan to implement this by allowing the
renderString
handler method to return a promise, this should be enough to handle most use cases. - URL prefix: Run in a sub-directory, so the entire website can be in a sub-directory or so this router can be used inside another application.
- Tests: yup
- Events: The router should emit an event when the route changes, so you can update other parts of your application.
Start
Install with npm:
npm install --save kingfish
In Node.js or Browserify, load it with:
var Kingfish = require('kingfish');
Using kingfish in the browser is just two lines, plus your routes.
var router = new Kingfish(routes, document.getElementById('content'));
router.start();
On the server, you can handle a request with:
var router = new Kingfish(routes);
var content = router.renderString(url);
Creating a sub-router in the browser is similar:
var childRouter = new Kingfish(childRoutes, document.getElementById('childContent'));
childRouter.render(subUrl);
Finally, creating a sub-router on the server is identical to a regular server router.
Routes are just objects, in ES6 these are very easy to create with the class
keyword. A basic route with both a server and browser render method might look like:
var routes = {
'/post/?': class {
renderString() {
return 'This is the post with ID ' + this.params[0];
}
mount() {
this.element.innerHTML = this.renderString();
}
}
}
Inside your route handlers you can use any libraries you choose, including Handlebars, React, Backbone, or even another router.
Docs
API
When you require('kingfish')
you will get back a constructor for the Kingfish object. Create the object with the following two parameters:
- Routes definition object as defined below
- DOM element to render into (browser only)
On the server, the resulting object instance will have just one method:
renderString(url)
- Routes the given URL, calls the
renderString
method of the route handler, and returns the resulting content - Environment: server only
- Routes the given URL, calls the
On the browser, it will have three:
start()
- Starts the client-side router. Hijacks link clicks, listens to URL changes, and listens to the back/forward buttons.
- Environment: browser only
stop()
- Stops the client-side router and removes all event listeners.
- Environment: browser only
render(url)
- Routes the given URL and calls either the
mount
orsetParams
method of the route handler. If you calledstart
, this will never be necessary–it will call this method automatically when needed. - Environment: browser only
- Routes the given URL and calls either the
An important part of modular applications is the ability to define sub-routes. With kingfish, this is done by creating a sub-router.
On the server, there is no difference between a regular router and a sub-router.
In the browser, create the sub-router the same way, but use the render
method instead of calling start
. You will need to call it every time the URL changes.
See the demo application for an example of how to use all the above methods, located in the “demo” folder of the GitHub repository.
Routes definition
The routes definition is an object with keys equal to the route patterns to match. These patterns support two special character sequences for extracting parameters:
/?
matches anything except for/
/*
matches anything including/
, generally this would be placed at the end of a pattern when you have another router inside the handler
The values in the routes definition are objects (I recommend using ES6 classes for readability) with the following properties and methods:
element
: The DOM element to render into. This is the same element passed into the router constructor.params
: Array of matched URL parameters. This is set automatically beforemount()
is called the first time, but is never automatically changed after that.renderString()
: Should return a string representing the entire content, used primarily for server-side rendering.mount()
: Called when first entering a route, you are responsible for rendering your content intothis.element
. Browser-only.setParams(newParams)
: Called when the URL parameters change, but the route stays the same, with an array of params. Browser-only.unmount()
: Called when leaving a route, here you should clean up any event handlers or data bindings you created to prevent bugs and memory leaks. Browser-only.
For server-side rendering, renderString
is the only method you need. For client-side rendering, you need either renderString
or mount
; if both are present, mount
will be used. If you have URL parameters you should implement setParams
as well, and you will get a warning if it does not exist when the parameters change.
Contributing
Bug requests and pull requests are welcome. Keep the following in mind:
- Follow the existing code style. All code must pass lint. Do not change the eslint config to make it pass.
- Any new features must be either included in the existing demo, or added to a second demo.
- If you are adding a feature, please suggest it on the bug tracker to start a dialog about the best way to implement it.
- Any changes to the API presented when kingfish is required must be documented
To develop, checkout the repository from GitHub and install the dependencies:
- Install Ruby. This is required for the documentation site, which is built using Jekyll. You need at least version 2.0, check with
ruby --version
. - Install Bundler, the Ruby dependency manager:
gem install bundler
- Install the Node.js dependencies:
npm install
Run build scripts with npm:
npm run start
: Builds the module, demo, docs, and runs the demo servernpm run watch
: Builds the module, demo, docs, runs the demo server, and re-builds when files change.
Set NODE_ENV to the following to affect the build process. This affects the build of the module, demo, and docs.
NODE_ENV=development
: Quickest build times, all debugging turned on, no minificationNODE_ENV=staging
: Debugging turned on, minification turned onNODE_ENV=production
: Debugging turned off, Minification turned on
License
Copyright 2015 Paul Rayes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.