• Stars
    star
    141
  • Rank 259,971 (Top 6 %)
  • Language
    JavaScript
  • License
    Other
  • Created over 10 years ago
  • Updated over 9 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An isomorphic JS router

monorouter

monorouter is an isomorphic JavaScript router by @matthewwithanm and @lettertwo. It was designed for use with ReactJS but doesn't have any direct dependencies on it and should be easily adaptable to other virtual DOM libraries.

While it can be used for both browser-only and server-only routing, it was designed from the ground up to be able to route apps on both sides of the wire.

Note: This project is in beta and we consider the API in flux. Let us know if you have any ideas for improvement!

Usage

Defining a router looks like this:

var monorouter = require('monorouter');
var reactRouting = require('monorouter-react');

monorouter()
  .setup(reactRouting())
  .route('/', function(req) {
    this.render(MyView);
  })
  .route('/pets/:name/', function(req) {
    this.render(PetView, {petName: req.params.name});
  });

Here, we're simply rendering views for two different routes. With monorouter, a "view" is any function that returns a DOM descriptor.

The router can be used on the server with express and connect-monorouter:

var express = require('express');
var router = require('./path/to/my/router');
var monorouterMiddleware = require('connect-monorouter');

var app = express();
app.use(monorouterMiddleware(router));

var server = app.listen(3000, function() {
    console.log('Listening on port %d', server.address().port);
});

And in the browser:

router
  .attach(document)
  .captureClicks(); // This is optional—it uses the router to handle normal links.

See the examples for a more in-depth look and more tricks!

Handling errors

Error handling with monorouter is similar to Express—just add a middleware with an arity of 3:

router.use(function(err, req, next) {
  if (err.status === 404) {
    this.render(My404Template);
  }
});

API

In addition to the router itself, monorouter has two important objects. The first one is the request object, and it's passed as the first argument to your handler. The second is the context (this) of your route handler, and it's used to interface with your application's state (you can think of this as being similar to a Response object in server-only routers). This section summarizes their APIs.

Request

  • param(name:String): Get the value for one of the dynamic parts of your route:

    monorouter()
      .route('/pets/:name', function(req) {
        console.log(req.param('name'));
        // snip
      });
  • params:Object: A hash of the params used in your route.

  • canceled:Boolean: A boolean that represents whether the request has been canceled. This is useful for preventing further action in async callbacks:

    monorouter()
      .route('/', function(req) {
        http('...', function(err, result) {
          if (req.canceled) return;
          this.render(MyView, {person: result.people[0]});
        }.bind(this));
      });

    Note that it's not necessary to check this value if all you're doing is rendering since those operations are no-ops when the request has been canceled. The request is also an EventEmitter that emits a "cancel" event so, if you'd like to take action immediately when a request is canceled (and abort an XHR request, for example), you can do that:

    monorouter()
      .route('/', function(req) {
        var xhr = http('...', function(err, result) {
          if (req.canceled) return;
          this.render(MyView, {person: result.people[0]});
        }.bind(this));
        this.on('cancel', function() {
          xhr.abort();
        });
      });
  • initialOnly:Boolean: Indicates whether the request is only for the initial state of the app. true when rendering on the server.

  • location:Object: A parsed version of the requested URL, in a format based on the document.location interface.

  • url:String: The requested URL.

  • protocol:String: The protocol of the requested URL, without the colon. e.g. "http"

  • hostname:String: The hostname of the requested URL, e.g. 'mysite.com'

  • host:String: The full host of the requested URL, e.g. 'mysite.com:5000'

  • search:String: The search portion of the requested URL, including the question mark, e.g. '?hello=5&goodbye=a'

  • querystring:String: The search portion of the requested URL, excluding the question mark, e.g. 'hello=5&goodbye=a'

  • query:Object: A version of the query string that's been parsed using @sindresorhus's query-string.

  • hash:String: The hash portion of the requested URL, including the hash mark. e.g. '#this-is-the-hash'

  • fragment:String: The hash portion of the requested URL, excluding the hash mark. e.g. 'this-is-the-hash'

  • first:Boolean: Is this the first request being handled by this router?

  • from(causes:String...):Boolean: Check the cause of the request. Causes that monorouter sends are:

    • "startup" - When a request is triggered by the router initialization in the browser.
    • "popstate" - When a request is triggered by a popstate event
    • "link" - When a request is triggered by a link click captured by the link hijacker

    You may also send custom causes. For example, connect-monorouter uses the string '"httprequest"'. Generally, causes shouldn't be used to affect routing behavior—they are meant primarily for logging.

Handler Context

