Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…rsal-hot-example into mocha-play
  • Loading branch information
james-lee1989 committed Aug 27, 2015
2 parents 640dd4a + 278b587 commit 24d8182
Show file tree
Hide file tree
Showing 52 changed files with 1,026 additions and 660 deletions.
5 changes: 4 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"block-scoped-var": 0,
// Temporarily disabled for test/* until babel/babel-eslint#33 is resolved
"padded-blocks": 0,
"comma-dangle": 0, // not sure why airbnb turned this on. gross!
"indent": [2, 2, {"SwitchCase": 1}],
"no-console": 0
},
"plugins": [
Expand All @@ -23,6 +25,7 @@
"__CLIENT__": true,
"__SERVER__": true,
"__DISABLE_SSR__": true,
"__DEVTOOLS__": true
"__DEVTOOLS__": true,
"webpackIsomorphicTools": true
}
}
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[![Demo on Heroku](https://img.shields.io/badge/demo-heroku-lightgrey.png)](https://react-redux.herokuapp.com)
[![Dependency Status](https://david-dm.org/erikras/react-redux-universal-hot-example.svg)](https://david-dm.org/erikras/react-redux-universal-hot-example)
[![devDependency Status](https://david-dm.org/erikras/react-redux-universal-hot-example/dev-status.svg)](https://david-dm.org/erikras/react-redux-universal-hot-example#info=devDependencies)
[![PayPal donate button](http://img.shields.io/paypal/donate.png?color=yellowgreen)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=E2LK57ZQ9YRMN)

This is a starter boiler plate app I've put together using the following technologies:

Expand All @@ -22,6 +23,7 @@ This is a starter boiler plate app I've put together using the following technol
* [redux-form](https://github.com/erikras/redux-form) to manage form state in Redux
* [lru-memoize](https://github.com/erikras/lru-memoize) to speed up form validation
* [style-loader](https://github.com/webpack/style-loader) and [sass-loader](https://github.com/jtangelder/sass-loader) to allow import of stylesheets
* [react-document-meta](https://github.com/kodyl/react-document-meta) to manage title and meta tag information on both server and client
* [webpack-isomorphic-tools](https://github.com/halt-hammerzeit/webpack-isomorphic-tools) to allow require() work for statics both on client and server
* [mocha](https://mochajs.org/) to allow writing unit tests for the project.

Expand All @@ -48,7 +50,7 @@ npm run start

## Demo

A demonstration of this app can be see [running on heroku](https://react-redux.herokuapp.com), which is a deployment of the [heroku branch](https://github.com/erikras/react-redux-universal-hot-example/tree/heroku).
A demonstration of this app can be seen [running on heroku](https://react-redux.herokuapp.com), which is a deployment of the [heroku branch](https://github.com/erikras/react-redux-universal-hot-example/tree/heroku).

## Explanation

Expand Down Expand Up @@ -79,6 +81,10 @@ The middleware, [`clientMiddleware.js`](https://github.com/erikras/react-redux-u
1. To allow the action creators access to the client API facade. Remember this is the same on both the client and the server, and cannot simply be `import`ed because it holds the cookie needed to maintain session on server-to-server requests.
2. To allow some actions to pass a "promise generator", a function that takes the API client and returns a promise. Such actions require three action types, the `REQUEST` action that initiates the data loading, and a `SUCCESS` and `FAILURE` action that will be fired depending on the result of the promise. There are other ways to accomplish this, some discussed [here](https://github.com/gaearon/redux/issues/99), which you may prefer, but to the author of this example, the middleware way feels cleanest.

#### What the Duck?

[Ducks](https://github.com/erikras/react-redux-universal-hot-example/blob/master/docs/Ducks.md) are a Redux Style Proposal that I came up with to better isolate concerns within a Redux application. I encourage you to read the [Ducks Docs](https://github.com/erikras/react-redux-universal-hot-example/blob/master/docs/Ducks.md) and provide feedback.

#### API Server

This is where the meat of your server-side application goes. It doesn't have to be implemented in Node or Express at all. This is where you connect to your database and provide authentication and session management. In this example, it's just spitting out some json with the current time stamp.
Expand Down
18 changes: 9 additions & 9 deletions bin/server.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node
require('../compiler'); // enables ES6 support

var path = require('path');
var rootDir = path.resolve(__dirname, '..');
/**
* Define isomorphic constants.
*/
Expand All @@ -18,12 +19,11 @@ if (__DEVELOPMENT__) {
}
}

// alternatively, if you you can skip using this and instead use this:
// (and webpack DefinePlugin for setting _client_ environment variable)
// const picture = _client_ ? require('./image.png') : webpackIsomorphicTools.require('./image.png')
var webpackConfiguration = require('../webpack/prod.config.js');
// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicTools = require('webpack-isomorphic-tools');
global.webpackIsomorphicTools = new WebpackIsomorphicTools(webpackConfiguration, require('../webpack/webpack-isomorphic-tools'));
global.webpackIsomorphicTools.register();

require('../src/server');
global.webpackIsomorphicTools = new WebpackIsomorphicTools(require('../webpack/webpack-isomorphic-tools'))
.development(__DEVELOPMENT__)
.server(rootDir, function()
{
require('../src/server');
});
94 changes: 94 additions & 0 deletions docs/Ducks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Ducks: Redux Reducer Bundles

<img src="duck.jpg" align="right"/>

I find as I am building my redux app, one piece of functionality at a time, I keep needing to add `{actionTypes, actions, reducer}` tuples for each use case. I have been keeping these in separate files and even separate folders, however 95% of the time, it's only one reducer/actions pair that ever needs their associated actions.

To me, it makes more sense for these components to be bundled together in an isolated module that is self contained, and can even be packaged easily into a library.

## The Proposal

### Example

```javascript
// widgets.js

const LOAD = 'my-app/widgets/LOAD';
const CREATE = 'my-app/widgets/CREATE';
const UPDATE = 'my-app/widgets/UPDATE';
const REMOVE = 'my-app/widgets/REMOVE';

export default function reducer(state = {}, action = {}) {
switch (action.type) {
// do reducer stuff
default: return state;
}
}

export function loadWidgets() = {
return { type: LOAD };
}

export function createWidget(widget) = {
return { type: CREATE, widget };
}

export function updateWidget(widget) = {
return { type: UPDATE, widget };
}

export function removeWidget(widget) = {
return { type: REMOVE, widget };
}
```
### Rules

A module...

1. MUST `export default` a function called `reducer()`
2. MUST `export` its action creators as functions
3. MUST have action types in the form `npm-module-or-app/reducer/ACTION_TYPE`
3. MAY export its action types as `UPPER_SNAKE_CASE`, if an external reducer needs to listen for them, or if it is a published reusable library

These same guidelines are recommended for `{actionType, action, reducer}` bundles that are shared as reusable Redux libraries.

### Name

Java has jars and beans. Ruby has gems. I suggest we call these reducer bundles "ducks", as in the last syllable of "redux".

### Usage

You can still do:

```javascript
import { combineReducers } from 'redux';
import * as reducers from './ducks/index';

const rootReducer = combineReducers(reducers);
export default rootReducer;
```

You can still do:

```javascript
import * as widgetActions from './ducks/widgets';
```
...and it will only import the action creators, ready to be passed to `bindActionCreators()`.

There will be some times when you want to `export` something other than an action creator. That's okay, too. The rules don't say that you can *only* `export` action creators. When that happens, you'll just have to enumerate the action creators that you want. Not a big deal.

```javascript
import {create, update, remove, increment} as widgetActions from './ducks/widgets';
// ...
bindActionCreators({create, update, remove, increment}, dispatch);
```

### Implementation

The migration to this code structure was [painless](https://github.com/erikras/react-redux-universal-hot-example/commit/3fdf194683abb7c40f3cb7969fd1f8aa6a4f9c57), and I foresee it reducing much future development misery.

Please submit any feedback via an issue or a tweet to [@erikras](https://twitter.com/erikras). It will be much appreciated.

Happy coding!

-- Erik Rasmussen
Binary file added docs/duck.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 28 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Example of an isomorphic (universal) webapp using react redux and hot reloading",
"author": "Erik Rasmussen <[email protected]> (http://github.com/erikras)",
"license": "MIT",
"version": "0.0.3",
"version": "0.0.4",
"repository": {
"type": "git",
"url": "https://github.com/erikras/react-redux-universal-hot-example"
Expand Down Expand Up @@ -57,64 +57,67 @@
}
},
"dependencies": {
"babel": "5.4.7",
"babel-plugin-typecheck": "0.0.3",
"babel": "5.8.21",
"babel-plugin-typecheck": "1.2.0",
"body-parser": "^1.13.2",
"compression": "^1.5.0",
"express": "^4.13.0",
"express-session": "^1.11.3",
"file-loader": "^0.8.4",
"http-proxy": "^1.11.1",
"lru-memoize": "0.0.1",
"piping": "0.1.8",
"lru-memoize": "0.0.2",
"map-props": "^0.1.1",
"piping": "0.2.0",
"pretty-error": "^1.1.2",
"query-string": "^2.4.0",
"react": "0.13.3",
"react-inline-css": "1.1.1",
"react-redux": "0.8.0",
"react-router": "v1.0.0-beta2",
"redux": "1.0.0-rc",
"redux-form": "^0.1.2",
"react-document-meta": "^0.1.4",
"react-inline-css": "1.2.1",
"react-redux": "0.9.0",
"react-router": "v1.0.0-beta3",
"redux": "^1.0.1",
"redux-form": "^0.5.0",
"serialize-javascript": "^1.0.0",
"serve-favicon": "^2.3.0",
"serve-static": "^1.10.0",
"superagent": "^1.2.0",
"url-loader": "^0.5.6",
"webpack-isomorphic-tools": "^0.3.3"
"webpack-isomorphic-tools": "^0.8.1"
},
"devDependencies": {
"redux-devtools": "1.0.2",
"autoprefixer-loader": "^2.0.0",
"babel-core": "5.4.7",
"babel-eslint": "^3.1.18",
"babel-loader": "5.1.3",
"babel-runtime": "5.4.7",
"babel-core": "^5.8.22",
"babel-eslint": "^4.0.10",
"babel-loader": "5.3.2",
"babel-runtime": "5.8.20",
"better-npm-run": "0.0.1",
"chai": "^3.2.0",
"clean-webpack-plugin": "^0.1.3",
"concurrently": "0.1.1",
"css-loader": "^0.15.1",
"eslint": "^0.23.0",
"eslint-config-airbnb": "0.0.6",
"eslint-plugin-react": "^2.5.2",
"css-loader": "^0.16.0",
"eslint": "^1.2.0",
"eslint-config-airbnb": "0.0.7",
"eslint-plugin-react": "^3.2.3",
"extract-text-webpack-plugin": "^0.8.1",
"json-loader": "0.5.2",
"karma": "^0.13.3",
"karma-chrome-launcher": "^0.2.0",
"karma-cli": "0.0.4",
"karma-cli": "0.1.0",
"karma-firefox-launcher": "^0.1.4",
"karma-mocha": "^0.1.10",
"karma-mocha": "^0.2.0",
"karma-mocha-reporter": "^1.1.0",
"karma-sourcemap-loader": "^0.3.5",
"karma-webpack": "^1.7.0",
"mocha": "^2.2.5",
"node-sass": "^3.2.0",
"react-a11y": "0.1.1",
"react-hot-loader": "1.2.7",
"react-a11y": "0.2.6",
"react-hot-loader": "1.2.8",
"redux-devtools": "1.0.2",
"sass-loader": "^2.0.0",
"strip-loader": "^0.1.0",
"style-loader": "^0.12.3",
"webpack": "^1.9.11",
"webpack-dev-server": "1.9.0"
"webpack-dev-server": "1.10.1"
},
"engines": {
"node": ">=0.10.32"
Expand Down
4 changes: 2 additions & 2 deletions src/ApiClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ApiClient_ {
forEach((method) => {
this[method] = (path, options) => {
return new Promise((resolve, reject) => {
let request = superagent[method](this.formatUrl(path));
const request = superagent[method](this.formatUrl(path));
if (options && options.params) {
request.query(options.params);
}
Expand All @@ -39,7 +39,7 @@ class ApiClient_ {

/* This was originally a standalone function outside of this class, but babel kept breaking, and this fixes it */
formatUrl(path) {
let adjustedPath = path[0] !== '/' ? '/' + path : path;
const adjustedPath = path[0] !== '/' ? '/' + path : path;
if (__SERVER__) {
// Prepend host and port of the API server to the path.
return 'http://localhost:' + config.apiPort + adjustedPath;
Expand Down
19 changes: 2 additions & 17 deletions src/Html.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {Component, PropTypes} from 'react';
import serialize from 'serialize-javascript';
import DocumentMeta from 'react-document-meta';
const cdn = '//cdnjs.cloudflare.com/ajax/libs/';

/**
Expand All @@ -20,27 +21,11 @@ export default class Html extends Component {

render() {
const {assets, component, store} = this.props;
const title = 'React Redux Example';
const description = 'All the modern best practices in one example.';
const image = 'https://react-redux.herokuapp.com/logo.jpg';
return (
<html lang="en-us">
<head>
<meta charSet="utf-8"/>
<title>{title}</title>
<meta property="og:site_name" content={title}/>
<meta property="og:image" content={image}/>
<meta property="og:locale" content="en_US"/>
<meta property="og:title" content={title}/>
<meta property="og:description" content={description}/>
<meta name="twitter:card" content="summary"/>
<meta property="twitter:site" content="@erikras"/>
<meta property="twitter:creator" content="@erikras"/>
<meta property="twitter:image" content={image}/>
<meta property="twitter:image:width" content="200"/>
<meta property="twitter:image:height" content="200"/>
<meta property="twitter:title" content={title}/>
<meta property="twitter:description" content={description}/>
{DocumentMeta.rewind({asReact: true})}

<link rel="shortcut icon" href="/favicon.ico" />
<link href={cdn + 'twitter-bootstrap/3.3.5/css/bootstrap.css'}
Expand Down
16 changes: 0 additions & 16 deletions src/actions/actionTypes.js

This file was deleted.

36 changes: 0 additions & 36 deletions src/actions/authActions.js

This file was deleted.

9 changes: 0 additions & 9 deletions src/actions/counterActions.js

This file was deleted.

Loading

0 comments on commit 24d8182

Please sign in to comment.