├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .stylelintrc ├── LICENSE ├── README.md ├── bin ├── compile.js └── server.js ├── package.json ├── src ├── index.html ├── index.jsx ├── layouts │ ├── CoreLayout.jsx │ ├── HeaderLayout │ │ ├── HeaderLayout.jsx │ │ ├── HeaderLayout.styl │ │ └── index.js │ └── index.js ├── routes │ ├── Home │ │ ├── HomeVIew.jsx │ │ ├── HomeView.styl │ │ ├── HomeView.test.js │ │ ├── assets │ │ │ └── images │ │ │ │ └── codetony.png │ │ ├── index.js │ │ └── modules │ │ │ └── counter.js │ └── index.jsx ├── store │ ├── create.js │ └── reducers.js ├── styles │ └── base.styl └── tests │ └── helpers │ └── setup.js └── webpack ├── config.js ├── dev.config.js ├── prod.config.js ├── utils └── helpers.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"], 3 | "plugins": [ 4 | "transform-runtime", 5 | "transform-decorators-legacy" 6 | ], 7 | "env": { 8 | "development": { 9 | "plugins": [ 10 | ["react-transform", { 11 | "transforms": [{ 12 | "transform": "react-transform-catch-errors", 13 | "imports": ["react", "redbox-react"] 14 | }] 15 | }] 16 | ] 17 | }, 18 | "production": { 19 | "plugins": [ 20 | "transform-react-remove-prop-types", 21 | "transform-react-constant-elements" 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | dist/** 3 | app/index.html -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser" : "babel-eslint", 3 | "extends" : [ 4 | "airbnb" 5 | ], 6 | "ecmaFeatures": { 7 | "jsx": true 8 | }, 9 | "plugins" : [ 10 | "react" 11 | ], 12 | "env" : { 13 | "browser" : true, 14 | "node" : true 15 | }, 16 | "globals" : { 17 | "__DEVELOPMENT__" : false, 18 | "__PRODUCTION__" : false 19 | }, 20 | "rules" : { 21 | "semi" : ["error", "never"], 22 | "new-cap" : 0, 23 | "arrow-body-style": ["error", "always"], 24 | "brace-style" : ["error", "stroustrup"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/ 3 | npm-debug.log 4 | dist 5 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "color-hex-case": "lower", 5 | "declaration-block-trailing-semicolon": null, 6 | "rule-non-nested-empty-line-before": "never", 7 | "indentation": null 8 | } 9 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 - 2016 Tony Tai Nguyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactJS-Starter-Boilerplate 2 | 3 | ## Features 4 | 5 | * [React](https://github.com/facebook/react) 6 | * [React-router](https://github.com/rackt/react-router) 7 | * [Babel](http://babeljs.io) for ES6 and ES7 transpiling 8 | * [Webpack](https://github.com/webpack/webpack) 9 | * [Koa Webpack Dev Middleware](https://github.com/yiminghe/koa-webpack-dev-middleware) 10 | * [Koa Webpack Hot Middleware](https://github.com/dayAlone/koa-webpack-hot-middleware) 11 | * [ESLint](http://eslint.org) uses airbnb linting preferences 12 | * [StyleLint](https://github.com/stylelint/stylelint) includes style linter for our css/stylus code 13 | * [Redux](https://github.com/rackt/redux)'s futuristic [Flux](https://facebook.github.io/react/blog/2014/05/06/flux.html) implementation 14 | * [React Router Redux](https://github.com/reactjs/react-router-redux) Redux/React Router bindings. 15 | 16 | ## To Do List 17 | ReactJS, Redux, Webpack, PostCSS, Stylus, ESLint, StyleLint, ES6, Modern Front End Starter Boilerplate 18 | 19 | Things planned for this boilerplate: 20 | - [x] Add ES6 Support with .babelrc 21 | - [x] Webpack Configuartions in ES6 22 | - [x] ReactJS 23 | - [x] ESLint 24 | - [x] StyleLint for linting css/stylus 25 | - [x] PostCSS 26 | - [x] Add Plugins for PostCSS (CSSNext which includes autoprefixer, RucksackCSS, Sorting, Mixins, and Short) 27 | - [x] Added support for CSS Modules 28 | - [x] React Router 29 | - [x] Redux 30 | - [x] Add Redux Example Counter 31 | 32 | Suggestions are welcome! 33 | 34 | ## Installation 35 | 36 | Download this by entering this command in your terminal: 37 | ``` 38 | $ git clone https://github.com/tonytainguyen/Modern-ReactJS-Starter-Boilerplate.git 39 | ``` 40 | ## Usage 41 | 42 | Navigate in the project and run the good ol' npm install and start to boot up the localhost. Feel free to change the port anytime in /webpack/config.js 43 | ``` 44 | npm install 45 | npm start 46 | ``` 47 | There are more commands to which I have included in the package.json file which include the following: 48 | - `npm start` starts up localhost with browserSync and hot reloading 49 | - `npm run compile` compiles project and puts them in the /dist folder. 50 | - `npm run deploy` removes the dist folder and compiles afterwards. 51 | - `npm run clean` removes the dist folder and clears npm cache. 52 | 53 | ## Styles 54 | * Stylus with React CSS modules 55 | * PostCSS Plugins included: RucksackCSS, Sorting. 56 | * CSS extraction and Uglify on Production build. 57 | * Supports Component Styling ie: `styleName="awesome"` using CSSModules. 58 | 59 | ## Contributing 60 | 61 | 1. Fork this! 62 | 2. Create your feature branch: `git checkout -b my-new-feature` 63 | 3. Commit your changes: `git commit -am 'Add some feature'` 64 | 4. Push to the branch: `git push origin my-new-feature` 65 | 5. Submit a pull request! 66 | 6. It will be reviewed and appreciated! :smile: 67 | 68 | ## History 69 | 70 | Just put this all together for myself and what I believe are best practices and saves me time and maybe you can benefit off it too! I love using tools that would increase my productivity. I had also structured my webpack to what I believe is pretty organized and not repeating a lot of code like those other boilerplates. My goal was to make webpack easy to understand as much as possible and hopefully I have done that. I will be constantly updating this project! All ideas/suggestions/pull requests are welcome! 71 | 72 | ## License 73 | 74 | Copyright (c) 2015-2016 Tony Tai Nguyen 75 | 76 | MIT (http://opensource.org/licenses/mit-license.php) 77 | -------------------------------------------------------------------------------- /bin/compile.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | import _debug from 'debug' 3 | import chalk from 'chalk' 4 | import webpackConfig from '../webpack/webpack.config.js' 5 | 6 | const debug = _debug('app:bin:compile') 7 | const compiler = webpack(webpackConfig) 8 | 9 | /** 10 | * Runs the webpack compiler 11 | */ 12 | 13 | try { 14 | compiler.run((error, stats) => { 15 | let jsonStats = stats.toJson(), 16 | errorLength = chalk.red.bold(jsonStats.errors.length), 17 | warningLength = chalk.red.bold(jsonStats.warnings.length) 18 | 19 | if (error) { 20 | debug('Error has occured while compiling', error) 21 | } 22 | 23 | if (stats.hasErrors()) { 24 | debug(`There has been ${errorLength} errors while compiling`, jsonStats.errors) 25 | } 26 | else if (stats.hasWarnings()) { 27 | debug(`There are ${warningLength} warnings while compiling`, jsonStats.warnings) 28 | } 29 | else { 30 | debug('No warnings or errors while compiling') 31 | debug('Webpack build has successfully finished!') 32 | } 33 | }) 34 | } 35 | catch (error) { 36 | debug('compiling has failed', error) 37 | } 38 | -------------------------------------------------------------------------------- /bin/server.js: -------------------------------------------------------------------------------- 1 | import koa from 'koa' 2 | import webpack from 'webpack' 3 | import config from '../webpack/config' 4 | import _debug from 'debug' 5 | import webpackDevMiddleware from 'koa-webpack-dev-middleware' 6 | import webpackHotMiddleware from 'koa-webpack-hot-middleware' 7 | import webpackConfig from '../webpack/webpack.config' 8 | 9 | const debug = _debug('app:bin:server') 10 | const app = koa() 11 | const compiler = webpack(webpackConfig) 12 | 13 | debug('Booting up server...') 14 | 15 | /** 16 | * Webpack Middleware Configuarations 17 | */ 18 | app.use(webpackDevMiddleware(compiler, { 19 | publicPath: webpackConfig.output.publicPath, 20 | quiet: false, 21 | noInfo: false, 22 | hot: true, 23 | inline: true, 24 | lazy: false, 25 | historyApiFallback: true, 26 | headers: {'Access-Control-Allow-Origin': '*'}, 27 | stats: { 28 | chunks: false, 29 | chunkModules: false, 30 | colors: true, 31 | }, 32 | })) 33 | 34 | /** 35 | * Webpack Hot Middleware Configuarations 36 | */ 37 | app.use(webpackHotMiddleware(compiler)) 38 | 39 | /** 40 | * Server port listining 41 | */ 42 | app.listen(config.serverPort, (error) => { 43 | if (error) { 44 | debug(`Error while listining to ${config.serverPort}`, error) 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modern-reactjs-starter-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.jsx", 6 | "scripts": { 7 | "start": "better-npm-run start", 8 | "compile": "better-npm-run compile", 9 | "deploy": "better-npm-run deploy", 10 | "clean": "rm -rf dist && npm cache clean" 11 | }, 12 | "betterScripts": { 13 | "start": { 14 | "command": "babel-node bin/server", 15 | "env": { 16 | "NODE_ENV": "development", 17 | "DEBUG": "app:*" 18 | } 19 | }, 20 | "compile": { 21 | "command": "babel-node bin/compile", 22 | "env": { 23 | "NODE_ENV": "production", 24 | "DEBUG": "app:*" 25 | } 26 | }, 27 | "deploy": { 28 | "command": "npm run clean && npm run compile", 29 | "env": { 30 | "NODE_ENV": "production", 31 | "DEBUG": "app:*" 32 | } 33 | } 34 | }, 35 | "author": "Tony Tai Nguyen ", 36 | "license": "ISC", 37 | "devDependencies": { 38 | "babel-cli": "^6.7.7", 39 | "babel-core": "^6.7.7", 40 | "babel-eslint": "^6.0.4", 41 | "babel-loader": "^6.2.4", 42 | "babel-plugin-add-module-exports": "^0.1.4", 43 | "babel-plugin-react-transform": "^2.0.2", 44 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 45 | "babel-plugin-transform-react-constant-elements": "^6.5.0", 46 | "babel-plugin-transform-react-remove-prop-types": "^0.2.6", 47 | "babel-plugin-transform-runtime": "^6.7.5", 48 | "babel-plugin-typecheck": "^3.8.0", 49 | "babel-preset-es2015": "^6.6.0", 50 | "babel-preset-react": "^6.5.0", 51 | "babel-preset-react-hmre": "^1.1.1", 52 | "babel-preset-stage-0": "^6.5.0", 53 | "babel-register": "^6.7.2", 54 | "better-npm-run": "0.0.8", 55 | "browser-sync": "^2.12.5", 56 | "browser-sync-webpack-plugin": "^1.0.1", 57 | "chai": "^3.5.0", 58 | "chalk": "^1.1.3", 59 | "clean-webpack-plugin": "^0.1.9", 60 | "css-loader": "^0.23.1", 61 | "debug": "^2.2.0", 62 | "enzyme": "^2.3.0", 63 | "eslint": "^2.9.0", 64 | "eslint-config-airbnb": "^8.0.0", 65 | "eslint-loader": "^1.3.0", 66 | "eslint-plugin-import": "^1.6.1", 67 | "eslint-plugin-jsx-a11y": "^1.0.4", 68 | "eslint-plugin-promise": "^1.1.0", 69 | "eslint-plugin-react": "^5.0.1", 70 | "extract-text-webpack-plugin": "^1.0.1", 71 | "file-loader": "^0.8.5", 72 | "html-webpack-plugin": "^2.16.0", 73 | "jsdom": "^9.1.0", 74 | "json-loader": "^0.5.4", 75 | "koa": "^1.2.0", 76 | "koa-webpack-dev-middleware": "^1.2.0", 77 | "koa-webpack-hot-middleware": "^1.0.3", 78 | "mobx-react-devtools": "^4.0.5", 79 | "mocha": "^2.4.5", 80 | "node-sass": "^3.7.0", 81 | "npm-install-webpack-plugin": "^3.1.1", 82 | "postcss-loader": "^0.9.1", 83 | "postcss-normalize": "^0.2.0", 84 | "postcss-sorting": "^1.3.1", 85 | "poststylus": "^0.2.3", 86 | "progress-bar-webpack-plugin": "^1.6.0", 87 | "react-addons-test-utils": "^15.0.2", 88 | "react-css-modules": "^3.7.6", 89 | "react-hot-loader": "3.0.0-beta.2", 90 | "react-transform-catch-errors": "^1.0.2", 91 | "react-transform-hmr": "^1.0.4", 92 | "redbox-react": "^1.2.3", 93 | "rucksack-css": "^0.8.6", 94 | "style-loader": "^0.13.1", 95 | "stylelint": "^6.2.2", 96 | "stylelint-config-standard": "^6.0.0", 97 | "stylelint-loader": "^5.0.1", 98 | "stylelint-webpack-plugin": "^0.2.0", 99 | "stylus": "^0.54.5", 100 | "stylus-loader": "^2.0.0", 101 | "url-loader": "^0.5.7", 102 | "webpack": "^1.13.0", 103 | "webpack-dev-server": "^1.14.1" 104 | }, 105 | "dependencies": { 106 | "history": "^2.1.1", 107 | "lodash": "^4.12.0", 108 | "react": "^15.0.2", 109 | "react-dom": "^15.0.2", 110 | "react-redux": "^4.4.5", 111 | "react-router": "^2.4.0", 112 | "react-router-redux": "^4.0.4", 113 | "redux": "^3.5.2" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Starter Boilerplate 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDom from 'react-dom' 3 | import Root from './routes' 4 | 5 | if (module.hot && __DEVELOPMENT__) { 6 | module.hot.accept() 7 | } 8 | 9 | ReactDom.render( 10 | , 11 | document.getElementById('app') 12 | ) 13 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout.jsx: -------------------------------------------------------------------------------- 1 | import '../styles/base.styl' 2 | import React from 'react' 3 | import { observer } from 'mobx-react' 4 | 5 | // Layouts 6 | import HeaderLayout from './HeaderLayout' 7 | 8 | const CoreLayout = observer(({ children }) => { 9 | return ( 10 |
11 | 12 | {children} 13 |
14 | ) 15 | }) 16 | 17 | export default CoreLayout 18 | -------------------------------------------------------------------------------- /src/layouts/HeaderLayout/HeaderLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CSSModules from 'react-css-modules' 3 | 4 | // Styles 5 | import Styles from './HeaderLayout.styl' 6 | 7 | const HeaderLayout = () => { 8 | return ( 9 |
10 |

