├── .editorconfig ├── .gitignore ├── README.md ├── config.js ├── gulpfile.js ├── images └── ReactLogo.svg ├── package.json ├── src ├── App.js ├── Flux.js ├── Uplink.js ├── UplinkServer.js ├── client.js ├── components │ ├── AboutPage.jsx │ ├── CurrentVisitorsCount.jsx │ ├── HistoryLink.jsx │ ├── HomePage.jsx │ ├── NotFoundPage.jsx │ ├── Pages.jsx │ ├── Root.jsx │ └── TotalVisitorsCount.jsx ├── dispatchers │ ├── MemoryDispatcher.js │ └── UplinkDispatcher.js ├── eventEmitters │ ├── MemoryEventEmitter.js │ └── UplinkEventEmitter.js ├── index.tpl ├── render-server.js ├── routers │ └── NavigationRouter.js ├── server.js ├── stores │ ├── MemoryStore.js │ └── UplinkStore.js ├── styles.js └── uplink-server.js ├── static └── ReactLogo.svg └── tasks ├── createComponent.js └── createComponent.tpl /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | [*] 3 | end_of_line = lf 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tmp 4 | *.pid 5 | *.lock 6 | npm-debug.log 7 | static/components.css 8 | static/client.js 9 | static/normalize.css 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React on Rails Starter Kit 2 | ========================== 3 | 4 | Plug and play Starter Kit for the [Ultimate React Framework](https://github.com/elierotenberg/react-rails). 5 | 6 | Usage 7 | ===== 8 | 0. Clone/fork 9 | 0. `npm install` 10 | 0. Hack the code 11 | 0. `npm start` 12 | 0. `gulp watch` 13 | 0. Visit `http://localhost:8080/` 14 | 15 | Important note: Don't `npm install react` or anything similar. `react-rails` includes its own version of React, 16 | and you should use it. `require('react-rails').React` if needed, but React just doesn't work well if multiple 17 | instances are in the same package. 18 | 19 | 20 | Release notes/news 21 | ================== 22 | - 30/9/2014: Should be ready for use, but expect some bugs, still very early release. 23 | Feel free to post and issue. 24 | 25 | Whats included? 26 | =============== 27 | 28 | This starter kit contains: 29 | 30 | - An opinionated, simple and efficient file structure (see below) 31 | 32 | - Preconfigured assets pipelines and build tools, including: 33 | - linting (`jshint` with `esnext` and `globals:Promise`) 34 | - Serveral bread-and-butter libs/polyfills, including: 35 | - `lodash` (fast `_` API implementation), 36 | - `bluebird` (fast `Promise` API implementation), 37 | - `R.scope` (fast `bind` for context-only function binding), 38 | - `cors` for `express`, 39 | - `co` (generators-based coroutines). 40 | 41 | - ES6/7/JSX to ES5 transpiling of many features: 42 | - Promises (`bluebird`), 43 | - `jsx` transforms (`react-tools`), 44 | - es6 generators (using [`regenerator`](https://github.com/facebook/regenerator)), 45 | - all the features supported by [`esnext`](https://github.com/esnext/esnext): 46 | - arrow functions, 47 | - classes, 48 | - comprehensions, 49 | - computed property keys, 50 | - default params, 51 | - destructuring, 52 | - iterators + for-of, 53 | - object literal concise definitions, 54 | - object literal shorthand, 55 | - rest params, 56 | - spread, 57 | - template strings. 58 | 59 | - Common JS bundling for the browser (using `browserify`) 60 | - all of the above transformations transpiled to ES5, 61 | - `fs.readFileSync` and alike static files preloading (using `brfs`), 62 | 63 | - `development`/`production` modes (configurable in `src/config.js`) to opt-in/out of: 64 | - js minification (using `UglifyJS 2`), 65 | - css minification (using `css-min`), 66 | - skip runtime types/invariants checks from React and React on Rails, 67 | - reduce console verbosity, 68 | - disable long traces support for `setImmediate` and `Promise`. 69 | 70 | - Style processing 71 | - `normalize.css` included by default, 72 | - `autoprefixer` and `css-min` (in `production` mode) on components styles and stylesheets, 73 | - optionally declare your components' style in their class definition, they get processed 74 | and bundled into `components.css` and served statically. Who needs a CSS preprocessor when you 75 | get the full power of JS? 76 | 77 | - A complete starter app source, including: 78 | - A simple components hierarchy, 79 | - A navigation router, 80 | - A memory-based Flux (Store/EventEmitter/Dispatcher) named `memory` 81 | - An uplink-based Flux named `uplink` 82 | - A static server, serving `static/` under the path `/` 83 | - A prerendering server, 84 | - A basic Uplink server implementing Flux over the wire, 85 | - Preconfigured plugins for React of Rails: 86 | - `R.History`, managing navigation, 87 | - `R.Localize`, managing i18n, 88 | - `R.Window`, managing window events, 89 | - `R.XWindow`, managing cross-window events, 90 | - `R.Fullscreen`, managing fullscreen state/events, 91 | 92 | - Several scaffolding tools: 93 | - `gulp component --displayName=MyComponent [--tagName=div]` to scaffold a component 94 | named `MyComponent` in `src/components`, and populates its `render` method with the appropriate JSX element. 95 | JSX element tagName defaults to div, but can be anything like "span", "MyOtherComponent", etc. 96 | Component name should match /[A-Z][a-zA-Z0-9]*$/, ie. be like MyComponent, not like myComponent or my_Component. 97 | - `gulp import-all-components` to update `src/componentsClasses` to reflect all the components in `src/components`. 98 | - `npm start` that "just works". 99 | 100 | 101 | What now? 102 | ========= 103 | 104 | Everything in this repo is configurable. 105 | 106 | - Just want to plug in your components? Head to `src/components` and consider using `gulp component` for scaffolding. 107 | - Want to implement client-side global logic? Head to `src/dispatchers/MemoryDispatcher`. 108 | - Want to implement server-side global logic? Head to `src/dispatchers/UplinkDispatcher`. 109 | - Want to modify how you HTML contains? Head to `src/index.tpl`. 110 | - Want to include more stylesheets, external components or customize which plugins are used? Head to `src/App.js`. 111 | - Want to customize your passive REST backend? Head to `src/Uplink.js`. 112 | 113 | File structure 114 | ============== 115 | 116 | The file structure has been carefully curated to be pleasant to work with. 117 | You are free to modify it but many things such as automated tasks and cross-dependencies depends on it. 118 | 119 | ``` 120 | Project root 121 | +--package.json 122 | | Package configuration. Make sure to update it by running `npm init`. 123 | | Core module `assert` is listed as a dependency because oddly browserify requires it. 124 | | 125 | +--config.js 126 | | Configuration variables. Keys are straightforward, notable keys include 127 | | `supportedLocales`, `mode` (either `development` or `production`), and render/uplink 128 | | server hostnames and ports. 129 | | Don't mind the convoluted trick with process["env"]["NODE_ENV"], its working as intented. 130 | | 131 | +--.gitignore 132 | | In addition to `node_modules` and the usual, contains `dist` and `tmp`. 133 | | Consider removing `node_modules` and `dist` if you plan to use git for deployment. 134 | | 135 | +--README.md 136 | | This file. Overwrite with you own README.md. 137 | | 138 | +--gulpfile.js 139 | | Contains a series of preconfigured tasks. You can safely add yours. 140 | | Notable tasks include: 141 | | - `default`, which builds everything, puts the server executables in `dist` and the 142 | | bundled client in `static`, 143 | | - `watch`, which automatically rebuilds everything that changes, 144 | | - `component`, which bootstraps a new component using eg. 145 | | `gulp component --displayName="MyComponent"` 146 | +--src 147 | | | Single source of truth for the building pipeline. Contains all of your actual source. 148 | | | Files in this directory will be transpiled to ES3, but you can safely use `jsx` tags 149 | | | (in `.jsx` files), generators (`function*()`), arrow functions, destructuring, etc. 150 | | | 151 | | +--components 152 | | | | Directory for the components classes. 153 | | | | You should have exactly one file per component class, with the extension `.jsx` 154 | | | | and the `jsx` pragma `/** @jsx React.DOM */`. This file should export a single 155 | | | | value, which should be the result of `React.createClass`. 156 | | | | Components class names should have their first letter uppercased. 157 | | | | Most components should have `R.Component.Mixin`. 158 | | | | You may easily scaffold new components using 159 | | | | `gulp component --displayName=MyComponent` (see below). 160 | | | | 161 | | | +--Root.jsx 162 | | | | Default root component, preconfigured to play nicely as an isomorphic router 163 | | | | backed by the `memory` store populated by `R.History.Plugin`. 164 | | | | Routes are read from `/src/routers/NavigationRouter`. 165 | | | | Feel free to modify the routing behaviour, remove it, or move it 166 | | | | into another component. 167 | | | | 168 | | | +--HistoryLink.jsx 169 | | | | App-level link component preconfigured to play nicely with `R.History.Plugin`, 170 | | | | backed by the `memory` dispatcher. 171 | | | 172 | | +--dispatchers 173 | | | | Directory for the dispatchers classes. 174 | | | | You should have exactly one file per dispatcher class. This file should export a 175 | | | | single value, which should be the result of `R.Dispatcher.createDispatcher`. 176 | | | | Each dispatcher class defines how actions are dispatched, and most often trigger 177 | | | | side effects in the current Flux instance, such as updating a store. 178 | | | | A single dispatcher class can be used, but it is recommended to use one dispatcher 179 | | | | class per backend (eg. one for local data, one for server-sent data) to avoid 180 | | | | confusion. 181 | | | | Most dispatchers will simply be instances of a `R.Dispatcher.createDispatcher` 182 | | | | constructor, but since Dispatcher is just an API contract, you might want to 183 | | | | implement your own. 184 | | | | 185 | | | +--MemoryDispatcher.js 186 | | | | Default memory dispatcher. Add your actions listeners here. 187 | | | | 188 | | | +--UplinkDispatcher.js 189 | | | | Default uplink dispatcher. Add your actions listeners here. 190 | | | | Note that an uplink dispatcher usually won't actually do much on its own; most 191 | | | | times it will only check input and forward appropriate messages to the underlying 192 | | | | `Uplink` instance. 193 | | | 194 | | +--eventEmitters 195 | | | | Directory for the event emitters classes. 196 | | | | You should have exactly one file per event emitter class. This file should export 197 | | | | a single value, which should be the result of `R.EventEmitter.createEventEmitter`. 198 | | | | Most event emitters will use presets from `R.EventEmitter`, but since EventEmitter 199 | | | | is just an API contract, you might want to implement your own. 200 | | | | 201 | | | +--MemoryEventEmitter.js 202 | | | | Cached value of calling `R.EventEmitter.createMemoryEventEmitter`. 203 | | | | Represents a local event emitter residing in memory. It exposes an `emit` method 204 | | | | which a Dispatcher (usually a MemoryDispatcher) may invoke. 205 | | | | 206 | | | +--UplinkEventEmitter.js 207 | | | | Cache value of calling `R.EventEmitter.createUplinkEventEmitter`. 208 | | | | Represents a remote event emitter residing in an uplink server. It is 209 | | | | subscribe-only and a Dispatcher may not emit directly, only pass actions to an 210 | | | | uplink server that will then emit. 211 | | | 212 | | +--routers 213 | | | | Directory for the routers classes. 214 | | | | You should have exactly one file per router class. This file should export a 215 | | | | single value, which should be derived from `R.Router`. 216 | | | | Most routers will simply derive `R.Router` by prototypal inheritance and 217 | | | | adding some routes, but since Router is just and API contract, you might want 218 | | | | to implement your own. 219 | | | | Remember that in `R`, routers are just URL-patterns-friendly generalized regular 220 | | | | expressions, and are passive objects waiting for you to call `match` on them. 221 | | | | 222 | | | +--NavigationRouter.js 223 | | | | An demo navigation router, feel free to edit it. 224 | | | | Note that `/src/components/Root` relies on its behaviour, update it accordingly. 225 | | | 226 | | +--stores 227 | | | | Directory for the stores classes. 228 | | | | You should have exactly one file per store class. This file should export a 229 | | | | single value, which should be the result of `R.Store.createStore`. 230 | | | | Most event emitters will use presets from `R.Store`, but since Store is just an 231 | | | | API contract, you might want to implement your own. 232 | | | | 233 | | | +--MemoryStore.js 234 | | | | Cached value of calling `R.Store.createMemoryStore`. 235 | | | | Represents a local store residing in memory. It exposes a `set` method which a 236 | | | | Dispatcher (usually a MemoryDispatcher) may invoke. 237 | | | | 238 | | | +--UplinkStore.js 239 | | | | Cached value of calling `R.Store.createUplinkStore`. 240 | | | | Represents the local reflection of a store residing in an Uplink server. This 241 | | | | reflection is automatically updated whenever the Uplink server is updated. 242 | | | 243 | | +--App.js 244 | | | Main `R.App` class. 245 | | | You may want to modify the main HTML file template, do more stuff at template vars 246 | | | bootstrapping time, load more stylesheets, etc. 247 | | | Default configuration include general purpose plugins (Window, History, Localize, 248 | | | Fullscreen, XWindow), which you may remove if you don't want them, as well as 249 | | | `normalize.css`. 250 | | | If you want to add your CSS framework of choice (such as Twitter Bootstrap or Pure), 251 | | | just drop your stylesheet there. 252 | | | Don't forget to copy the stylesheet in `static` at build time (see 253 | | | `gulpfile.js`). 254 | | | If you need more scripts to be loaded on the client (eg. Facebook SDK), you can also 255 | | | add them, but note that they won't be available in node, so make sure no isomorphic 256 | | | code depends on them. 257 | | | 258 | | +--client.js 259 | | | Client entry point. Simply mounts the class on the client. You most likey won't modify 260 | | | anything here. 261 | | | 262 | | +--Flux.js 263 | | | Main `R.Flux` class. 264 | | | Here is setup the context in which each app instance will run (either on the client or 265 | | | on the server). 266 | | | You may want to add more initalization, but beware not to create leaks, such as 267 | | | timeouts or intervals. 268 | | | This file is typically where your define your remote connections (such as uplink or 269 | | | REST client), stores, event emitters and dispatchers. 270 | | | The default configuration includes: 271 | | | - one uplink client 272 | | | - one memory-backed store (`memory`) 273 | | | - one uplink-backed store (`uplink`) 274 | | | - one memory-backed event emitter (`memory`) 275 | | | - one uplink-backed event emitter (`uplink`) 276 | | | - one dispatcher intented to handle purely local actions (`memory`) 277 | | | - one dispatcher intented to handle local-remote actions (`uplink`) 278 | | | 279 | | +--index.tpl 280 | | | Main HTML template file. You probably won't need to modify it, since the default 281 | | | template is production-ready, assuming `/App` is correctly configured. 282 | | | 283 | | +--render-server.js 284 | | | Render/static server entry point. Starts a new `express` server and mounts a 285 | | | `static` middleware to serve `/static` as `/`. 286 | | | Feel free to replace `express` by something else or add more middleware (caching, etc). 287 | | | 288 | | +--server.js 289 | | | Simple process manager whose sole job is to start both `/render-server` and 290 | | | `/uplink-server` as child processes. 291 | | | In addition, server.js watches for changes in `dist` and restarts its children 292 | | | whenever its contents changes. Ideal in combination with `gulp watch`. 293 | | | 294 | | +--uplink-server.js 295 | | | Uplink server entry point. Starts a new `express` server and mounts the uplink server. 296 | | | 297 | | +--Uplink.js 298 | | | Main `R.Uplink` class. Simple `Uplink` client configured to work well. 299 | | | 300 | | +--UplinkServer.js 301 | | | Main `R.UplinkServer` class. Resembles closely an all-in-one dispatcher. 302 | | | Here you can modify what happens when a new session is created, or a session is 303 | | | destroyed (either leaves or expires). 304 | | | Stores and event emitterw need to be explicitly whitelisted (router-like patterns 305 | | | are accepted). 306 | | | Actions handlers are passed to their handler generators. 307 | | | You may want to use locks to avoid race conditions (see `R.Lock`). 308 | | 309 | +--dist 310 | | Don't put anything here. Its intented to be populated and cleaned by automated tasks. 311 | | 312 | +--static 313 | | All files in this directory will be publicly accessible. 314 | | This is were the browserified client build is put, as `client.js`. 315 | | You can put here custom CSS stylesheets, external JS deps, images, favicon.ico, etc. 316 | | By default, should contain "normalize.css", copied from the npm package `normalize.css` 317 | | for automatic update. 318 | | 319 | +--tasks 320 | | | Quality of life tasks to ease your development/deployment experience. 321 | | | 322 | | +--createAllComponentsStylesheets.js 323 | | | Extracts all the styles declared inside components source files, process them, and 324 | | | bundles them into the appropriate .css files in `static/`. For example, if a components' 325 | | | `statics.getStylesheetRules` returned { components: ..., main: ... }, then the rules 326 | | | will respectively get dumped into `static/components.css` and `static/main.css`. 327 | | | 328 | | +--createComponent.js 329 | | | Pass a component name as `--displayName="ComponentName"`. 330 | | | Creates a new file into `/src/components` in a new `.jsx` file, containing a predefined 331 | | | template. 332 | | | 333 | | +--createComponent.tpl 334 | | | Template injected into new components files. Feel free to modify it, for example if you 335 | | | want to include more or less libs. 336 | ``` 337 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var mode = 'development'; 2 | 3 | try { 4 | // Convoluted way to avoid browserify (with envify) to throw 5 | // errors at us. You shouldn't modify this. 6 | process['env']['NODE_ENV'] = mode; 7 | } 8 | catch(err) {} 9 | 10 | var config = { 11 | install: { 12 | mode: mode, 13 | }, 14 | supportedLocales: ['en-US', 'fr-FR'], 15 | renderServer: { 16 | hostname: 'localhost', 17 | port: 8080, 18 | }, 19 | uplinkServer: { 20 | hostname: 'localhost', 21 | port: 8000, 22 | prefix: '/uplink/', 23 | }, 24 | }; 25 | 26 | module.exports = config; 27 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require('6to5/polyfill'); 2 | var _ = require('lodash-next'); 3 | var browserify = require('browserify'); 4 | var buffer = require('vinyl-buffer'); 5 | var del = require('del'); 6 | var envify = require('envify/custom'); 7 | var es6to5 = require('gulp-6to5'); 8 | var gulp = require('gulp'); 9 | var gutil = require('gulp-util'); 10 | var imagemin = require('gulp-imagemin'); 11 | var insert = require('gulp-insert'); 12 | var jshint = require('gulp-jshint'); 13 | var merge = require('merge-stream'); 14 | var plumber = require('gulp-plumber'); 15 | var postcss = require('gulp-postcss'); 16 | var Promise = require('bluebird'); 17 | var R = require('react-rails'); 18 | var react = require('gulp-react'); 19 | var rename = require('gulp-rename'); 20 | var source = require('vinyl-source-stream'); 21 | var sourcemaps = require('gulp-sourcemaps'); 22 | var stylish = require('jshint-stylish'); 23 | var uglify = require('gulp-uglify'); 24 | 25 | var createComponent = require('./tasks/createComponent'); 26 | 27 | var config = require('./config'); 28 | 29 | var dev = config.install.mode !== 'production'; 30 | var prod = !dev; 31 | var style = require('gulp-react-rails-style')(R, dev ? [R.Style.Processors.autoprefixer] : [R.Style.Processors.autoprefixer, R.Style.Processors.min]); 32 | 33 | var statics = [ 34 | 'node_modules/normalize.css/normalize.css', 35 | ]; 36 | 37 | var jshintOptions = { 38 | globals: { 39 | Promise: true, 40 | }, 41 | esnext: true, 42 | sub: true, 43 | }; 44 | 45 | var bustConfig = function bustConfig() { 46 | var module = require.resolve('./config'); 47 | if(require.cache[module]) { 48 | delete require.cache[module]; 49 | require('./config'); 50 | }; 51 | return Promise.resolve(void 0); 52 | }; 53 | 54 | var browserifyClient = function browserifyClient() { 55 | var b = browserify({ 56 | fullPaths: false, 57 | entries: ['./dist/client.js'], 58 | debug: dev, 59 | ignoreMissing: ['promise'], 60 | }); 61 | 62 | var NODE_ENV = prod ? 'production': 'development'; 63 | 64 | b.transform('brfs'); 65 | b.transform(envify({ 66 | NODE_ENV: NODE_ENV, 67 | })); 68 | 69 | return b.bundle() 70 | .pipe(plumber()) 71 | .pipe(source('client.js')) 72 | .pipe(buffer()) 73 | .pipe(prod ? uglify() : gutil.noop()) 74 | .pipe(gulp.dest('./static')); 75 | }; 76 | 77 | var lintJS = function lintJS() { 78 | return gulp.src('src/**/*.js') 79 | .pipe(plumber()) 80 | .pipe(jshint(jshintOptions)) 81 | .pipe(jshint.reporter(stylish)); 82 | }; 83 | 84 | var lintJSX = function lintJSX() { 85 | return gulp.src('src/**/*.jsx') 86 | .pipe(plumber()) 87 | .pipe(react()) 88 | .pipe(jshint(jshintOptions)) 89 | .pipe(jshint.reporter(stylish)); 90 | }; 91 | 92 | var lint = function lint() { 93 | return merge(lintJS(), lintJSX()); 94 | }; 95 | 96 | var compileSources = function compileSources() { 97 | return gulp.src(['src/**/*.js', 'src/**/*.jsx']) 98 | .pipe(plumber()) 99 | .pipe(sourcemaps.init()) 100 | .pipe(react()) 101 | .pipe(insert.prepend('require(\'6to5/polyfill\');\nconst Promise = require(\'bluebird\');\n')) 102 | .pipe(rename({ 103 | extname: '.js', 104 | })) 105 | .pipe(es6to5({ })) 106 | .pipe(sourcemaps.write()) 107 | .pipe(gulp.dest('dist')); 108 | }; 109 | 110 | var copyTemplates = function copyTemplates() { 111 | return gulp.src('src/**/*.tpl') 112 | .pipe(plumber()) 113 | .pipe(gulp.dest('dist')); 114 | }; 115 | 116 | var copyStatics = function copyStatics() { 117 | return gulp.src(statics) 118 | .pipe(plumber()) 119 | .pipe(gulp.dest('static')); 120 | }; 121 | 122 | var compileStyles = function compileStyles() { 123 | return gulp.src('dist/components/*.js') 124 | .pipe(plumber()) 125 | .pipe(style(__dirname + '/dist/styles')) 126 | .pipe(postcss([])) 127 | .pipe(gulp.dest('static')) 128 | }; 129 | 130 | var promisify = function promisify(stream, name) { 131 | return new Promise(function(resolve, reject) { 132 | gutil.log('Starting ' + name + '...'); 133 | stream 134 | .on('error', reject) 135 | .on('end', function() { 136 | gutil.log('Finished ' + name + '.'); 137 | }) 138 | .on('end', resolve); 139 | }); 140 | }; 141 | 142 | gulp.task('build', function(done) { 143 | promisify(lint(), 'lint') 144 | .then(function() { 145 | return bustConfig(); 146 | }) 147 | .then(function() { 148 | return Promise.all([ 149 | promisify(compileSources(), 'compileSources'), 150 | promisify(copyTemplates(), 'copyTemplates'), 151 | promisify(copyStatics(), 'copyStatics'), 152 | ]); 153 | }) 154 | .then(function() { 155 | return Promise.all([ 156 | promisify(compileStyles(), 'compileStyles'), 157 | promisify(browserifyClient(), 'browserifyClient'), 158 | ]); 159 | }) 160 | .then(function() { 161 | done(null); 162 | }) 163 | .catch(function(err) { 164 | gutil.log('Build error', err); 165 | done(null); 166 | }); 167 | }); 168 | 169 | gulp.task('clean', function() { 170 | del(['dist']); 171 | }); 172 | 173 | gulp.task('watch', function() { 174 | gulp.watch(['src/**/*', 'config.js'], ['build']); 175 | gulp.watch('images/**/*', ['imagemin']); 176 | }); 177 | 178 | gulp.task('component', function() { 179 | createComponent(gutil.env.displayName, gutil.env.tagName); 180 | }); 181 | 182 | gulp.task('imagemin', function() { 183 | gulp.src(['images/*.{png,jpg,gif,svg}']) 184 | .pipe(plumber()) 185 | .pipe(imagemin()) 186 | .pipe(gulp.dest('static')); 187 | }); 188 | 189 | gulp.task('default', ['build', 'imagemin']); 190 | -------------------------------------------------------------------------------- /images/ReactLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-rails-starterkit", 3 | "version": "0.3.0", 4 | "description": "React on Rails starter kit", 5 | "scripts": { 6 | "start": "node dist/server.js", 7 | "install": "gulp" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/elierotenberg/react-rails-starterkit.git" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "react-rails", 16 | "starterkit" 17 | ], 18 | "author": "Elie Rotenberg ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/elierotenberg/react-rails-starterkit/issues" 22 | }, 23 | "homepage": "https://github.com/elierotenberg/react-rails-starterkit", 24 | "dependencies": { 25 | "6to5": "^1.11.3", 26 | "assert": "^1.1.2", 27 | "bluebird": "^2.3.2", 28 | "brfs": "^1.2.0", 29 | "browserify": "^6.0.2", 30 | "co": "^3.1.0", 31 | "cors": "^2.4.2", 32 | "del": "^0.1.3", 33 | "envify": "^3.0.0", 34 | "express": "^4.9.4", 35 | "gulp": "^3.8.8", 36 | "gulp-6to5": "^1.0.1", 37 | "gulp-cached": "^1.0.1", 38 | "gulp-changed": "^1.0.0", 39 | "gulp-imagemin": "^1.2.1", 40 | "gulp-insert": "^0.4.0", 41 | "gulp-jshint": "^1.9.0", 42 | "gulp-nodemon": "^1.0.4", 43 | "gulp-plumber": "^0.6.6", 44 | "gulp-postcss": "^2.0.0", 45 | "gulp-react": "^2.0.0", 46 | "gulp-react-rails-style": "^0.1.0", 47 | "gulp-regenerator": "^1.1.0", 48 | "gulp-rename": "^1.2.0", 49 | "gulp-sourcemaps": "^1.2.8", 50 | "gulp-uglify": "^1.0.1", 51 | "gulp-util": "^3.0.1", 52 | "jshint-stylish": "^1.0.0", 53 | "lodash-next": "^1.0.1", 54 | "merge-stream": "^0.1.6", 55 | "node-watch": "^0.3.4", 56 | "normalize.css": "^3.0.1", 57 | "open": "0.0.5", 58 | "postcss-url": "^1.0.0", 59 | "react-rails": "^0.2.5", 60 | "react-tools": "^0.11.2", 61 | "through2": "^0.6.2", 62 | "vinyl-buffer": "^1.0.0", 63 | "vinyl-source-stream": "^1.0.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var NavigationRouter = require('./routers/NavigationRouter'); 3 | 4 | var _ = require('lodash-next'); 5 | var config = require('../config'); 6 | var fs = require('fs'); 7 | 8 | var tpl = _.template(fs.readFileSync(__dirname + '/index.tpl', 'utf-8')); 9 | var router = new NavigationRouter(); 10 | 11 | var App = R.App.createApp({ 12 | fluxClass: require('./Flux'), 13 | rootClass: require('./components/Root'), 14 | 15 | bootstrapTemplateVarsInServer(req) { 16 | return (fn) => { 17 | _.defer(() => { 18 | fn(null, _.extend({ 19 | lang: R.Localize.extractLocale(req.headers, config.supportedLocales), 20 | }, router.match(req.path))); 21 | }); 22 | }; 23 | }, 24 | 25 | vars: { 26 | stylesheets: [ 27 | // normalize.css 28 | '/normalize.css', 29 | // generated components CSS 30 | '/components.css', 31 | // Google WebFonts: Open Sans Condensed 32 | 'http://fonts.googleapis.com/css?family=Open+Sans+Condensed:300', 33 | // Google WebFonts: Roboto 34 | 'http://fonts.googleapis.com/css?family=Roboto', 35 | ], 36 | scripts: ['/client.js'], 37 | }, 38 | 39 | template(vars, libs) { 40 | return tpl({ vars: vars, libs: libs }); 41 | }, 42 | 43 | templateLibs: { 44 | _: _, 45 | }, 46 | 47 | plugins: { 48 | 'Window': R.Window.createPlugin('memory', 'memory'), 49 | 'History': R.History.createPlugin('memory', 'memory', 'memory'), 50 | 'Localize': R.Localize.createPlugin('memory', 'memory', config.supportedLocales), 51 | 'Fullscreen': R.Fullscreen.createPlugin('memory', 'memory'), 52 | 'XWindow': R.XWindow.createPlugin('memory', 'memory'), 53 | }, 54 | }); 55 | 56 | R.Style.registerCSSProcessor(R.Style.Processors.autoprefix); 57 | R.Debug.prod(() => { 58 | R.Style.registerCSSProcessor(R.Style.Processors.min); 59 | }); 60 | 61 | module.exports = App; 62 | -------------------------------------------------------------------------------- /src/Flux.js: -------------------------------------------------------------------------------- 1 | var MemoryDispatcher = require('./dispatchers/MemoryDispatcher'); 2 | var MemoryEventEmitter = require('./eventEmitters/MemoryEventEmitter'); 3 | var MemoryStore = require('./stores/MemoryStore'); 4 | var R = require('react-rails'); 5 | var Uplink = require('./Uplink'); 6 | var UplinkDispatcher = require('./dispatchers/UplinkDispatcher'); 7 | var UplinkEventEmitter = require('./eventEmitters/UplinkEventEmitter'); 8 | var UplinkStore = require('./stores/UplinkStore'); 9 | 10 | var Flux = R.Flux.createFlux({ 11 | bootstrap: function* bootstrap(uplink) { 12 | this.registerStore('memory', new MemoryStore()); 13 | this.registerStore('uplink', new UplinkStore(uplink)); 14 | yield Promise.resolve(void 0); 15 | }, 16 | 17 | bootstrapInClient: function* bootstrapInClient(window, headers, guid) { 18 | var uplink = new Uplink(guid); 19 | yield uplink.ready; 20 | yield this.bootstrap(uplink); 21 | this.registerEventEmitter('memory', new MemoryEventEmitter()); 22 | this.registerEventEmitter('uplink', new UplinkEventEmitter(uplink)); 23 | this.registerDispatcher('memory', new MemoryDispatcher(this)); 24 | this.registerDispatcher('uplink', new UplinkDispatcher(this, uplink)); 25 | }, 26 | 27 | bootstrapInServer: function* bootstrapInServer(req, headers, guid) { 28 | var uplink = new Uplink(guid); 29 | yield uplink.ready; 30 | yield this.bootstrap(uplink); 31 | }, 32 | }); 33 | 34 | module.exports = Flux; 35 | -------------------------------------------------------------------------------- /src/Uplink.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | var config = require('../config'); 4 | 5 | function Uplink(guid) { 6 | var url = 'http://' + config.uplinkServer.hostname + ':' + config.uplinkServer.port + config.uplinkServer.prefix; 7 | 8 | if(R.isClient()) { 9 | return new R.Uplink(url, url, guid, true); 10 | } 11 | 12 | if(R.isServer()) { 13 | return new R.Uplink(url, null, guid, false); 14 | } 15 | } 16 | 17 | module.exports = Uplink; 18 | -------------------------------------------------------------------------------- /src/UplinkServer.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | var UplinkServer = R.SimpleUplinkServer.createServer({ 4 | sessionTimeout: 10000, 5 | 6 | _locks: { 7 | counters: new R.Lock(), 8 | }, 9 | 10 | bootstrap: function *bootstrap() { 11 | yield this._locks.counters.acquire(); 12 | yield this.setStore('/counters', { 13 | total: 0, 14 | current: 0, 15 | }); 16 | this._locks.counters.release(); 17 | }, 18 | 19 | sessionCreated: function *sessionCreated(guid) { 20 | yield this._locks.counters.acquire(); 21 | var counter = yield this.getStore('/counters'); 22 | yield this.setStore('/counters', { 23 | total: counter.total + 1, 24 | current: counter.current + 1, 25 | }); 26 | this._locks.counters.release(); 27 | }, 28 | 29 | sessionDestroyed: function *sessionDestroyed(guid) { 30 | yield this._locks.counters.acquire(); 31 | var counters = yield this.getStore('/counters'); 32 | yield this.setStore('/counters', _.extend(counters, { 33 | current: counters.current - 1, 34 | })); 35 | this._locks.counters.release(); 36 | }, 37 | 38 | store: [ 39 | '/counters', 40 | ], 41 | 42 | events: [ 43 | // 44 | ], 45 | 46 | actions: { 47 | // : function*(params) 48 | }, 49 | }); 50 | 51 | module.exports = UplinkServer; 52 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var co = require('co'); 3 | 4 | var App = require('./App'); 5 | 6 | var client = new R.Client(App); 7 | co(client.mount).call(client); 8 | -------------------------------------------------------------------------------- /src/components/AboutPage.jsx: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var React = R.React; 3 | 4 | var AboutPage = React.createClass({ 5 | mixins: [R.Component.Mixin], 6 | 7 | statics: { 8 | getStylesheetRules() { 9 | return { 10 | 'components': { 11 | '.AboutPage': { 12 | }, 13 | }, 14 | }; 15 | }, 16 | }, 17 | 18 | render() { 19 | return ( 20 |
21 |

About page

22 |
23 | ); 24 | }, 25 | }); 26 | 27 | module.exports = AboutPage; 28 | -------------------------------------------------------------------------------- /src/components/CurrentVisitorsCount.jsx: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var React = R.React; 3 | 4 | var CurrentVisitorsCount = React.createClass({ 5 | mixins: [R.Component.Mixin], 6 | 7 | statics: { 8 | getStylesheetRules() { 9 | return { 10 | 'components': { 11 | '.CurrentVisitorsCount': { 12 | }, 13 | }, 14 | }; 15 | }, 16 | }, 17 | 18 | getFluxStoreSubscriptions(props) { 19 | return { 20 | 'uplink://counters': 'counters', 21 | }; 22 | }, 23 | 24 | render() { 25 | return ( 26 |
27 | CurrentVisitorsCount: {this.state.counters ? this.state.counters.current : null} 28 |
29 | ); 30 | }, 31 | }); 32 | 33 | module.exports = CurrentVisitorsCount; 34 | -------------------------------------------------------------------------------- /src/components/HistoryLink.jsx: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | var HistoryLink = R.History.createLinkClass({ 4 | dispatcherName: 'memory', 5 | }); 6 | 7 | module.exports = HistoryLink; 8 | -------------------------------------------------------------------------------- /src/components/HomePage.jsx: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var React = R.React; 3 | 4 | var HomePage = React.createClass({ 5 | mixins: [R.Component.Mixin], 6 | 7 | render() { 8 | return ( 9 |
10 |

Home page

11 | More information in the github repos: 12 | 24 |
25 | ); 26 | }, 27 | }); 28 | 29 | module.exports = HomePage; 30 | -------------------------------------------------------------------------------- /src/components/NotFoundPage.jsx: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var React = R.React; 3 | 4 | var NotFoundPage = React.createClass({ 5 | mixins: [R.Component.Mixin], 6 | 7 | propTypes: { 8 | splat: React.PropTypes.string.isRequired, 9 | }, 10 | 11 | render() { 12 | return ( 13 |
14 |

Not found page

15 |
Requested splat: {this.props.splat}
16 |
17 | ); 18 | }, 19 | }); 20 | 21 | module.exports = NotFoundPage; 22 | -------------------------------------------------------------------------------- /src/components/Pages.jsx: -------------------------------------------------------------------------------- 1 | var AboutPage = require('./AboutPage'); 2 | var CurrentVisitorsCount = require('./CurrentVisitorsCount'); 3 | var HistoryLink = require('./HistoryLink'); 4 | var HomePage = require('./HomePage'); 5 | var NavigationRouter = require('../routers/NavigationRouter'); 6 | var NotFoundPage = require('./NotFoundPage'); 7 | var R = require('react-rails'); 8 | var React = R.React; 9 | var TotalVisitorsCount = require('./TotalVisitorsCount'); 10 | 11 | var styles = require('../styles'); 12 | 13 | var navigationRouter = new NavigationRouter(); 14 | 15 | var Pages = React.createClass({ 16 | mixins: [R.Component.Mixin], 17 | 18 | statics: { 19 | getStylesheetRules() { 20 | return { 21 | 'components': { 22 | '.Pages': { 23 | width: '100%', 24 | }, 25 | '.Pages-header': { 26 | backgroundColor: styles.swatch.TextGrey, 27 | height: 300, 28 | textAlign: 'center', 29 | }, 30 | '.Pages-header h1': { 31 | color: styles.swatch.ReactBlue, 32 | fontFamily: styles.fonts.Helvetica, 33 | fontSize: 64, 34 | fontWeight: 400, 35 | margin: '0 auto', 36 | paddingTop: 60, 37 | width: styles.pageWidth, 38 | }, 39 | '.Pages-header h2': { 40 | color: styles.swatch.TitleGrey, 41 | fontSize: 24, 42 | lineHeight: '33px', 43 | margin: '0 auto', 44 | textTransform: 'uppercase', 45 | width: styles.pageWidth, 46 | }, 47 | '.Pages-ReactLogo': { 48 | display: 'inline-block', 49 | height: 25, 50 | marginBottom: -4, 51 | width: 25, 52 | }, 53 | '.Pages-main': { 54 | margin: '20px auto', 55 | width: styles.pageWidth, 56 | }, 57 | '.Pages-footer': { 58 | margin: '0 auto', 59 | width: styles.pageWidth, 60 | }, 61 | '.Pages-links': { 62 | display: 'table', 63 | listStyleType: 'none', 64 | margin: 0, 65 | padding: 0, 66 | textAlign: 'center', 67 | width: '100%', 68 | }, 69 | '.Pages-links > li': { 70 | display: 'table-cell', 71 | width: 100 / 3 + '%', 72 | }, 73 | '.Pages-counters': { 74 | bottom: 0, 75 | padding: 20, 76 | position: 'fixed', 77 | right: 0, 78 | }, 79 | }, 80 | }; 81 | }, 82 | }, 83 | getFluxStoreSubscriptions(props) { 84 | return { 85 | 'memory://History/pathname': 'pathname', 86 | }; 87 | }, 88 | 89 | fluxStoreDidUpdate() { 90 | var route = navigationRouter.match(this.state.pathname); 91 | document.querySelector('title').innerHTML = route.title; 92 | document.querySelector('meta[name=\'description\']') 93 | .setAttribute('description', route.description); 94 | }, 95 | 96 | getCurrentPage() { 97 | var matchedRoute = navigationRouter.match(this.state.pathname); 98 | switch (matchedRoute.name) { 99 | case 'home': return ; 100 | case 'about': return ; 101 | default: return ; 102 | } 103 | }, 104 | 105 | render() { 106 | return ( 107 |
108 |
109 |

React on Rails

110 |

A React Framework for building Real-World WebApps

111 |
112 |
113 | {this.getCurrentPage()} 114 |
115 |
116 |
    117 |
  • Home
  • 118 |
  • About
  • 119 |
  • Nothing relevant
  • 120 |
121 |
122 | 126 |
127 | ); 128 | }, 129 | }); 130 | 131 | module.exports = Pages; 132 | -------------------------------------------------------------------------------- /src/components/Root.jsx: -------------------------------------------------------------------------------- 1 | var Pages = require('./Pages'); 2 | var R = require('react-rails'); 3 | var React = R.React; 4 | 5 | var styles = require('../styles'); 6 | 7 | var Root = React.createClass({ 8 | mixins: [R.Root.Mixin], 9 | 10 | statics: { 11 | getStylesheetRules() { 12 | return { 13 | 'components': { 14 | '.Root': { 15 | width: '100%', 16 | }, 17 | 'html, body': { 18 | color: styles.swatch.TextGrey, 19 | fontFamily: styles.fonts.Helvetica, 20 | }, 21 | 'a, a:hover, a:visited, a:active': { 22 | textDecoration: 'none', 23 | }, 24 | 'a': { 25 | color: styles.swatch.LinkGrey, 26 | }, 27 | 'a:hover': { 28 | color: styles.swatch.LinkHoverGrey, 29 | }, 30 | 'a:active': { 31 | color: '#fff', 32 | }, 33 | }, 34 | }; 35 | }, 36 | }, 37 | 38 | render() { 39 | return ( 40 |
41 | {this.props.children ? this.props.children : } 42 |
43 | ); 44 | }, 45 | }); 46 | 47 | module.exports = Root; 48 | -------------------------------------------------------------------------------- /src/components/TotalVisitorsCount.jsx: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var React = R.React; 3 | 4 | var TotalVisitorsCount = React.createClass({ 5 | mixins: [R.Component.Mixin], 6 | 7 | getFluxStoreSubscriptions(props) { 8 | return { 9 | 'uplink://counters': 'counters', 10 | }; 11 | }, 12 | 13 | render() { 14 | return ( 15 |
16 | TotalVisitorsCount: {this.state.counters ? this.state.counters.total : null} 17 |
18 | ); 19 | }, 20 | }); 21 | 22 | module.exports = TotalVisitorsCount; 23 | -------------------------------------------------------------------------------- /src/dispatchers/MemoryDispatcher.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | function MemoryDispatcher(flux) { 4 | return new (R.Dispatcher.createDispatcher({ 5 | displayName: 'MemoryDispatcher', 6 | actions: { 7 | // : function*(params) 8 | }, 9 | }))(); 10 | } 11 | 12 | module.exports = MemoryDispatcher; 13 | -------------------------------------------------------------------------------- /src/dispatchers/UplinkDispatcher.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | function UplinkDispatcher(flux, uplink) { 4 | return new (R.Dispatcher.createDispatcher({ 5 | displayName: 'UplinkDispatcher', 6 | actions: { 7 | // : function*(params) 8 | }, 9 | }))(); 10 | } 11 | 12 | module.exports = UplinkDispatcher; 13 | -------------------------------------------------------------------------------- /src/eventEmitters/MemoryEventEmitter.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | var MemoryEventEmitter = R.EventEmitter.createMemoryEventEmitter(); 4 | 5 | module.exports = MemoryEventEmitter; 6 | -------------------------------------------------------------------------------- /src/eventEmitters/UplinkEventEmitter.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | var UplinkEventEmitter = R.EventEmitter.createUplinkEventEmitter(); 4 | 5 | module.exports = UplinkEventEmitter; 6 | -------------------------------------------------------------------------------- /src/index.tpl: -------------------------------------------------------------------------------- 1 | <% _ = libs._; %> 2 | <% if(vars.lang) { %>'><% } else { %><% } %> 3 | 4 | 5 | <% if(vars.charset) { %><% } else { %><% } %> 6 | 7 | <% if(vars.base) { %><% } %> 8 | <% if(vars.description) { %><% } %> 9 | <% if(vars.viewport) { %><% } else { %><% } %> 10 | <% if(vars.title) { %><%- vars.title %><% } %> 11 | <% if(vars.stylesheets) { %> 12 | <% _.each(vars.stylesheets, function(href) { %> 13 | 14 | <% }); %> 15 | <% } %> 16 | 17 | 18 |
19 | <%= vars.rootHtml %> 20 |
21 | 27 | <% if(vars.scripts) { %> 28 | <% _.each(vars.scripts, function(script) { %> 29 | 30 | <% }); %> 31 | <% } %> 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/render-server.js: -------------------------------------------------------------------------------- 1 | var App = require('./App'); 2 | var R = require('react-rails'); 3 | 4 | var config = require('../config'); 5 | var cors = require('cors'); 6 | var express = require('express'); 7 | 8 | express() 9 | .use(cors()) 10 | .use(express.static(__dirname + '/../static')) 11 | .use(new R.RenderServer(App).middleware) 12 | .listen(config.renderServer.port); 13 | 14 | var path = 'http://' + config.renderServer.hostname + ':' + config.renderServer.port; 15 | console.warn('Render Server listening on', path); 16 | -------------------------------------------------------------------------------- /src/routers/NavigationRouter.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var _ = require('lodash-next'); 3 | 4 | class NavigationRouter extends R.Router { 5 | constructor() { 6 | super(); 7 | 8 | _.each({ 9 | '/': { 10 | title: 'Home', 11 | description: 'Home page', 12 | name: 'home', 13 | }, 14 | '/home': { 15 | title: 'Home', 16 | description: 'Home page', 17 | name: 'home', 18 | }, 19 | '/about': { 20 | title: 'About', 21 | description: 'About page', 22 | name: 'about', 23 | }, 24 | }, (val, route) => { 25 | this.route(route, () => _.extend(val, { props: {} })); 26 | }); 27 | 28 | this.route('/*splat', (splat) => ({ 29 | title: 'Not found', 30 | description: 'Not found', 31 | name: 'notfound', 32 | props: { 33 | splat: splat, 34 | }, 35 | })); 36 | 37 | this.def(() => ({ 38 | title: 'Not found', 39 | description: 'Not found', 40 | name: 'notfound', 41 | })); 42 | } 43 | } 44 | 45 | module.exports = NavigationRouter; 46 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash-next'); 2 | var spawn = require('child_process').spawn; 3 | var watch = require('node-watch'); 4 | 5 | var childs = []; 6 | 7 | ['render-server', 'uplink-server'].forEach((name) => { 8 | function spawnWorker() { 9 | var child = spawn('node', [__dirname + '/' + name + '.js']); 10 | 11 | child.stdout.setEncoding('utf-8'); 12 | child.stdout.on('data', (data) => { 13 | console.log(name, ':', data.slice(0, -1)); 14 | }); 15 | 16 | child.stderr.setEncoding('utf-8'); 17 | child.stderr.on('data', (data) => { 18 | console.warn(name, ':', data.slice(0, -1)); 19 | }); 20 | 21 | child.on('close', (code) => { 22 | console.error(name, ':', 'exited with code', code); 23 | _.defer(spawnWorker); 24 | }); 25 | 26 | childs.push(child); 27 | } 28 | 29 | spawnWorker(); 30 | }); 31 | 32 | watch(__dirname, _.debounce(function restart() { 33 | console.warn('Restarting children processes.'); 34 | childs.forEach((child) => child.kill()); 35 | }, 1000)); 36 | -------------------------------------------------------------------------------- /src/stores/MemoryStore.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | var MemoryStore = R.Store.createMemoryStore(); 4 | 5 | module.exports = MemoryStore; 6 | -------------------------------------------------------------------------------- /src/stores/UplinkStore.js: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | 3 | var UplinkStore = R.Store.createUplinkStore(); 4 | 5 | module.exports = UplinkStore; 6 | -------------------------------------------------------------------------------- /src/styles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | swatch: { 3 | ReactBlue: '#61dafb', 4 | LinkGrey: '#aaa', 5 | LinkHoverGrey: '#fafafa', 6 | TextGrey: '#484848', 7 | TitleGrey: '#e9e9e9', 8 | }, 9 | fonts: { 10 | Helvetica: '"Open Sans Condensed"', // Pretend this is actually Helvetica 11 | Roboto: '"Roboto"', 12 | }, 13 | pageWidth: 980, 14 | }; 15 | -------------------------------------------------------------------------------- /src/uplink-server.js: -------------------------------------------------------------------------------- 1 | var UplinkServer = require('./UplinkServer'); 2 | 3 | var co = require('co'); 4 | var config = require('../config'); 5 | var cors = require('cors'); 6 | var express = require('express'); 7 | 8 | co(function*() { 9 | return yield new UplinkServer().installHandlers( 10 | express() 11 | .use(cors()), 12 | config.uplinkServer.prefix 13 | ); 14 | }).call(null, function(err, server) { 15 | if (err) { 16 | throw err; 17 | } 18 | server.listen(config.uplinkServer.port); 19 | var path = 'http://' + config.uplinkServer.hostname + ':' + config.uplinkServer.port; 20 | console.log('Uplink Server listening on', path); 21 | }); 22 | -------------------------------------------------------------------------------- /static/ReactLogo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tasks/createComponent.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fs = require('fs'); 3 | var _ = require('lodash-next'); 4 | 5 | module.exports = function createComponent(displayName, tagName) { 6 | tagName = tagName || 'div'; 7 | assert(displayName && _.isString(displayName), 'displayName should be a String.'); 8 | assert(/^[A-Z][a-zA-Z0-9]*$/.exec(displayName) !== null, 'displayName should match /^[A-Z][a-zA-Z0-9]*$/.'); 9 | var tpl = _.template(fs.readFileSync(__dirname + '/createComponent.tpl')); 10 | fs.writeFileSync(__dirname + '/../src/components/' + displayName + '.jsx', tpl({ displayName: displayName, tagName: tagName })); 11 | }; 12 | -------------------------------------------------------------------------------- /tasks/createComponent.tpl: -------------------------------------------------------------------------------- 1 | var R = require('react-rails'); 2 | var React = R.React; 3 | var _ = require('lodash'); 4 | var co = require('co'); 5 | var styles = require('../styles'); 6 | 7 | var <%= displayName %> = React.createClass(/** @lends <%= displayName %>.prototype */{ 8 | mixins: [R.Component.Mixin], 9 | 10 | propTypes: { 11 | }, 12 | 13 | statics: { 14 | getStylesheetRules() { 15 | return { 16 | 'components': { 17 | '.<%= displayName %>': { 18 | }, 19 | }, 20 | }; 21 | }, 22 | }, 23 | 24 | getFluxStoreSubscriptions(props) { 25 | return { 26 | }; 27 | }, 28 | 29 | render() { 30 | return (<<%= tagName %> className='<%= displayName %>'> 31 | >); 32 | }, 33 | }); 34 | 35 | module.exports = <%= displayName %>; 36 | --------------------------------------------------------------------------------