Within a route handler, you use properties and methods of this to define the application state. Here are some of those:

  • render(view:Function?, vars:Object?, callback:Function?): Render the view and consider the request complete. The view is a function that returns a virtual DOM instance. It may be omitted if you've previously set one for this request using setView (e.g. in middleware). "vars" are arguments for this function that will be bound to it for as long as it's rendered.
  • renderIntermediate(view:Function?, vars:Object?, callback:Function?): Like render, but doesn't end the request. This is useful if you'd like to render several different states during the course of handling a single route.
  • renderInitial(view:Function?, vars:Object?, callback:Function?): Like render, but only ends "initialOnly" requests.
  • setView(view:Function): Sets the view to be rendered for this response. The application won't actually be updated until/unless one of the render* methods is called.
  • setVars(vars:Object): Add vars for any subsequent renders in this request. "vars" are passed to the view function for rendering.
  • setState(state:Object): "state" is similar to vars in that its values are passed to the view for rendering. Unlike "vars", however, "state" is preserved between requests. Setting state also triggers a rerender of the current view. The state and vars are merged and the result passed to the view function.
  • notFound(): A function that tells the server to send a 404 status code with this view.
  • doctype:String: The doctype for the document. Defaults to the HTML5 doctype.
  • contentType:String: The content type of the document. Defaults to 'text/html; charset=utf-8'
  • beforeRender(hook:Function): An interface for adding before-render hooks. All hooks are executed in parallel immediately prior to rendering.
  • ended:Boolean: Specifies whether the response has ended.
  • initialEnded:Boolean: Specifies whether the initial state has been rendered.

Philosophy

If the original idea for react-nested-router was "Ember got it mostly right," monorouter's can be said to be "Express got it mostly right." (Or koa. Or Flask. Or Django. Or Rails…) Server-side routing is very easy: a request comes in and, in response, you render a template. Each time this happens, you render the entire document. Until recently, this approach seemed incongruent with client-side rendering, but ReactJS and the virtual DOM have changed that.

Still, client-side routing is fundamentally different than server-side in some ways—most notably in that state can be shared between routes. monorouter aims to expose the functionality related to GUI routing that's common to the server and browser. Some principles of the project are:

  1. The same routing that's used in the browser should be used to render the server response.
  2. Routing is (at least potentially) an asynchronous process while rendering a view is a synchronous one.
  3. Routes need not map to a single state (or view), but may result in any number of them throughout their life.
    • Two of these states are special: the "initial" state (which will be serialized and sent by the server and which the browser app must begin in) and the final state (which the app is in once the handling of a route is completed).
  4. A view is any JavaScript function that returns a DOM descriptor.
  5. Each view should represent the entire document at a given state—not just a portion of it.
    • Not only does this make reasoning about the application easier, it's very important when dealing with <head>s
  6. We think monorouter covers all the possible use cases, and we see it as a foundation on which to build—both via extensions (i.e. middleware) and additional abstractions (i.e. JSX-friendly interfaces and declarative, lifecycle-centric route declarations).

More Repositories

1

django-imagekit

Automated image processing for Django. Currently v4.0
Python
2,260
star
2

python-markdownify

Convert HTML to Markdown
Python
1,036
star
3

react-controllables

Easily create controllable components
JavaScript
256
star
4

pilkit

Utilities and processors built for, and on top of PIL
Python
195
star
5

react-mediaswitch

Choose your DOM based on media queries
CoffeeScript
59
star
6

react-frozenhead

Make your whole page a React component and render it on the server
JavaScript
57
star
7

django-classbasedsettings

Class based settings. You couldn't get that from the title?
Python
52
star
8

httpplease.js

The polite HTTP request library for node and the browser
JavaScript
31
star
9

django-html5boilerplate

A packaging of Paul Irish's HTML5 Boilerplate for Django projects.
Python
20
star
10

markdown-with-front-matter-loader

A webpack loader for markdown with yaml front matter (think Jekyll)
JavaScript
19
star
11

jquery-icbiacontrol

Style browser controls without losing their native behaviors.
JavaScript
12
star
12

assert-this.js

A clear assertion style that uses virtual methods instead of wrappers
JavaScript
7
star
13

grunt-jinja

A grunt plugin for compiling Jinja2 templates with James Long's nunjucks templating system
CoffeeScript
6
star
14

jquery.picplus

Take control of your images.
JavaScript
6
star
15

django-throttleandcache

Cache the results of arbitrary function calls!
Python
5
star
16

gulp-heroku-deploy-slug

Deploy slug archives to Heroku
JavaScript
4
star
17

react-template

Organized rendering.
JavaScript
3
star
18

wrapperator.js

Create a function that can be used as a wrapper or method decorator
JavaScript
2
star
19

jquery.picplus.queueup

Use a load queue to load your picplus pictures. Load the important stuff first.
JavaScript
2
star
20

jquery.picplus.lazyload

Add lazyloading to your picplus pictures. Load them when they're scrolled into your viewport.
CoffeeScript
2
star
21

httpplease-promises

A plugin that adds promise support to httpplease
JavaScript
2
star
22

monorouter-react

ReactJS tools for monorouter
JavaScript
2
star
23

django-maid

Django maid cleans up your orphaned media files.
Python
1
star
24

Google-Closure.tmbundle

The fastest way to get started with Google Closure.
JavaScript
1
star
25

jquery.parallaxin

Create an element that scrolls at a different speed than the document but always stays within a container.
JavaScript
1
star
26

django-scspostgis

A PostGIS db backend that works with standard_conforming_strings.
Python
1
star