├── .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 | [](https://circleci.com/gh/irvinebroque/isomorphic-hot-loader)
2 | [](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 |
15 |
About
16 | Home
17 |
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 |
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 |
31 |
Home
32 | About
33 | Foo
34 |
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 |
15 |
404 Not Found
16 | Home
17 |
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 |
--------------------------------------------------------------------------------