├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── devServer.js ├── index.html ├── package.json ├── src ├── actions │ └── CounterActions.js ├── components │ ├── Counter.js │ └── Footer.js ├── constants │ └── ActionTypes.js ├── containers │ ├── App.js │ ├── DevTools.js │ ├── Root.dev.js │ ├── Root.js │ └── Root.prod.js ├── index.js ├── reducers │ ├── counter.js │ └── index.js ├── store │ ├── configureStore.dev.js │ ├── configureStore.js │ └── configureStore.prod.js └── styles │ └── main.scss ├── webpack.config.dev.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | "env": { 4 | "development": { 5 | "presets": ["react-hmre"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | webpack.config.dev.js 3 | webpack.config.prod.js 4 | devServer.js 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "comma-dangle": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | lib 5 | coverage 6 | dist 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Redux Boilerplate 2 | 3 | A simple, yet comprehensive React + Redux application, complete with DevTools. 4 | 5 | Author: [@tsaiDavid](https://github.com/tsaiDavid/) | [www.davidtsai.codes](https://www.davidtsai.codes) 6 | 7 | *Let's make this a community effort! Many thanks for all the great suggestions and help that's already underway!* 8 | 9 | *** 10 | 11 | ### Overview 12 | 13 | *Available Branches:* 14 | 15 | *Checkout a branch to start with exactly what you need, explore others to learn how additional features might be implemented!* 16 | 17 | | Branch | Description | Status | 18 | |----------------------|-----------------------------------------|-------------------------| 19 | | `master` | Basic React, Redux, with DevTools. | Complete, with SCSS now | 20 | | `react-router-redux` | Adds 'react-router' and Redux bindings! | In Progress! | 21 | 22 | This is the perfect way to start any React + Redux application - **especially if you're looking for a middle-ground**. Minimize bulk and overhead - and get the exact amount of tools and configuration necessary to hit the ground running! 23 | 24 | With educational comments and documentation sprinkled throughout this app, you'll learn and see how all the pieces come together - from Webpack and Babel all the way through React with Redux. 25 | 26 | Designed to keep style/structure as un-opinionated as possible, to offer you - the user - as much creativity and flexibility when it comes to your needs. As this is stil a **work-in-progress**, do reach out if you have suggestions, fixes, etc! If you want to help, a basic Roadmap can be found below! 27 | 28 | This project features a super simple UI - just for you to see how everything is wired up, using the classic counter example: 29 | 30 | ![](https://fat.gfycat.com/WarlikeFrightenedGraywolf.gif) 31 | 32 | If you found this helpful, please star/fork/follow me on **[GitHub](https://github.com/tsaiDavid/)** and follow me on **[Twitter](https://twitter.com/tftsai)**! 33 | 34 | ### Features 35 | 36 | ##### Basic: 37 | - React + Redux 38 | - Babel 6 w/ basic presets and transform 39 | - Webpack w/ basic dev and prod configurations 40 | - Express development server; easily roll out a production enabled server of your own 41 | - Eslint w/ basic configs 42 | - Redux DevTools + Logger middleware - easily removable/replaceable based on your needs 43 | 44 | ##### Optional: 45 | - *React Router + bindings (checkout `react-router-redux` branch for more info)* 46 | 47 | *** 48 | 49 | ### Requirements 50 | - `node 5.0.0` and higher! 51 | - [*you can use a version manager like `n`*](https://github.com/tj/n) 52 | 53 | *** 54 | 55 | ### FAQ 56 | 57 | - Why another React and Redux boilerplate? 58 | > There are tons of great boilerplates out there, some of them with some pretty advanced functionality! But they aren't good for learning the holistic approach of getting a React/Redux app up and running. I wanted to create a boilerplate that would encourage and help the user learn how everything is set up, from Babel and Webpack through conditional requires and giving them the DevTools they need! 59 | 60 | - Why not use WebpackDevServer? 61 | > The included `devServer.js` is a Node/Express server - mainly because most people will end up creating applications that rely on a Node server! Using the `webpack-dev-middleware` and `webpack-hot-middleware` allow us to get syntax errors displayed in an overlay, which using WebpackDevServer doesn't allow for. 62 | 63 | >![](https://cloud.githubusercontent.com/assets/1539088/11611771/ae1a6bd8-9bac-11e5-9206-42447e0fe064.gif) 64 | 65 | - How can I get this thing into production? 66 | > I'm currently working on including a guide or walkthrough, but at the moment all you have is a `devServer.js`, your first step would probably include creating a separate `server.js` file and going from there - just be sure to see how the application relies on the NODE_ENV variables to select between "dev" and "prod" files! 67 | 68 | - What is this missing? 69 | > At the moment, I have not enabled the loading of SASS, but I do plan on it. As your apps grow in size, you might want to consider creating a `utils` directory. Lastly, be sure to follow @gaeron's tips on reducer composition! 70 | 71 | ### Usage 72 | 73 | ##### Getting Started: 74 | 75 | To begin, fork this repo and then clone those contents down! 76 | 77 | Ideally, fork this boilerplate, then clone. 78 | ``` 79 | $ git clone https://github.com/YOUR_GITHUB_USERNAME_HERE/simple-redux-boilerplate.git 80 | ``` 81 | 82 | Install required dependencies. 83 | (*Did you make sure you have the right version of Node?*) 84 | ``` 85 | npm install 86 | ``` 87 | 88 | Run development server, complete with DevTools and related configuration. 89 | ``` 90 | npm run dev 91 | ``` 92 | 93 | You're now ready to get working! *(enter command or visit via browser directly)*. 94 | ``` 95 | open http://localhost:3000/ 96 | ``` 97 | 98 | If you wish not to free your system *3000* port. Then just pass the port of your wish/available. 99 | ``` 100 | npm start --port=3003 101 | ``` 102 | ``` 103 | open http://localhost:3003/ 104 | ``` 105 | 106 | *** 107 | 108 | ##### Next Steps & Other Notes: 109 | 110 | Now that your development server is up and running, you will see that you have your Redux DevTools available for you to use. The keyboard shortcuts available follow the generally accepted config - but you're free to make changes to them here: `containers/DevTools.js`. 111 | 112 | ***To toggle the DevTool panel during development:*** 113 | CTRL + H 114 | 115 | ***Change the DevTool panel's position during development:*** 116 | CTRL + Q 117 | 118 | *** 119 | 120 | ### Roadmap 121 | 122 | - [x] Base boilerplate design off of "react-transform-boilerplate" 123 | - [x] Begin work on a complementary Yeoman generated package 124 | - [x] Implement Redux 125 | - [x] Implement Redux DevTools 126 | - [x] Optional Redux Logger Middleware is included (pop open console to see logging) 127 | - [x] Conditional require statements of `configureStore.js` and `Root.js` - based on whether user is in development or production environments 128 | - [ ] Clean up Redux actions, reducers, constants 129 | - [ ] Add basic styles and enable webpack compilation of CSS/SASS 130 | - [ ] Provide additional documentation and example of pushing to production 131 | 132 | *** 133 | 134 | ### Style Guide 135 | 136 | Code style can be a tricky subject - I've instead decided to rely on the ever trustworthy configurations that AirBnb follows! 137 | 138 | This project relies on `eslint-config-airbnb`. 139 | Learn more here: [AirBnb Style Guide](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb) 140 | 141 | *** 142 | 143 | ### Credits 144 | 145 | ##### Other Contributors: 146 | - [@yilenpan](https://github.com/yilenpan) | *working on react-router-redux!* 147 | 148 | >This boilerplate is initially based on [@gaeron's](https://github.com/gaeron) awesome [react-transform-boilerplate](https://github.com/gaearon/react-transform-boilerplate). 149 | 150 | This project supports [Babel 6](https://github.com/babel/babel), with reference implementations of: 151 | 152 | **[babel-plugin-react-transform](https://github.com/gaearon/babel-plugin-react-transform)**. It can be used as a boilerplate for quickly getting a new project up and running with a few useful transforms: 153 | 154 | * [**react-transform-hmr**](https://github.com/gaearon/react-transform-hmr) - enables hot reloading react components 155 | * [**react-transform-catch-errors**](https://github.com/gaearon/react-transform-catch-errors) - catches errors inside `render()` 156 | 157 | For convenience they are packed in a single preset called [**react-transform-hmre**](https://github.com/danmartinez101/babel-preset-react-hmre) but you can make your own. 158 | 159 | Syntax errors are displayed in an overlay using **[@glenjamin](https://github.com/glenjamin)**’s **[webpack-hot-middleware](https://github.com/glenjamin/webpack-hot-middleware)**, which replaces Webpack Dev Server. This project **[does not](https://medium.com/@dan_abramov/the-death-of-react-hot-loader-765fa791d7c4)** use React Hot Loader. 160 | -------------------------------------------------------------------------------- /devServer.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const webpack = require('webpack'); 4 | const config = require('./webpack.config.dev'); 5 | 6 | const app = express(); 7 | const compiler = webpack(config); 8 | 9 | const host = 'http://localhost'; 10 | const port = process.env.npm_config_port ? process.env.npm_config_port : 3000; 11 | 12 | app.use(require('webpack-dev-middleware')(compiler, { 13 | noInfo: true, 14 | publicPath: config.output.publicPath 15 | })); 16 | 17 | app.use(require('webpack-hot-middleware')(compiler)); 18 | 19 | app.get('*', (req, res) => { 20 | res.sendFile(path.join(__dirname, 'index.html')); 21 | }); 22 | 23 | app.listen(port, 'localhost', (err) => { 24 | if (err) { 25 | console.log(err); 26 | return; 27 | } 28 | console.info('==> Listening on port %s. Open up %s:%s/ in your browser.', port, host, port); 29 | }); 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= htmlWebpackPlugin.options.title %> 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-simple-redux", 3 | "version": "1.0.0", 4 | "description": "My simple redux application.", 5 | "scripts": { 6 | "prestart": "npm install", 7 | "clean": "rimraf dist", 8 | "build:webpack": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js --progress --profile --colors", 9 | "build": "npm run clean && npm run build:webpack --progress --profile --colors", 10 | "dev": "NODE_ENV=development npm start", 11 | "start": "node devServer.js", 12 | "lint": "eslint src" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/tsaiDavid/simple-redux-boilerplate.git" 17 | }, 18 | "license": "MIT", 19 | "homepage": "https://github.com/tsaiDavid/simple-redux-boilerplate", 20 | "devDependencies": { 21 | "autoprefixer": "^6.3.1", 22 | "babel-core": "^6.3.15", 23 | "babel-eslint": "^5.0.0-beta4", 24 | "babel-loader": "^6.2.0", 25 | "babel-preset-es2015": "^6.3.13", 26 | "babel-preset-react": "^6.3.13", 27 | "babel-preset-react-hmre": "^1.0.0", 28 | "cross-env": "^1.0.6", 29 | "css-loader": "^0.23.1", 30 | "eslint": "^1.10.3", 31 | "eslint-config-airbnb": "^4.0.0", 32 | "eslint-plugin-babel": "^3.0.0", 33 | "eslint-plugin-react": "^3.16.1", 34 | "eventsource-polyfill": "^0.9.6", 35 | "express": "^4.13.3", 36 | "html-webpack-plugin": "^2.24.1", 37 | "node-sass": "^3.4.2", 38 | "redux-devtools": "^3.0.1", 39 | "redux-devtools-dock-monitor": "^1.0.1", 40 | "redux-devtools-log-monitor": "^1.0.2", 41 | "redux-logger": "^2.4.0", 42 | "rimraf": "^2.4.3", 43 | "sass-loader": "^3.1.2", 44 | "style-loader": "^0.13.0", 45 | "webpack": "^1.12.12", 46 | "webpack-dev-middleware": "^1.4.0", 47 | "webpack-hot-middleware": "^2.6.0" 48 | }, 49 | "dependencies": { 50 | "react": "^0.14.3", 51 | "react-dom": "^0.14.3", 52 | "react-redux": "^4.1.1", 53 | "redux-thunk": "^1.0.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/actions/CounterActions.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; 2 | 3 | export function increment() { 4 | return { 5 | type: INCREMENT_COUNTER 6 | }; 7 | } 8 | 9 | export function decrement() { 10 | return { 11 | type: DECREMENT_COUNTER 12 | }; 13 | } 14 | 15 | export function incrementIfOdd() { 16 | return (dispatch, getState) => { 17 | const { counter } = getState(); 18 | 19 | if (counter % 2 === 0) { 20 | return; 21 | } 22 | 23 | dispatch(increment()); 24 | }; 25 | } 26 | 27 | export function incrementAsync() { 28 | return dispatch => { 29 | setTimeout(() => { 30 | dispatch(increment()); 31 | }, 1000); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | export default class Counter extends Component { 4 | constructor(props, context) { 5 | super(props, context); 6 | } 7 | 8 | handleIncrement() { 9 | this.props.actions.increment(); 10 | } 11 | 12 | handleDecrement() { 13 | this.props.actions.decrement(); 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 |
{this.props.counter}
20 | {/* Below, the even or odd statement is simply used to demonstrate how one could 21 | easily use a ternary operator to conditionally show an 'even' or 'odd' string 22 | based on the counter's value on state. */} 23 |
{this.props.counter % 2 === 0 ? 'even' : 'odd'}
24 |
25 |
26 | 27 | 28 |
29 |
30 | ); 31 | } 32 | } 33 | 34 | Counter.propTypes = { 35 | counter: PropTypes.number.isRequired, 36 | actions: PropTypes.object.isRequired 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Footer extends Component { 4 | render() { 5 | return ( 6 | 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Constants are important - they describe what type of action is performed 3 | * within your app. Combined with the DevTools/logger, you can see how state and subsequently 4 | * your UI is being affected. 5 | */ 6 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 7 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; 8 | -------------------------------------------------------------------------------- /src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import * as CounterActions from '../actions/CounterActions'; 5 | import Counter from '../components/Counter'; 6 | import Footer from '../components/Footer'; 7 | 8 | /** 9 | * It is common practice to have a 'Root' container/component require our main App (this one). 10 | * Again, this is because it serves to wrap the rest of our application with the Provider 11 | * component to make the Redux store available to the rest of the app. 12 | */ 13 | class App extends Component { 14 | render() { 15 | // we can use ES6's object destructuring to effectively 'unpack' our props 16 | const { counter, actions } = this.props; 17 | return ( 18 |
19 |
Simple Redux Boilerplate
20 | {/* notice that we then pass those unpacked props into the Counter component */} 21 | 22 |
24 | ); 25 | } 26 | } 27 | 28 | App.propTypes = { 29 | counter: PropTypes.number.isRequired, 30 | actions: PropTypes.object.isRequired 31 | }; 32 | 33 | /** 34 | * Keep in mind that 'state' isn't the state of local object, but your single 35 | * state in this Redux application. 'counter' is a property within our store/state 36 | * object. By mapping it to props, we can pass it to the child component Counter. 37 | */ 38 | function mapStateToProps(state) { 39 | return { 40 | counter: state.counter 41 | }; 42 | } 43 | 44 | /** 45 | * Turns an object whose values are 'action creators' into an object with the same 46 | * keys but with every action creator wrapped into a 'dispatch' call that we can invoke 47 | * directly later on. Here we imported the actions specified in 'CounterActions.js' and 48 | * used the bindActionCreators function Redux provides us. 49 | * 50 | * More info: http://redux.js.org/docs/api/bindActionCreators.html 51 | */ 52 | function mapDispatchToProps(dispatch) { 53 | return { 54 | actions: bindActionCreators(CounterActions, dispatch) 55 | }; 56 | } 57 | 58 | /** 59 | * 'connect' is provided to us by the bindings offered by 'react-redux'. It simply 60 | * connects a React component to a Redux store. It never modifies the component class 61 | * that is passed into it, it actually returns a new connected componet class for use. 62 | * 63 | * More info: https://github.com/rackt/react-redux 64 | */ 65 | 66 | export default connect( 67 | mapStateToProps, 68 | mapDispatchToProps 69 | )(App); 70 | -------------------------------------------------------------------------------- /src/containers/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | 4 | /** 5 | * These 2 monitors are very commonly used with 'redux-devtools'. 6 | * However, you can choose to make your own! 7 | */ 8 | import LogMonitor from 'redux-devtools-log-monitor'; 9 | import DockMonitor from 'redux-devtools-dock-monitor'; 10 | 11 | const DevTools = createDevTools( 12 | /** 13 | * Monitors are individually adjustable via their props. 14 | * Consult their respective repos for further information. 15 | * Here, we are placing the LogMonitor within the DockMonitor. 16 | */ 17 | 19 | 20 | 21 | ); 22 | 23 | export default DevTools; 24 | 25 | /** 26 | * For further information, please see: 27 | * https://github.com/gaearon/redux-devtools 28 | */ 29 | -------------------------------------------------------------------------------- /src/containers/Root.dev.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import App from './App'; 4 | import DevTools from './DevTools'; 5 | 6 | /** 7 | * Component is exported for conditional usage in Root.js 8 | */ 9 | module.exports = class Root extends Component { 10 | render() { 11 | const { store } = this.props; 12 | return ( 13 | /** 14 | * Provider is a component provided to us by the 'react-redux' bindings that 15 | * wraps our app - thus making the Redux store/state available to our 'connect()' 16 | * calls in component hierarchy below. 17 | */ 18 | 19 |
20 | 21 | {/* Being the dev version of our Root component, we include DevTools below */} 22 | 23 |
24 |
25 | ); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/containers/Root.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Just like our store, we configure a 'Root' component that is 3 | * required based on the env variable. This component is typically one 4 | * surrounded by a . 5 | */ 6 | 7 | let loadedModule = null; 8 | 9 | if (process.env.NODE_ENV === 'production') { 10 | loadedModule = require('./Root.prod.js'); 11 | } else { 12 | loadedModule = require('./Root.dev.js'); 13 | } 14 | 15 | export const Root = loadedModule; 16 | -------------------------------------------------------------------------------- /src/containers/Root.prod.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import App from './App'; 4 | 5 | /** 6 | * Component is exported for conditional usage in Root.js 7 | */ 8 | module.exports = class Root extends Component { 9 | render() { 10 | const { store } = this.props; 11 | return ( 12 | /** 13 | * Provider is a component provided to us by the 'react-redux' bindings that 14 | * wraps our app - thus making the Redux store/state available to our 'connect()' 15 | * calls in component hierarchy below. 16 | */ 17 | 18 | 19 | 20 | ); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | /** 4 | * Import the stylesheet you want used! Here we just reference 5 | * the main SCSS file we have in the styles directory. 6 | */ 7 | import './styles/main.scss'; 8 | 9 | /** 10 | * Both configureStore and Root are required conditionally. 11 | * See configureStore.js and Root.js for more details. 12 | */ 13 | import { configureStore } from './store/configureStore'; 14 | import { Root } from './containers/Root'; 15 | 16 | const store = configureStore(); 17 | 18 | ReactDOM.render( 19 | , 20 | document.getElementById('root') 21 | ); 22 | -------------------------------------------------------------------------------- /src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; 2 | 3 | export default function counter(state = 0, action) { 4 | switch (action.type) { 5 | case INCREMENT_COUNTER: 6 | return state + 1; 7 | case DECREMENT_COUNTER: 8 | return state - 1; 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import counter from './counter'; 3 | 4 | /** 5 | * combineReducers is important to understand. As your app might grow in size 6 | * and complexity, you will likely begin to split your reducers into separate 7 | * functions - with each one managing a separate slice of the state! This helper 8 | * function from 'redux' simply merges the reducers. Keep in mind we are using 9 | * the ES6 shorthand for property notation. 10 | * 11 | * If you're transitioning from Flux, you will notice we only use one store, but 12 | * instead of relying on multiple stores to manage diff parts of the state, we use 13 | * various reducers and combine them. 14 | * 15 | * More info: http://rackt.org/redux/docs/api/combineReducers.html 16 | */ 17 | const rootReducer = combineReducers({ 18 | counter, // you might be used to: counter: counter, 19 | }); 20 | 21 | export default rootReducer; 22 | -------------------------------------------------------------------------------- /src/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import rootReducer from '../reducers'; 3 | import createLogger from 'redux-logger'; 4 | import thunk from 'redux-thunk'; 5 | import DevTools from '../containers/DevTools'; 6 | 7 | /** 8 | * Entirely optional, this tiny library adds some functionality to 9 | * your DevTools, by logging actions/state to your console. Used in 10 | * conjunction with your standard DevTools monitor gives you great 11 | * flexibility! 12 | */ 13 | const logger = createLogger(); 14 | 15 | const finalCreateStore = compose( 16 | // Middleware you want to use in development: 17 | applyMiddleware(logger, thunk), 18 | // Required! Enable Redux DevTools with the monitors you chose 19 | DevTools.instrument() 20 | )(createStore); 21 | 22 | module.exports = function configureStore(initialState) { 23 | const store = finalCreateStore(rootReducer, initialState); 24 | 25 | // Hot reload reducers (requires Webpack or Browserify HMR to be enabled) 26 | if (module.hot) { 27 | module.hot.accept('../reducers', () => 28 | store.replaceReducer(require('../reducers')) 29 | ); 30 | } 31 | 32 | return store; 33 | }; 34 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on the current environment variable, we need to make sure 3 | * to exclude any DevTools-related code from the production builds. 4 | * The code is envify'd - using 'DefinePlugin' in Webpack. 5 | */ 6 | 7 | let loadedStore = null; 8 | 9 | if (process.env.NODE_ENV === 'production') { 10 | loadedStore = require('./configureStore.prod'); 11 | } else { 12 | loadedStore = require('./configureStore.dev'); 13 | } 14 | 15 | export const configureStore = loadedStore; 16 | -------------------------------------------------------------------------------- /src/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import rootReducer from '../reducers'; 3 | import thunk from 'redux-thunk'; 4 | 5 | const finalCreateStore = compose( 6 | applyMiddleware(thunk) 7 | )(createStore); 8 | 9 | module.exports = function configureStore(initialState) { 10 | return finalCreateStore(rootReducer, initialState); 11 | }; 12 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Raleway:100,400); 2 | 3 | $main-bg-color: #f1f2f0; 4 | $accent-blue: #37bbe4; 5 | $accent-gray: #e1e0dd; 6 | $accent-black: #35342f; 7 | $accent-orange: #ec6b2d; 8 | $accent-red: #dd6464; 9 | 10 | body { 11 | background-color: $main-bg-color; 12 | color: $accent-black; 13 | display: flex; 14 | justify-content: center; 15 | font-family: 'Raleway', sans-serif; 16 | } 17 | 18 | .main-app-container { 19 | padding-top: 2em; 20 | } 21 | 22 | .main-app-nav { 23 | font-size: 2em; 24 | text-align: center; 25 | } 26 | 27 | .counter-num-label { 28 | color: $accent-orange; 29 | padding-top: 1em; 30 | font-size: 3em; 31 | text-align: center; 32 | width: 100%; 33 | } 34 | 35 | .counter-even-label { 36 | padding-top: 1em; 37 | font-size: 0.75em; 38 | text-align: center; 39 | text-transform: uppercase; 40 | letter-spacing: 3px; 41 | } 42 | 43 | .counter-buttons { 44 | display: flex; 45 | justify-content: center; 46 | align-items: center; 47 | padding-top: 3em; 48 | 49 | button { 50 | background-color: #CCC; 51 | border: none; 52 | height: 30px; 53 | width: 80px; 54 | margin-left: 1em; 55 | margin-right: 1em; 56 | 57 | &:hover { 58 | background-color: $accent-gray; 59 | } 60 | } 61 | } 62 | 63 | footer { 64 | font-size: 0.75em; 65 | text-align: center; 66 | padding-top: 10em; 67 | 68 | a { 69 | text-decoration: none; 70 | color: inherit; 71 | 72 | &:hover { 73 | color: $accent-blue; 74 | } 75 | } 76 | 77 | #heart{ 78 | color: $accent-red; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | devtool: 'cheap-module-eval-source-map', 7 | entry: [ 8 | 'eventsource-polyfill', // necessary for hot reloading with IE 9 | 'webpack-hot-middleware/client', 10 | './src/index' 11 | ], 12 | output: { 13 | path: path.join(__dirname, 'dist'), 14 | filename: '[name]-[hash].js', 15 | publicPath: '/' 16 | }, 17 | plugins: [ 18 | /** 19 | * This is where the magic happens! You need this to enable Hot Module Replacement! 20 | */ 21 | new webpack.HotModuleReplacementPlugin(), 22 | /** 23 | * NoErrorsPlugin prevents your webpack CLI from exiting with an error code if 24 | * there are errors during compiling - essentially, assets that include errors 25 | * will not be emitted. If you want your webpack to 'fail', you need to check out 26 | * the bail option. 27 | */ 28 | new webpack.NoErrorsPlugin(), 29 | /** 30 | * This is a webpack plugin that simplifies creation of HTML files to serve your 31 | * webpack bundles. This is especially useful for webpack bundles that 32 | * include a hash in the filename which changes every compilation. 33 | */ 34 | new HtmlWebpackPlugin({ 35 | template: 'index.html', 36 | filename: 'index.html', 37 | title: 'Simple Redux Boilerplate', 38 | inject: 'body' 39 | }), 40 | /** 41 | * DefinePlugin allows us to define free variables, in any webpack build, you can 42 | * use it to create separate builds with debug logging or adding global constants! 43 | * Here, we use it to specify a development build. 44 | */ 45 | new webpack.DefinePlugin({ 46 | 'process.env.NODE_ENV': JSON.stringify('development') 47 | }), 48 | ], 49 | module: { 50 | loaders: [ 51 | { 52 | test: /\.js?/, 53 | exclude: [/node_modules/, /styles/], 54 | loaders: ['babel'], 55 | include: path.join(__dirname, 'src') 56 | }, 57 | { 58 | test: /\.scss$/, 59 | loader: 'style!css!sass' 60 | } 61 | ] 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | devtool: 'source-map', 7 | entry: [ 8 | './src/index' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: '[name]-[hash].js', 13 | publicPath: '/' 14 | }, 15 | plugins: [ 16 | /** 17 | * This plugin assigns the module and chunk ids by occurence count. What this 18 | * means is that frequently used IDs will get lower/shorter IDs - so they become 19 | * more predictable. 20 | */ 21 | new webpack.optimize.OccurenceOrderPlugin(), 22 | /** 23 | * This is a webpack plugin that simplifies creation of HTML files to serve your 24 | * webpack bundles. This is especially useful for webpack bundles that 25 | * include a hash in the filename which changes every compilation. 26 | */ 27 | new HtmlWebpackPlugin({ 28 | template: 'index.html', 29 | filename: 'index.html', 30 | title: 'Simple Redux Boilerplate', 31 | inject: 'body' 32 | }), 33 | /** 34 | * See description in 'webpack.config.dev' for more info. 35 | */ 36 | new webpack.DefinePlugin({ 37 | 'process.env.NODE_ENV': JSON.stringify('production') 38 | }), 39 | /** 40 | * Some of you might recognize this! It minimizes all your JS output of chunks. 41 | * Loaders are switched into a minmizing mode. Obviously, you'd only want to run 42 | * your production code through this! 43 | */ 44 | new webpack.optimize.UglifyJsPlugin({ 45 | compressor: { 46 | warnings: false 47 | } 48 | }) 49 | ], 50 | module: { 51 | loaders: [ 52 | { 53 | test: /\.js$/, 54 | loaders: ['babel'], 55 | include: path.join(__dirname, 'src') 56 | }, 57 | { 58 | test: /\.scss$/, 59 | loader: 'style!css!sass' 60 | } 61 | ] 62 | } 63 | }; 64 | --------------------------------------------------------------------------------