├── src ├── pages │ ├── home │ │ ├── style.css │ │ └── page.js │ └── login │ │ ├── style.css │ │ └── page.js ├── common │ ├── components │ │ ├── App.js │ │ └── Routes.js │ └── base.css └── main.js ├── .babelrc ├── start.js ├── test └── common │ └── components │ └── App-test.js ├── .gitignore ├── app.json ├── .travis.yml ├── webpack.production.config.js ├── LICENSE ├── CHANGELOG.md ├── package.json ├── server.js ├── webpack.local.config.js └── README.md /src/pages/home/style.css: -------------------------------------------------------------------------------- 1 | .content { 2 | margin-top: 20px; 3 | } 4 | 5 | .welcomeText { 6 | font-size: 20px; 7 | color: #7F7F7F; 8 | } 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ], 7 | "plugins": [ 8 | "transform-runtime" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | // Server-side entrypoint that registers Babel's require() hook 2 | const babelRegister = require('babel-register'); 3 | babelRegister(); 4 | 5 | require('./server'); 6 | -------------------------------------------------------------------------------- /src/common/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | export default ({children}) => { 5 | return ( 6 |
7 | {children} 8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /test/common/components/App-test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import React from 'react'; 3 | import { shallow } from 'enzyme'; 4 | 5 | import App from '../../../src/common/components/App'; 6 | 7 | 8 | test('render with container div', t => { 9 | const wrapper = shallow(React.createElement(App)); 10 | 11 | t.is(wrapper.find('#container').length, 1); 12 | }); 13 | -------------------------------------------------------------------------------- /src/pages/home/page.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./style.css"; 3 | 4 | 5 | export default class HomePage extends React.Component { 6 | render() { 7 | return ( 8 |
9 |

Home Page

10 |

Thanks for joining!

11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | build/app.js 3 | build/style.css 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Coverage directory used by tools like nyc 10 | .nyc_output 11 | coverage 12 | 13 | # Compiled binary addons (http://nodejs.org/api/addons.html) 14 | build/Release 15 | 16 | # Dependency directory 17 | node_modules 18 | 19 | # Users Environment Variables 20 | .lock-wscript 21 | 22 | .idea 23 | -------------------------------------------------------------------------------- /src/common/components/Routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | import App from './App'; 5 | import LoginPage from '../../pages/login/page'; 6 | import HomePage from '../../pages/home/page'; 7 | 8 | 9 | export default ( 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Essential React", 3 | "description": "A minimal skeleton for building testable React apps using Babel", 4 | "keywords": [ 5 | "react", 6 | "babel", 7 | "starter" 8 | ], 9 | "repository": "https://github.com/pheuter/essential-react", 10 | "logo": "https://dl.dropboxusercontent.com/u/1803181/essential-react-logo.png", 11 | "env": { 12 | "PRODUCTION": "true", 13 | "NPM_CONFIG_PRODUCTION": "false" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/login/style.css: -------------------------------------------------------------------------------- 1 | .content { 2 | margin-top: 50px; 3 | } 4 | 5 | .heading { 6 | font-size: 10vw; 7 | } 8 | 9 | .lead { 10 | display: inline-block; 11 | margin-right: 20px; 12 | font-size: 3vw; 13 | } 14 | 15 | .signUpButton { 16 | display: inline-block; 17 | padding: 1vw 2vw; 18 | border: 0; 19 | font-size: 2vw; 20 | 21 | background-color: #4B9DA3; 22 | color: #f3f3f3; 23 | 24 | cursor: pointer; 25 | border-radius: 2px; 26 | text-shadow: 0px 1px 1px rgba(0,0,0,0.5); 27 | box-shadow: 0px 5px 25px -5px rgba(0,0,0,0.5); 28 | } 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 4 5 | - 5 6 | - 6 7 | script: 8 | - npm test 9 | - npm run build 10 | after_script: 11 | - npm run coveralls 12 | env: 13 | global: 14 | - secure: H7Yi0dWwKnnAbGHIQjFnFTDbZy177ghm2VtzV0z9FpAFI5arQZQpE0cIe3glsEawRqXoYSjopLza8oDHRnbYynPj2a7vrQy/duiYFNbTgE4xzXlvZzRMs2D+9De+fMHNjmcsSoEgR9gukwooiN2KYELtQC8tyn/H8T0J3GAGXBM= 15 | - secure: LcwufGqqrC7aMj88Dwkevu2ugmDWIpvL07gLtk3gMu+xPFTnyXtPNgFOXnOOXSl3tsw5w8y04aJvIWNKWN5xHOSBdAd/T4sNfhqhHh5q8h6vwsww4IH9u3cCsEQburY9tnIxs2qUkcIweijsXWXrAZUTP0sckqgq8fTtKW8JF8k= 16 | -------------------------------------------------------------------------------- /src/pages/login/page.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory } from 'react-router'; 3 | import styles from './style.css'; 4 | 5 | 6 | export default class LoginPage extends React.Component { 7 | signUp() { 8 | browserHistory.push('/home'); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |

Login Page

15 |

Create an account to get started!

16 | 17 |
18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App entry point 3 | */ 4 | 5 | // Polyfill 6 | import 'babel-polyfill'; 7 | 8 | // Libraries 9 | import React from 'react'; 10 | import ReactDOM from 'react-dom'; 11 | import { Router, browserHistory } from 'react-router'; 12 | 13 | // Routes 14 | import Routes from './common/components/Routes'; 15 | 16 | // Base styling 17 | import './common/base.css'; 18 | 19 | 20 | // ID of the DOM element to mount app on 21 | const DOM_APP_EL_ID = 'app'; 22 | 23 | // Render the router 24 | ReactDOM.render(( 25 | 26 | {Routes} 27 | 28 | ), document.getElementById(DOM_APP_EL_ID)); 29 | 30 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | /** 5 | * This is the Webpack configuration file for production. 6 | */ 7 | module.exports = { 8 | entry: "./src/main", 9 | 10 | output: { 11 | path: __dirname + "/build/", 12 | filename: "app.js" 13 | }, 14 | 15 | plugins: [ 16 | new ExtractTextPlugin('style.css', { allChunks: true }) 17 | ], 18 | 19 | module: { 20 | loaders: [ 21 | { test: /\.jsx?$/, exclude: /node_modules/, loader: "babel-loader" }, 22 | { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader') } 23 | ] 24 | }, 25 | 26 | resolve: { 27 | extensions: ['', '.js', '.jsx', '.css'] 28 | }, 29 | 30 | postcss: [ 31 | require('autoprefixer'), 32 | require('postcss-nested') 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /src/common/base.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 8 | font-weight: 300; 9 | background-color: #f1f1f1; 10 | } 11 | 12 | h1 { 13 | margin: 0; 14 | color: #F97F85; 15 | } 16 | 17 | :global(#container) { 18 | width: 100%; 19 | 20 | @media (min-width: 960px) { 21 | width: 960px; 22 | margin: 0 auto; 23 | } 24 | 25 | :global(#navigation) { 26 | padding: 10px 15px; 27 | background-color: #F9E957; 28 | 29 | ul { 30 | list-style: none; 31 | margin: 0; 32 | padding: 0; 33 | 34 | li { 35 | display: inline-block; 36 | font-size: 18px; 37 | font-weight: bold; 38 | 39 | a { 40 | color: #555; 41 | text-decoration: none; 42 | 43 | &:hover { 44 | text-decoration: underline; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mark Fayngersh 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. 22 | 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.5.1 2 | 3 | Added package-lock.json file 4 | 5 | # v0.5.0 6 | 7 | Upgrade React 0.14 -> 15.0 8 | 9 | # v0.4.0 10 | 11 | Major refactor to bring dependencies up-to-date: 12 | 13 | - Upgrade React 0.13 -> 0.14 14 | - Upgrade React Router 0.x -> 2.0 15 | - Upgrade Babel 5 -> 6 16 | - Replace Karma and Istanbul with ava and nyc 17 | - No more custom routers 18 | - Added server-side Babel require hook 19 | 20 | 21 | # v0.3.0 22 | 23 | - Implemented [css-modules](https://github.com/css-modules/css-modules) with basic styling 24 | - Bumped karma to 0.13.9 @claudiopro 25 | 26 | # v0.2.5 27 | 28 | ES6 cleanup with static class properties - @StevenLangbroek 29 | 30 | # v0.2.4 31 | 32 | Updated various dependencies 33 | 34 | # v0.2.3 35 | 36 | - Use the HTML5 history API for cleaner URLs - @paulyoung 37 | - Wrapped up remaining unit tests, better code coverage 38 | - `PRODUCTION` environment variable now serves static file when running `node server.js` 39 | - Build app upon npm postinstall 40 | - Instant deploy with Heroku button 41 | 42 | # v0.2.2 43 | 44 | Integrated with [Sauce Labs](https://saucelabs.com/) for automated, cross-browser testing. 45 | 46 | # v0.2.1 47 | 48 | Updated React and React Router to v0.13.1 49 | 50 | # v0.2.0 51 | 52 | Major changes: 53 | - Moved underlying module / build system from Browserify to Webpack. 54 | - Using React Hot Loader under the scenes to automatically reload file changes during local dev 55 | - Using Karma to run test suite and generate code coverage reports 56 | 57 | Removed no longer necessary dependencies: 58 | - browserify 59 | - watchify 60 | - expect.js 61 | - xhr 62 | - es5-shim 63 | - console-polyfill 64 | 65 | Continuing to maintain core philosophies of minimal tooling and testing: 66 | - Simplified run scripts: `watch`, `watch-js` and `server` replaced with `server` 67 | - No additional commands needed 68 | - Tests now include Sinon for mocks and Chai for assertions, no more expect.js 69 | - Same `src/` directory structure 70 | 71 | # v0.1.0 72 | 73 | First release. 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "essential-react", 3 | "version": "0.5.0", 4 | "description": "A minimal skeleton for building testable React apps using Babel", 5 | "main": "src/main.js", 6 | "scripts": { 7 | "postinstall": "npm run build", 8 | "server": "node start.js", 9 | "build": "webpack -p --progress --config webpack.production.config.js", 10 | "test": "nyc ava --fail-fast --verbose --require babel-register", 11 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 12 | "clean": "rm build/app.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/pheuter/essential-react.git" 17 | }, 18 | "keywords": [ 19 | "skeleton", 20 | "template", 21 | "react", 22 | "quickstart" 23 | ], 24 | "author": "Mark Fayngersh", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/pheuter/essential-react/issues" 28 | }, 29 | "homepage": "https://github.com/pheuter/essential-react", 30 | "devDependencies": { 31 | "autoprefixer": "^6.0.2", 32 | "ava": "^0.12.0", 33 | "babel-loader": "^6.2.4", 34 | "babel-plugin-transform-runtime": "^6.5.2", 35 | "babel-preset-es2015": "^6.5.0", 36 | "babel-preset-react": "^6.5.0", 37 | "babel-preset-stage-0": "^6.5.0", 38 | "babel-register": "^6.5.2", 39 | "coveralls": "^2.11.2", 40 | "css-loader": "^0.18.0", 41 | "enzyme": "^2.0.0", 42 | "extract-text-webpack-plugin": "^0.8.2", 43 | "nyc": "^5.6.0", 44 | "postcss-loader": "^0.6.0", 45 | "postcss-nested": "^1.0.0", 46 | "react-addons-test-utils": "^15.0.1", 47 | "react-hot-loader": "^1.3.0", 48 | "sinon": "^1.17.2", 49 | "style-loader": "^0.12.3", 50 | "webpack": "^1.12.14", 51 | "webpack-dev-server": "^1.14.1" 52 | }, 53 | "dependencies": { 54 | "babel-core": "^6.5.2", 55 | "babel-polyfill": "^6.5.0", 56 | "babel-runtime": "^6.5.0", 57 | "express": "^4.12.3", 58 | "react": "^15.0.1", 59 | "react-dom": "^15.0.1", 60 | "react-router": "^2.0.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const app = express(); 3 | 4 | 5 | /************************************************************ 6 | * 7 | * Express routes for: 8 | * - app.js 9 | * - style.css 10 | * - index.html 11 | * 12 | ************************************************************/ 13 | 14 | // Serve application file depending on environment 15 | app.get('/app.js', (req, res) => { 16 | if (process.env.PRODUCTION) { 17 | res.sendFile(__dirname + '/build/app.js'); 18 | } else { 19 | res.redirect('//localhost:9090/build/app.js'); 20 | } 21 | }); 22 | 23 | // Serve aggregate stylesheet depending on environment 24 | app.get('/style.css', (req, res) => { 25 | if (process.env.PRODUCTION) { 26 | res.sendFile(__dirname + '/build/style.css'); 27 | } else { 28 | res.redirect('//localhost:9090/build/style.css'); 29 | } 30 | }); 31 | 32 | // Serve index page 33 | app.get('*', (req, res) => { 34 | res.sendFile(__dirname + '/build/index.html'); 35 | }); 36 | 37 | 38 | /************************************************************* 39 | * 40 | * Webpack Dev Server 41 | * 42 | * See: http://webpack.github.io/docs/webpack-dev-server.html 43 | * 44 | *************************************************************/ 45 | 46 | if (!process.env.PRODUCTION) { 47 | const webpack = require('webpack'); 48 | const WebpackDevServer = require('webpack-dev-server'); 49 | const config = require('./webpack.local.config'); 50 | 51 | new WebpackDevServer(webpack(config), { 52 | publicPath: config.output.publicPath, 53 | hot: true, 54 | noInfo: true, 55 | historyApiFallback: true 56 | }).listen(9090, 'localhost', (err, result) => { 57 | if (err) { 58 | console.log(err); 59 | } 60 | }); 61 | } 62 | 63 | 64 | /****************** 65 | * 66 | * Express server 67 | * 68 | *****************/ 69 | 70 | const port = process.env.PORT || 8080; 71 | const server = app.listen(port, () => { 72 | const host = server.address().address; 73 | const port = server.address().port; 74 | 75 | console.log('Essential React listening at http://%s:%s', host, port); 76 | }); 77 | -------------------------------------------------------------------------------- /webpack.local.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | /** 5 | * This is the Webpack configuration file for local development. It contains 6 | * local-specific configuration such as the React Hot Loader, as well as: 7 | * 8 | * - The entry point of the application 9 | * - Where the output file should be 10 | * - Which loaders to use on what files to properly transpile the source 11 | * 12 | * For more information, see: http://webpack.github.io/docs/configuration.html 13 | */ 14 | module.exports = { 15 | 16 | // Efficiently evaluate modules with source maps 17 | devtool: "eval", 18 | 19 | // Set entry point to ./src/main and include necessary files for hot load 20 | entry: [ 21 | "webpack-dev-server/client?http://localhost:9090", 22 | "webpack/hot/only-dev-server", 23 | "./src/main" 24 | ], 25 | 26 | // This will not actually create a bundle.js file in ./build. It is used 27 | // by the dev server for dynamic hot loading. 28 | output: { 29 | path: __dirname + "/build/", 30 | filename: "app.js", 31 | publicPath: "http://localhost:9090/build/" 32 | }, 33 | 34 | // Necessary plugins for hot load 35 | plugins: [ 36 | new webpack.HotModuleReplacementPlugin(), 37 | new webpack.NoErrorsPlugin(), 38 | new ExtractTextPlugin('style.css', { allChunks: true }) 39 | ], 40 | 41 | // Transform source code using Babel and React Hot Loader 42 | module: { 43 | loaders: [ 44 | { test: /\.jsx?$/, exclude: /node_modules/, loaders: ["react-hot", "babel-loader"] }, 45 | { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader') } 46 | ] 47 | }, 48 | 49 | // Automatically transform files with these extensions 50 | resolve: { 51 | extensions: ['', '.js', '.jsx', '.css'] 52 | }, 53 | 54 | // Additional plugins for CSS post processing using postcss-loader 55 | postcss: [ 56 | require('autoprefixer'), // Automatically include vendor prefixes 57 | require('postcss-nested') // Enable nested rules, like in Sass 58 | ] 59 | 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Essential React 2 | 3 | [![Travis branch](https://img.shields.io/travis/pheuter/essential-react.svg?style=flat-square)](https://travis-ci.org/pheuter/essential-react) 4 | [![Coveralls](https://img.shields.io/coveralls/pheuter/essential-react.svg?style=flat-square)](https://coveralls.io/r/pheuter/essential-react) 5 | 6 | --- 7 | 8 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 9 | 10 | A minimal skeleton for building testable React apps using Babel. 11 | 12 | - [Design Goals](#design-goals) 13 | - [Getting Started](#getting-started) 14 | - [Commands](#commands) 15 | - [server](#server) 16 | - [build](#build) 17 | - [test](#test) 18 | - [coveralls](#coveralls) 19 | - [clean](#clean) 20 | - [Changelog](#changelog) 21 | 22 | ## Design Goals 23 | 24 | - Use fewer tools (no yeoman, gulp, bower, etc...) 25 | - Babel 6 with Webpack and Hot Loader 26 | - Fast testing with mocked-out DOM 27 | - Import css files as class names 28 | - Separate [Smart and Dumb](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) components 29 | - No specific implementation of Flux or data fetching patterns 30 | 31 | 32 | ## Getting Started 33 | 34 | ```sh 35 | $ npm install 36 | ``` 37 | 38 | Start the local dev server: 39 | 40 | ```sh 41 | $ npm run server 42 | ``` 43 | 44 | Navigate to **http://localhost:8080/** to view the app. 45 | 46 | ## Commands 47 | 48 | A core philosophy of this skeleton app is to keep the tooling to a minimum. For this reason, you can find all the commands in the `scripts` section of [package.json](package.json). 49 | 50 | ### server 51 | 52 | ```sh 53 | $ npm run server 54 | ``` 55 | 56 | **Input:** `src/main.jsx` 57 | 58 | This leverages [React Hot Loader](https://github.com/gaearon/react-hot-loader) to automatically start a local dev server and refresh file changes on the fly without reloading the page. 59 | 60 | It also automatically includes source maps, allowing you to browse code and set breakpoints on the original ES6 code: 61 | 62 | ### build 63 | 64 | ```sh 65 | $ npm run build 66 | ``` 67 | 68 | **Input:** `src/main.jsx` 69 | 70 | **Output:** `build/app.js` 71 | 72 | Build minified app for production using the [production](http://webpack.github.io/docs/cli.html#production-shortcut-p) shortcut. 73 | 74 | ### test 75 | 76 | ```sh 77 | $ npm test 78 | ``` 79 | 80 | **Input:** `test/main.js` 81 | 82 | **Output:** `coverage/` 83 | 84 | Leverages [ava](https://github.com/sindresorhus/ava) to execute the test suite and generate code coverage reports using [nyc](https://github.com/bcoe/nyc) 85 | 86 | ### coveralls 87 | 88 | ```sh 89 | $ npm run coveralls 90 | ``` 91 | 92 | **Input:** `coverage/lcov.info` 93 | 94 | Sends the code coverage report generated by [nyc](https://github.com/bcoe/nyc) to [Coveralls](http://coveralls.io/). 95 | 96 | ### clean 97 | 98 | ```sh 99 | $ npm run clean 100 | ``` 101 | 102 | **Input:** `build/app.js` 103 | 104 | Removes the compiled app file from build. 105 | 106 | ## [Changelog](CHANGELOG.md) 107 | --------------------------------------------------------------------------------