1 | horse
|
2 | =====
|
3 |
|
4 | horse is a couple of helper classes that can be used to help you build isomorphic
|
5 | applications for io.js / node. It abstracts routing and rendering helpers so
|
6 | that you can plug in a rendering system, bind links, and have an application
|
7 | that works anywhere.
|
8 |
|
9 | The vast bulk of your application will live in your routes file (`routes.jsx`
|
10 | in the example below), your API library, and your views - and will be shared
|
11 | between the server and the client. horse's job is to get out of the way so that
|
12 | you don't care where the code is running, and yet you get both server-side and
|
13 | client-side rendering.
|
14 |
|
15 | A Brief Overview
|
16 | ----------------
|
17 |
|
18 | ```
|
19 | ======================================
|
20 | Your App
|
21 |
|
22 | +---------+ +---------------+
|
23 | | koa | | html5 history |
|
24 | +---------+ | api |
|
25 | | +---------------+
|
26 | req req
|
27 | | | render and
|
28 | \ / wait for new route
|
29 | ------------------------ event
|
30 | | ^
|
31 | v |
|
32 | ====================================== |
|
33 | +--------------+ |
|
34 | | horse/App.js | |
|
35 | +--------------+ |
|
36 | | |
|
37 | v |
|
38 | ==================================== |
|
39 | Your App's Routes |
|
40 | |
|
41 | +---------------+ |
|
42 | | route handler | -> yield { body: reactElement }
|
43 | | | -> throw MissingAuthenticationError();
|
44 | +---------------+
|
45 | ```
|
46 |
|
47 | The App has an instance of an Express-like request router that it uses to map
|
48 | requests to the appropriate handling function, and is run on both the client-
|
49 | and server- side. It's meant to abstract just enough boilerplate out of the
|
50 | way so that you can do your own custom stuff.
|
51 |
|
52 | An example usage might be like: (es6 incoming)
|
53 |
|
54 | `routes.jsx`
|
55 |
|
56 | ```javascript
|
57 | // This is used both client- and server- side, and simply sets up an app with
|
58 | // routes; in this case, returning React elements.
|
59 |
|
60 | import Layout from '../layouts/layout.jsx';
|
61 | import Index from '../pages/index.jsx';
|
62 |
|
63 | function setupRoutes(app) {
|
64 | app.router.get('/', function *() {
|
65 | this.layout = Layout;
|
66 |
|
67 | var user = yield db.getUser(1);
|
68 | this.props = { user };
|
69 |
|
70 | this.body = <Index {...this.props} />;
|
71 | });
|
72 | }
|
73 |
|
74 | export default setupRoutes;
|
75 | ```
|
76 |
|
77 |
|
78 | `server.es6.js`
|
79 |
|
80 | ```javascript
|
81 | import koa from 'koa';
|
82 | import React from 'react';
|
83 |
|
84 | import {App} from 'horse';
|
85 | import setupRoutes from './setupRoutes';
|
86 |
|
87 | var server = koa();
|
88 |
|
89 | var app = new App();
|
90 | setupRoutes(app);
|
91 |
|
92 | server.use(function *(next) {
|
93 | yield app.route(this, function () {
|
94 | var Layout = this.layout;
|
95 |
|
96 | this.body = react.renderToStaticMarkup(
|
97 | <Layout>{this.body}</Layout>
|
98 | );
|
99 | });
|
100 | }
|
101 | ```
|
102 |
|
103 | `client.es6.js`
|
104 |
|
105 | ```javascript
|
106 | import React from 'react';
|
107 | import {ClientApp} from 'horse';
|
108 |
|
109 | import setupRoutes from './setupRoutes';
|
110 |
|
111 | import jQuery as $ from 'jquery';
|
112 |
|
113 | var app = new ClientApp();
|
114 | setupRoutes(app);
|
115 |
|
116 | var $mountPoint = document.getElementById('app-container');
|
117 |
|
118 | $(function() {
|
119 | $('body').on('click', 'a', function(e) {
|
120 | var $link = $(this);
|
121 |
|
122 | var ctx = app.buildContext($link.attr('href'));
|
123 | yield app.route(ctx);
|
124 |
|
125 | React.render(ctx.body, $mountPoint);
|
126 | });
|
127 | });
|
128 |
|
129 | ```
|
130 |
|
131 |
|
132 | Final Notes
|
133 | -----------
|
134 |
|
135 | Default events:
|
136 |
|
137 | ```
|
138 | app.on('route:start', function(ctx){})
|
139 | app.on('route:end', function(ctx){})
|
140 | app.on('route:end', function(error, ctx, app){})
|
141 | ```
|
142 |
|
143 | You can also add an array of request start / end functions that operate per
|
144 | request, instead of globally on the app:
|
145 |
|
146 | ```
|
147 | app.startRequest.push(function(app, server) {
|
148 | if (server) { console.log('started on the server'); }
|
149 | });
|
150 |
|
151 | app.endRequest.push(function(app, server) {
|
152 | if (server) { console.log('started on the server'); }
|
153 | });
|
154 | ```
|
155 |
|
156 |
|
157 | * This is all written using ES6, so you'll need to use a transpiler; I like
|
158 | [babel](http://babeljs.io). To get babel to work with npm modules, you'll
|
159 | need to turn off `ignore npm` and add `.es6.js` to the transpiled files, like
|
160 | so:
|
161 |
|
162 | ```
|
163 | require('babel/register')({
|
164 | ignore: false,
|
165 | only: /.+(?:(?:\.es6\.js)|(?:.jsx))$/,
|
166 | extensions: ['.js', '.es6.js', '.jsx' ],
|
167 | sourceMap: true,
|
168 | });
|
169 | ```
|
170 |
|
171 | * Tested with iojs 1.0.0 and later and node 0.10.30 and later.
|