CodeTony's React Starter Boilerplate

11 |
12 | ) 13 | } 14 | 15 | export default CSSModules(HeaderLayout, Styles, {allowMultiple: true}) 16 | -------------------------------------------------------------------------------- /src/layouts/HeaderLayout/HeaderLayout.styl: -------------------------------------------------------------------------------- 1 | /*** Header Layout Styles ***/ 2 | @import '../../styles/base' 3 | 4 | .main-header 5 | font-size: 20px 6 | text-align: center 7 | -------------------------------------------------------------------------------- /src/layouts/HeaderLayout/index.js: -------------------------------------------------------------------------------- 1 | import HeaderLayout from './HeaderLayout.jsx' 2 | export default HeaderLayout 3 | -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import CoreLayout from './CoreLayout.jsx' 2 | export default CoreLayout 3 | -------------------------------------------------------------------------------- /src/routes/Home/HomeVIew.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import CssModules from 'react-css-modules' 4 | import { 5 | incrementByOne, 6 | incrementByFive, 7 | resetCouter 8 | } from './modules/counter' 9 | 10 | // Styles 11 | import Styles from './HomeView.styl' 12 | 13 | // Images 14 | import MainLogo from './assets/images/codetony.png' 15 | 16 | class HomeView extends React.Component { 17 | constructor(props) { 18 | super(props) 19 | } 20 | 21 | render() { 22 | return ( 23 |
24 | 25 |

