├── 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 | [](https://travis-ci.org/pheuter/essential-react)
4 | [](https://coveralls.io/r/pheuter/essential-react)
5 |
6 | ---
7 |
8 | [](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 |
--------------------------------------------------------------------------------