├── .editorconfig ├── .eslintignore ├── .flowconfig ├── .gitignore ├── Dockerfile ├── FEATURES.md ├── LICENSE.md ├── README.md ├── actions └── Actions.js ├── client.js ├── components ├── About.js ├── App.js ├── Head.js ├── Home.js └── NotFound.js ├── dispatcher.js ├── hotLoadServer.js ├── package.json ├── preprocessor.js ├── routes.js ├── server.js ├── static └── normalize.css ├── stores └── ExampleStore.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | .DS_Store 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | build 3 | node_modules 4 | 5 | [include] 6 | 7 | [libs] 8 | 9 | [options] 10 | module.system=node 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | .DS_Store 4 | npm-debug.log -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM google/nodejs-runtime 2 | EXPOSE 80 3 | ENV PORT=80 4 | -------------------------------------------------------------------------------- /FEATURES.md: -------------------------------------------------------------------------------- 1 | ### Routing 2 | 3 | - [x] React Router v0.11 4 | - [x] Page Title Updates on both server and client 5 | 6 | ### Isomorphic 7 | 8 | - [x] Server-side rendering 9 | - [x] Server-side routing 10 | 11 | ### ES6 12 | 13 | - [x] babel-loader with minimal runtime dependency footprint 14 | - [x] Server-side `require("babel/register");` ES6 polyfill 15 | 16 | ### Flux & Data Fetching 17 | 18 | - [x] Flux architecture 19 | - [x] Uses `fetch()` polyfill on both server and client 20 | - [ ] Immutable data stuctures using [Immutable.js](https://github.com/facebook/immutable-js) 21 | 22 | ### Development & design workflow 23 | 24 | - [x] "Hot-loaded" javascript builds (live-reload with no refresh, components maintain state) 25 | - [ ] CSS in JS 26 | 27 | ### CSS 28 | 29 | - [x] normalize.css to fix cross-browser inconsistencies 30 | 31 | ### Testing & Linting 32 | 33 | - [x] ESLint configured to support JSX and ES6 34 | - [x] Jest configured to support JSX 35 | - [x] Flow configured for weak mode inferred type checking 36 | - [x] .editorconfig file with sane defaults 37 | - [x] Continuous Integration via CircleCI 38 | - [ ] CI integration with BrowserStack 39 | 40 | ### Deployment & Builds 41 | 42 | - [x] Minimized JS build with dead code elimination (`webpack -p`) 43 | - [ ] Docker container configuration 44 | - [ ] Cache busting 45 | - [ ] Bundle splitting per-route 46 | 47 | ### Internationalization 48 | 49 | - [ ] react-intl 50 | 51 | ### SEO 52 | 53 | - [ ] Open Graph and other domain-specific meta tags via [react-side-effect](https://github.com/gaearon/react-side-effect) 54 | 55 | ### Accessibility 56 | 57 | - [ ] Console warning in dev if elements aren't visible to screen readers, via [react-a11y](https://github.com/rackt/react-a11y). 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Brendan Irvine-Broque 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Circle CI](https://circleci.com/gh/irvinebroque/isomorphic-hot-loader.svg?style=svg)](https://circleci.com/gh/irvinebroque/isomorphic-hot-loader) 2 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/irvinebroque/isomorphic-hot-loader) 3 | 4 | **Goal:** To provide a starter project for React.js projects with sane defaults and community-driven best practices. 5 | 6 | - [Features](/FEATURES.md) 7 | 8 | ## Install 9 | 10 | 1. Install [node.js](https://nodejs.org) (`brew install node`). 11 | 2. Install [Flow](http://flowtype.org/docs/getting-started.html#_) (`brew install flow`) for inferred type checking in development with no extra work. 12 | 3. Clone this repository 13 | 4. `npm install` 14 | 15 | ## Run it locally 16 | 17 | `npm run dev` 18 | 19 | Runs our server with [node-supervisor](https://github.com/isaacs/node-supervisor) to restart when any javascript changes, and a [webpack hot loader](https://github.com/gaearon/react-hot-loader) that pushes the compiled javascript bundle into the client via websockets, for fast incremental live-reload. 20 | 21 | ## CSS/JS Builds 22 | 23 | `npm run build` 24 | 25 | Runs [webpack](http://webpack.github.io) to generate a production build. This minifies the javascript and uses static analysis to perform dead code elimination, which reduces our total bundle considerably, especially when requiring large libraries like moment.js. 26 | 27 | Which will generate the proper webpack bundle. Then run `npm run prod`. 28 | 29 | ## Tests 30 | 31 | `npm run test` 32 | 33 | Runs all [jest](https://facebook.github.io/jest/) unit tests within `__tests__` directories. 34 | 35 | ## Linting & Style Checking 36 | 37 | `npm run lint` 38 | 39 | Runs [eslint](https://github.com/eslint/eslint). The eslint config is stored as `"eslintConfig"` within `package.json`, and is configured to work with both ES6 and JSX. 40 | 41 | 42 | ### License 43 | 44 | [MIT](/LICENSE.md) 45 | -------------------------------------------------------------------------------- /actions/Actions.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var Dispatcher = require('../dispatcher'); 6 | 7 | var Actions = { 8 | 9 | exampleAction: function(thing) { 10 | Dispatcher.dispatch({ 11 | action: 'EXAMPLE_ACTION', 12 | thing: thing 13 | }); 14 | } 15 | 16 | }; 17 | 18 | module.exports = Actions; 19 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var React = require('react'); 6 | var routes = require('./routes'); 7 | var Router = require('react-router'); 8 | var RouteHandler = Router.RouteHandler; 9 | 10 | // Set a device type based on window width, so that we can write media queries in javascript 11 | // by calling if (this.props.deviceType === "mobile") 12 | var deviceType; 13 | 14 | if (window.matchMedia("(max-width: 639px)").matches) { 15 | deviceType = "mobile"; 16 | } else if (window.matchMedia("(max-width: 768px)").matches) { 17 | deviceType = "tablet"; 18 | } else { 19 | deviceType = "desktop"; 20 | } 21 | 22 | Router.run(routes, Router.HistoryLocation, function (Handler, state) { 23 | React.render(, document.body); 24 | }); 25 | -------------------------------------------------------------------------------- /components/About.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var React = require('react'); 6 | var Title = require('react-document-title'); 7 | var Link = require('react-router').Link; 8 | 9 | var About = React.createClass({ 10 | 11 | render: function() { 12 | return ( 13 | 14 | <div> 15 | <h1>About</h1> 16 | <Link to='Home'>Home</Link> 17 | </div> 18 | 19 | ); 20 | } 21 | 22 | }); 23 | 24 | module.exports = About; 25 | -------------------------------------------------------------------------------- /components/App.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var React = require('react'); 6 | var Title = require('react-document-title'); 7 | var RouteHandler = require('react-router').RouteHandler; 8 | // This adds accessibility warnings to the console in development 9 | var a11y = require('react-a11y'); 10 | a11y(); 11 | 12 | var App = React.createClass({ 13 | 14 | render: function() { 15 | return ( 16 | 17 | <RouteHandler/> 18 | 19 | ); 20 | } 21 | 22 | }); 23 | 24 | module.exports = App; 25 | -------------------------------------------------------------------------------- /components/Head.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | // The head component ONLY gets rendered server-side 6 | var React = require('react'); 7 | var PureRenderMixin = require('react/addons').addons.PureRenderMixin; 8 | 9 | var Head = React.createClass({ 10 | 11 | displayName: 'Head', 12 | 13 | mixins: [PureRenderMixin], 14 | 15 | render: function() { 16 | return ( 17 | 18 | 19 | {this.props.title} 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | }); 29 | 30 | module.exports = Head; 31 | -------------------------------------------------------------------------------- /components/Home.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | /*global fetch*/ 4 | 5 | "use strict"; 6 | 7 | var React = require('react'); 8 | var Title = require('react-document-title'); 9 | var Link = require('react-router').Link; 10 | require('isomorphic-fetch'); 11 | 12 | var Home = React.createClass({ 13 | 14 | componentWillMount: function() { 15 | fetch('//offline-news-api.herokuapp.com/stories') 16 | .then(function(response) { 17 | if (response.status >= 400) { 18 | throw new Error("Bad response from server"); 19 | } 20 | return response.json(); 21 | }) 22 | .then(function(stories) { 23 | this.setState({stories: stories}); 24 | }.bind(this)); 25 | }, 26 | 27 | render: function() { 28 | return ( 29 | 30 | <div> 31 | <h1>Home</h1> 32 | <Link to='About Me'>About</Link> 33 | <Link to='/foo'>Foo</Link> 34 | </div> 35 | 36 | ); 37 | } 38 | 39 | }); 40 | 41 | module.exports = Home; 42 | -------------------------------------------------------------------------------- /components/NotFound.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var React = require('react'); 6 | var Title = require('react-document-title'); 7 | var Link = require('react-router').Link; 8 | 9 | var NotFound = React.createClass({ 10 | 11 | render: function() { 12 | return ( 13 | 14 | <div> 15 | <h1>404 Not Found</h1> 16 | <Link to='Home'>Home</Link> 17 | </div> 18 | 19 | ); 20 | } 21 | 22 | }); 23 | 24 | module.exports = NotFound; 25 | -------------------------------------------------------------------------------- /dispatcher.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var Dispatcher = require('flux').Dispatcher; 6 | 7 | var BuildingOsDispatcher = new Dispatcher(); 8 | 9 | module.exports = BuildingOsDispatcher; 10 | -------------------------------------------------------------------------------- /hotLoadServer.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | // This little dev server is basically a wrapped express server that 6 | // 'hot loads' our javascript for super fast live reload in development 7 | var webpack = require('webpack'); 8 | var WebpackDevServer = require('webpack-dev-server'); 9 | var config = require('./webpack.config'); 10 | 11 | var port = process.env.HOT_LOAD_PORT || 8888; 12 | 13 | new WebpackDevServer(webpack(config), { 14 | contentBase: 'http://localhost:' + port, 15 | publicPath: config.output.publicPath, 16 | noInfo: true, 17 | hot: true 18 | }).listen(port, 'localhost', function (err, result) { 19 | if (err) { 20 | console.log(err); 21 | } 22 | 23 | console.log('Hot load server listening at localhost:' + port); 24 | }); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isomorphic-hot-loader", 3 | "version": "0.0.4", 4 | "description": "An actively maintained starter project for building isomorphic web apps with React.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/irvinebroque/isomorphic-hot-loader.git" 8 | }, 9 | "main": "server.js", 10 | "author": "Brendan Irvine-Broque ", 11 | "scripts": { 12 | "dev": "NODE_ENV=development supervisor --harmony --ignore build/ -e js server & NODE_ENV=development node hotLoadServer.js", 13 | "start": "NODE_ENV=production node --harmony server", 14 | "lint": "eslint ./", 15 | "typecheck": "flow", 16 | "build": "NODE_ENV=production webpack -p --progress --colors", 17 | "test": "jest && eslint ./" 18 | }, 19 | "eslintConfig": { 20 | "ecmaFeatures": { 21 | "jsx": true, 22 | "blockBindings": true, 23 | "forOf": true, 24 | "generators": true 25 | }, 26 | "env": { 27 | "browser": true, 28 | "node": true 29 | }, 30 | "rules": { 31 | "strict": [ 32 | 2, 33 | "global" 34 | ], 35 | "no-underscore-dangle": false, 36 | "quotes": false, 37 | "no-unused-vars": false, 38 | "new-cap": false 39 | } 40 | }, 41 | "jest": { 42 | "scriptPreprocessor": "preprocessor.js", 43 | "unmockedModulePathPatterns": [ 44 | "node_modules/react" 45 | ] 46 | }, 47 | "dependencies": { 48 | "babel": "^4.4.2", 49 | "babel-runtime": "^4.4.2", 50 | "body-parser": "^1.12.0", 51 | "chroma-js": "^0.6.3", 52 | "compression": "^1.3.0", 53 | "cors": "^2.5.2", 54 | "express": "^4.3.2", 55 | "flux": "^2.0.1", 56 | "fuzzy": "^0.1.0", 57 | "isomorphic-fetch": "^1.7.0", 58 | "moment": "^2.8.4", 59 | "node-jsx": "^0.12.4", 60 | "object-assign": "^2.0.0", 61 | "react": "^0.12.2", 62 | "react-a11y": "0.0.5", 63 | "react-document-title": "^0.1.3", 64 | "react-router": "^0.12.0", 65 | "ua-parser-js": "^0.7.3", 66 | "when": "^3.6.3" 67 | }, 68 | "devDependencies": { 69 | "babel-core": "^4.3.0", 70 | "babel-loader": "^4.0.0", 71 | "eslint": "^0.14.1", 72 | "jest-cli": "^0.4.0", 73 | "json-loader": "^0.5.1", 74 | "react-hot-loader": "^1.0.4", 75 | "react-tools": "^0.12.2", 76 | "supervisor": "^0.6.0", 77 | "webpack": "^1.5.3", 78 | "webpack-dev-server": "^1.7.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /preprocessor.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var ReactTools = require('react-tools'); 6 | 7 | module.exports = { 8 | process: function(src) { 9 | return ReactTools.transform(src); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var React = require('react'); 6 | var Route = require('react-router').Route; 7 | var NotFoundRoute = require('react-router').NotFoundRoute; 8 | var DefaultRoute = require('react-router').DefaultRoute; 9 | var Redirect = require('react-router').Redirect; 10 | 11 | var Routes = ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | module.exports = Routes; 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | require("babel/register"); 5 | require('node-jsx').install({extension: '.js'}); 6 | var express = require('express'); 7 | var compression = require('compression'); 8 | var cors = require('cors'); 9 | var React = require('react'); 10 | var routes = require('./routes'); 11 | var Head = React.createFactory(require('./components/Head')); 12 | var Router = require('react-router'); 13 | var RouteHandler = Router.RouteHandler; 14 | var ReactDocumentTitle = require('react-document-title'); 15 | var UAParser = require('ua-parser-js'); 16 | var path = require('path'); 17 | 18 | // Setup the express server 19 | var server = express(); 20 | // Gzip all the things 21 | server.use(compression()); 22 | 23 | // Serve a static directory for the webpack-compiled Javascript and CSS. Only in production since the webpack dev server handles this otherwise. 24 | if (process.env.NODE_ENV === "production") {server.use('/build', express.static(path.join(__dirname, '/build')));} 25 | 26 | // Serves up a static directory for images and other assets that we don't (yet) require via Webpack 27 | server.use('/static', express.static(path.join(__dirname, '/static'))); 28 | 29 | // Cross-origin resource sharing 30 | server.use(cors()); 31 | 32 | // Our handler for all incoming requests 33 | server.use(function (req, res, next) { 34 | 35 | // In order to handle "media queries" server-side (preventing FOUT), we parse the user agent string, 36 | // and pass a string down through the router that lets components style and render themselves 37 | // For the correct viewport. Client.js uses window width, which resolves any problems with 38 | // browser sniffing. 39 | var parser = new UAParser(); 40 | var ua = parser.setUA(req.headers['user-agent']).getResult(); 41 | var deviceType = ""; 42 | if (ua.device.type === undefined) { 43 | deviceType = "desktop"; 44 | } else { 45 | deviceType = ua.device.type; 46 | } 47 | 48 | // We customize the onAbort method in order to handle redirects 49 | var router = Router.create({ 50 | routes: routes, 51 | location: req.path, 52 | onAbort: function defaultAbortHandler(abortReason, location) { 53 | if (abortReason && abortReason.to) { 54 | res.redirect(301, abortReason.to); 55 | } else { 56 | res.redirect(404, "404"); 57 | } 58 | } 59 | }); 60 | 61 | var content = ""; 62 | 63 | // Run the router, and render the result to string 64 | router.run(function (Handler, state) { 65 | content = React.renderToString(React.createElement(Handler, {routerState: state, deviceType: deviceType, environment: "server"}), null); 66 | }); 67 | 68 | // Resets the document title on each request 69 | // See https://github.com/gaearon/react-document-title#server-usage 70 | var title = ReactDocumentTitle.rewind(); 71 | 72 | // Render to string 73 | var head = React.renderToStaticMarkup(Head({title: title})); 74 | 75 | // Write the response 76 | res.write(''); 77 | res.write(head); 78 | res.write(''); 79 | res.write(content); 80 | res.write(''); 81 | 82 | // In development, the compiled javascript is served by a WebpackDevServer, which lets us 'hot load' scripts in for live editing. 83 | if (process.env.NODE_ENV === "development") { 84 | var hotLoadPort = process.env.HOT_LOAD_PORT || 8888; 85 | res.write(''); 86 | } 87 | 88 | // In production, we just serve the pre-compiled assets from the /build directory 89 | if (process.env.NODE_ENV === "production") { 90 | res.write(''); 91 | } 92 | 93 | res.write(''); 94 | res.end(); 95 | 96 | }); 97 | 98 | var port = process.env.PORT || 8080; 99 | server.listen(port); 100 | 101 | if (process.env.NODE_ENV === "development") { 102 | console.log('server.js is listening on port ' + port); 103 | } 104 | -------------------------------------------------------------------------------- /static/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | 429 | 430 | 431 | 432 | 433 | /*Because box-sizing anything else is awful.*/ 434 | 435 | * { 436 | box-sizing: border-box; 437 | } 438 | 439 | 440 | 441 | 442 | -------------------------------------------------------------------------------- /stores/ExampleStore.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var Dispatcher = require('../dispatcher'); 6 | var EventEmitter = require('events').EventEmitter; 7 | var assign = require('object-assign'); 8 | var fuzzy = require('fuzzy'); 9 | var CHANGE_EVENT = 'change'; 10 | 11 | // Data structures that this store manipulates 12 | var _things = []; 13 | 14 | function _addThing(thing) { 15 | _things.push(thing); 16 | } 17 | 18 | var ExampleStore = assign({}, EventEmitter.prototype, { 19 | 20 | emitChange: function() { 21 | this.emit(CHANGE_EVENT); 22 | }, 23 | 24 | addChangeListener: function(callback) { 25 | this.on(CHANGE_EVENT, callback); 26 | }, 27 | 28 | removeChangeListener: function(callback) { 29 | this.removeListener(CHANGE_EVENT, callback); 30 | }, 31 | 32 | getThing: function() { 33 | return _things; 34 | } 35 | 36 | }); 37 | 38 | ExampleStore.dispatchToken = Dispatcher.register(function(payload) { 39 | var action = payload.action; 40 | 41 | if (action === 'EXAMPLE_ACTION') { 42 | _addThing(payload.thing); 43 | ExampleStore.emitChange(); 44 | } 45 | 46 | }); 47 | 48 | module.exports = ExampleStore; 49 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* @flow weak */ 2 | 3 | "use strict"; 4 | 5 | var webpack = require('webpack'); 6 | var path = require('path'); 7 | 8 | var port = process.env.HOT_LOAD_PORT || 8888; 9 | 10 | var config = { 11 | cache: true, 12 | resolve: { 13 | extensions: ['', '.js'] 14 | }, 15 | entry: [ 16 | 'webpack-dev-server/client?http://localhost:' + port, 17 | 'webpack/hot/dev-server', 18 | './client.js' 19 | ], 20 | output: { 21 | path: path.join(__dirname, '/build/'), 22 | filename: 'client.js', 23 | publicPath: 'http://localhost:' + port + '/build/' 24 | }, 25 | plugins: [ 26 | new webpack.HotModuleReplacementPlugin() 27 | ], 28 | module: { 29 | loaders: [ 30 | { test: /\.js$/, exclude: /node_modules/, loaders: ['react-hot', 'babel-loader?experimental&optional=selfContained'] } 31 | ] 32 | } 33 | }; 34 | 35 | if (process.env.NODE_ENV === "development") { 36 | config.devtool = 'eval'; // This is not as dirty as it looks. It just generates source maps without being crazy slow. 37 | } 38 | 39 | if (process.env.NODE_ENV === "production") { 40 | config.resolve.alias = {'react-a11y': function() {}}; // Aliases react-a11y to nothing in production 41 | } 42 | 43 | module.exports = config; 44 | --------------------------------------------------------------------------------