Counter Example

26 |

27 | Currently our number is: 28 | {this.props.counter.number} 29 |

30 | 31 | 32 | 33 |
34 | ) 35 | } 36 | } 37 | 38 | const mapActionCreators = { 39 | incrementByOne: () => incrementByOne(), 40 | incrementByFive: () => incrementByFive(), 41 | resetCounter: () => resetCouter() 42 | } 43 | 44 | const mapStateToProps = (state) => { 45 | return { 46 | counter: state.counter 47 | } 48 | } 49 | 50 | export default connect( 51 | mapStateToProps, 52 | mapActionCreators 53 | )(CssModules(HomeView, Styles)) 54 | -------------------------------------------------------------------------------- /src/routes/Home/HomeView.styl: -------------------------------------------------------------------------------- 1 | /*** Main Home Styles ***/ 2 | @import '../../styles/base' 3 | 4 | .main-home 5 | font-size: 16px 6 | text-align: center 7 | 8 | h3 9 | font-size: 30px 10 | 11 | span 12 | color: blue 13 | margin-left: 10px 14 | font-size: 40px 15 | 16 | button 17 | padding: 20px 18 | 19 | button:not(:last-child) 20 | margin-right: 30px 21 | margin-top: 30px -------------------------------------------------------------------------------- /src/routes/Home/HomeView.test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codetony25/react-redux-starter-boilerplate/27d684d758945ce7383f537dc6d0f30448bf0efb/src/routes/Home/HomeView.test.js -------------------------------------------------------------------------------- /src/routes/Home/assets/images/codetony.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codetony25/react-redux-starter-boilerplate/27d684d758945ce7383f537dc6d0f30448bf0efb/src/routes/Home/assets/images/codetony.png -------------------------------------------------------------------------------- /src/routes/Home/index.js: -------------------------------------------------------------------------------- 1 | import HomeView from './HomeView.jsx' 2 | export default HomeView 3 | -------------------------------------------------------------------------------- /src/routes/Home/modules/counter.js: -------------------------------------------------------------------------------- 1 | // Action Constants 2 | const INCREMENT_BY_ONE = 'INCREMENT_BY_ONE' 3 | const INCREMENT_BY_FIVE = 'INCREMENT_BY_FIVE' 4 | const RESET_COUNTER = 'RESET_COUNTER' 5 | 6 | // Action Creators 7 | export function incrementByOne() { 8 | return { 9 | type: INCREMENT_BY_ONE, 10 | } 11 | } 12 | 13 | export function incrementByFive() { 14 | return { 15 | type: INCREMENT_BY_FIVE, 16 | } 17 | } 18 | 19 | export function resetCouter() { 20 | return { 21 | type: RESET_COUNTER, 22 | } 23 | } 24 | 25 | // Reducer 26 | export default function counterReducer(state = {number: 0}, action) { 27 | if (state === 'undefined') { 28 | state = { number: 0 } 29 | } 30 | switch (action.type) { 31 | case 'INCREMENT_BY_ONE': 32 | return { 33 | ...state, 34 | number: ++state.number 35 | } 36 | case 'INCREMENT_BY_FIVE': 37 | return { 38 | ...state, 39 | number: state.number + 5 40 | } 41 | case 'RESET_COUNTER': 42 | return { 43 | ...state, 44 | number: 0 45 | } 46 | default: 47 | return state 48 | } 49 | } -------------------------------------------------------------------------------- /src/routes/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import { syncHistoryWithStore } from 'react-router-redux' 4 | import createStore from '../store/create' 5 | import { 6 | Router, 7 | Route, 8 | browserHistory, 9 | IndexRoute, 10 | } from 'react-router' 11 | 12 | // Views 13 | import Home from './Home' 14 | 15 | // Layouts 16 | import CoreLayout from '../layouts' 17 | 18 | const store = createStore({}) 19 | const history = syncHistoryWithStore(browserHistory, store) 20 | 21 | export default () => { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/store/create.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose } from 'redux' 2 | import rootReducer from './reducers' 3 | 4 | export default (initialState = {}) => { 5 | const store = createStore( 6 | rootReducer, 7 | initialState 8 | ) 9 | 10 | if (module.hot) { 11 | module.hot.accept('./reducers', () => { 12 | const reducers = require('./reducers').default 13 | store.replaceReducer(reducers) 14 | }) 15 | } 16 | 17 | return store 18 | } 19 | -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { routerReducer } from 'react-router-redux' 3 | 4 | // Modules 5 | import counter from '../routes/Home/modules/counter' 6 | 7 | const rootReducer = combineReducers({ 8 | counter, 9 | routing: routerReducer 10 | }) 11 | 12 | export default rootReducer -------------------------------------------------------------------------------- /src/styles/base.styl: -------------------------------------------------------------------------------- 1 | /*** Global styles ***/ 2 | 3 | /* Import Example */ 4 | //@import "colors" 5 | //@import "fonts" 6 | //@import "responsive" 7 | 8 | html 9 | box-sizing: border-box 10 | 11 | body 12 | margin: 0 13 | padding: 0 14 | height: 100% 15 | font-smoothing: antialiased 16 | -webkit-font-smoothing: subpixel-antialiased 17 | text-rendering: optimizeLegibility 18 | -moz-osx-font-smoothing: grayscale 19 | 20 | *, 21 | *::before, 22 | *::after 23 | box-sizing: inherit 24 | 25 | a 26 | text-decoration: none 27 | -------------------------------------------------------------------------------- /src/tests/helpers/setup.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(); 2 | 3 | let jsdom = require('jsdom').jsdom; 4 | 5 | let exposedProperties = ['window', 'navigator', 'document']; 6 | 7 | global.document = jsdom(''); 8 | global.window = document.defaultView; 9 | Object.keys(document.defaultView).forEach((property) => { 10 | if (typeof global[property] === 'undefined') { 11 | exposedProperties.push(property); 12 | global[property] = document.defaultView[property]; 13 | } 14 | }); 15 | 16 | global.navigator = { 17 | userAgent: 'node.js' 18 | }; 19 | 20 | documentRef = document; 21 | -------------------------------------------------------------------------------- /webpack/config.js: -------------------------------------------------------------------------------- 1 | import { getPath } from './utils/helpers' 2 | 3 | /** 4 | * Set up Webpack Configurations 5 | */ 6 | const nodeEnv = process.env.NODE_ENV 7 | 8 | let isNodeEnvironmentDevelopment = (nodeEnv === 'development'), 9 | isNodeEnvironmentProduction = (nodeEnv === 'production'), 10 | devTool = (nodeEnv === 'development' ? 'cheap-module-source-map' : 'source-map') 11 | 12 | /** 13 | * Custom Webpack Configuration 14 | */ 15 | const config = { 16 | env: nodeEnv || 'development', 17 | target: process.env.npm_lifecycle_event, 18 | cache: isNodeEnvironmentDevelopment ? true : false, 19 | appPath: getPath('../src'), 20 | distPath: getPath('../dist'), 21 | rootPath: getPath('../'), 22 | stylePath: getPath('../src/styles/base.css'), 23 | htmlPath: getPath('../src/index.html'), 24 | serverHost: 'localhost', 25 | serverPort: '8080', 26 | devTool, 27 | globals: { 28 | __DEVELOPMENT__: isNodeEnvironmentDevelopment, 29 | __PRODUCTION__ : isNodeEnvironmentProduction, 30 | } 31 | } 32 | 33 | export default config 34 | -------------------------------------------------------------------------------- /webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | import _debug from 'debug' 3 | import BrowserSyncPlugin from 'browser-sync-webpack-plugin' 4 | 5 | const debug = _debug('app:webpack:dev') 6 | 7 | /** 8 | * Development Plugins 9 | */ 10 | const developmentConfig = (webpackConfig, config) => { 11 | debug('Setting up development plugins') 12 | webpackConfig.plugins.push( 13 | new BrowserSyncPlugin( 14 | { 15 | host: config.serverHost, 16 | port: config.serverPort, 17 | proxy: `http://${config.serverHost}:${config.serverPort}/`, 18 | }, 19 | { 20 | reload: false, 21 | }, 22 | ), 23 | new webpack.HotModuleReplacementPlugin(), 24 | new webpack.NoErrorsPlugin() 25 | ) 26 | } 27 | 28 | export default developmentConfig 29 | -------------------------------------------------------------------------------- /webpack/prod.config.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | import _debug from 'debug' 3 | import ExtractTextPlugin from 'extract-text-webpack-plugin' 4 | import { cssLoaderAddExtract } from './utils/helpers' 5 | 6 | const debug = _debug('app:webpack:prod') 7 | 8 | /** 9 | * Production Plugins 10 | */ 11 | const productionConfig = (webpackConfig) => { 12 | debug('Extracting Css Loaders with the ExtractTextPlugin...') 13 | cssLoaderAddExtract(webpackConfig, /css/, ExtractTextPlugin) 14 | 15 | debug('Setting up development plugins') 16 | webpackConfig.plugins.push( 17 | new webpack.optimize.DedupePlugin(), 18 | new webpack.optimize.OccurenceOrderPlugin(), 19 | new webpack.optimize.UglifyJsPlugin({ 20 | compress: { 21 | warnings: false, 22 | dead_code: true, 23 | unused: true, 24 | screw_ie8: true, 25 | sequences: true, 26 | conditionals: true, 27 | booleans: true, 28 | if_return: true, 29 | join_vars: true, 30 | drop_console: true, 31 | }, 32 | }), 33 | new webpack.optimize.CommonsChunkPlugin({ 34 | name: 'vendor', 35 | filename: 'vendor-[hash].min.js', 36 | minChunks: Infinity, 37 | }), 38 | new ExtractTextPlugin('[name].[contenthash].css', { 39 | allChunks: true, 40 | }) 41 | ) 42 | } 43 | 44 | export default productionConfig 45 | -------------------------------------------------------------------------------- /webpack/utils/helpers.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import fs from 'fs' 3 | 4 | /** 5 | * Webpack Configuration Helpers 6 | */ 7 | 8 | // Gets directory path 9 | export const getPath = (pathParam) => { 10 | return path.join(__dirname, `../${pathParam}`) 11 | } 12 | 13 | // Replaces css loader with the extract plugin css loader 14 | export const cssLoaderAddExtract = (config, searchParam, plugin) => { 15 | config.module.loaders.filter((loader) => { 16 | return loader.loaders && loader.loaders.find((name) => { 17 | return searchParam.test(name.split('?')[0]) 18 | }) 19 | }).forEach((cssLoader) => { 20 | const [first, ...rest] = cssLoader.loaders 21 | let singleCssLoader = cssLoader.loader 22 | singleCssLoader = plugin.extract(first, rest.join('!')) 23 | return singleCssLoader 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | import HtmlWebpackPlugin from 'html-webpack-plugin' 2 | import _debug from 'debug' 3 | import webpack from 'webpack' 4 | import rucksack from 'rucksack-css' 5 | import sorting from 'postcss-sorting' 6 | import normalize from 'postcss-normalize' 7 | import poststylus from 'poststylus' 8 | import chalk from 'chalk' 9 | import ProgressBarPlugin from 'progress-bar-webpack-plugin'; 10 | import config from './config.js' 11 | 12 | import developmentConfig from './dev.config.js' 13 | import productionConfig from './prod.config.js' 14 | 15 | const debug = _debug('app:webpack:config') 16 | 17 | /** 18 | * Webpack Global Variables 19 | */ 20 | const { 21 | __DEVELOPMENT__, 22 | __PRODUCTION__, 23 | } = config.globals 24 | 25 | /** 26 | * Webpack Configuarations 27 | */ 28 | debug('Starting Webpack Configurations...') 29 | const webpackConfig = { 30 | target: 'web', 31 | devtool: config.devTool, 32 | node: { fs: 'empty' }, 33 | module: {}, 34 | cache: config.cache, 35 | stylus: {}, 36 | resolve: { 37 | extensions: ['', '.json', '.js', '.jsx'], 38 | modulesDirectories: ['node_modules'], 39 | }, 40 | } 41 | 42 | /** 43 | * Entry points for App 44 | */ 45 | webpackConfig.entry = { 46 | app: [ 47 | 'webpack/hot/dev-server', 48 | 'webpack-hot-middleware/client', 49 | config.appPath, 50 | ], 51 | vendor: [ 52 | 'react', 53 | 'react-dom', 54 | 'react-router', 55 | 'react-redux', 56 | 'react-router-redux', 57 | 'redux', 58 | 'history', 59 | 'lodash', 60 | ], 61 | } 62 | 63 | /** 64 | * Output points for App 65 | */ 66 | webpackConfig.output = { 67 | path: config.distPath, 68 | filename: '[name].[hash].js', 69 | publicPath: `http://${config.serverHost}:${config.serverPort}/`, 70 | } 71 | 72 | /** 73 | * JavaScript and JSON Loaders 74 | */ 75 | webpackConfig.module.loaders = [ 76 | { 77 | test: /\.jsx?$/, 78 | include: config.appPath, 79 | loader: 'babel?cacheDirectory', 80 | }, 81 | { 82 | test: /\.json$/, 83 | loader: 'json', 84 | }, 85 | ] 86 | 87 | /** 88 | * Style Loaders and Configurations 89 | */ 90 | const styleModuleLoader = [ 91 | 'css?sourceMap&-minimize', 92 | 'modules', 93 | 'importLoaders=1', 94 | 'localIdentName=[name]__[local]___[hash:base64:5]' 95 | ].join('&') 96 | 97 | webpackConfig.module.loaders.push( 98 | { 99 | test: /\.styl$/, 100 | loaders: [ 101 | 'style', 102 | styleModuleLoader, 103 | 'postcss', 104 | 'stylus?resolve url', 105 | ], 106 | include: config.appPath, 107 | } 108 | ) 109 | 110 | webpackConfig.stylus = { 111 | use: [ 112 | poststylus([ 113 | normalize, 114 | sorting, 115 | rucksack({ 116 | autoprefixer: true, 117 | fallback: true, 118 | }), 119 | ]), 120 | ], 121 | } 122 | 123 | /** 124 | * File Loaders 125 | */ 126 | const filePrefix = 'prefix=fonts/&name=[path][name]' 127 | const fileType = '.[ext]&limit=10000&mimetype=' 128 | 129 | webpackConfig.module.loaders.push( 130 | { 131 | test: /\.woff(\?.*)?$/, 132 | loader: `url?${filePrefix}${fileType}application/font-woff`, 133 | }, 134 | { 135 | test: /\.woff2(\?.*)?$/, 136 | loader: `url?${filePrefix}${fileType}application/font-woff2`, 137 | }, 138 | { 139 | test: /\.otf(\?.*)?$/, 140 | loader: `file?${filePrefix}${fileType}font/opentype`, 141 | }, 142 | { 143 | test: /\.ttf(\?.*)?$/, 144 | loader: `url?${filePrefix}${fileType}application/octet-stream`, 145 | }, 146 | { 147 | test: /\.eot(\?.*)?$/, 148 | loader: `file?${filePrefix}`, 149 | }, 150 | { 151 | test: /\.svg(\?.*)?$/, 152 | loader: `url?{$filePrefix}${fileType}image/svg+xml`, 153 | }, 154 | { 155 | test: /\.(png|jpg|gif)$/, 156 | loader: 'url?limit=8192', 157 | }, 158 | ) 159 | 160 | /** 161 | * Plugin Configurations 162 | */ 163 | let progressChalk = chalk.cyan.bold(' Webpack buliding in progress: '), 164 | barChalk = chalk.magenta.bold('[:bar]'), 165 | percentChalk = chalk.green.bold(':percent'); 166 | 167 | webpackConfig.plugins = [ 168 | new webpack.DefinePlugin({ 169 | 'process.env.NODE_ENV': JSON.stringify(config.env), 170 | __DEVELOPMENT__, 171 | __PRODUCTION__, 172 | }), 173 | new ProgressBarPlugin({ 174 | format: `${progressChalk} ${barChalk} ${percentChalk} ( :elapsed seconds )`, 175 | clear: false, 176 | }), 177 | new HtmlWebpackPlugin({ 178 | template: config.htmlPath, 179 | hash : false, 180 | filename: 'index.html', 181 | inject : 'body', 182 | minify : { collapseWhitespace: true }, 183 | }), 184 | ] 185 | 186 | /** 187 | * Development Only Configurations 188 | */ 189 | if (__DEVELOPMENT__) { 190 | debug('Running only Development Webpack Configurations') 191 | developmentConfig(webpackConfig, config) 192 | } 193 | 194 | /** 195 | * Production Only Configurations 196 | */ 197 | if (__PRODUCTION__) { 198 | debug('Running only Production Webpack Configurations') 199 | productionConfig(webpackConfig, config) 200 | } 201 | 202 | export default webpackConfig 203 | --------------------------------------------------------------------------------