├── .babelrc ├── .cfignore ├── .env ├── .eslintrc ├── .gitignore ├── .react-tools ├── .travis.yml ├── LICENSE ├── Procfile ├── Procfile.dev ├── README.md ├── Staticfile ├── app ├── api │ └── fake_posts_api.js ├── components │ ├── api_page.js │ ├── application.js │ ├── layout.js │ ├── router.js │ ├── todo_adder.js │ ├── todo_item.js │ ├── todo_list.js │ ├── todo_page.js │ ├── use_router.js │ ├── user_create_page.js │ └── user_list_page.js ├── config.js ├── dispatchers │ ├── api_dispatcher.js │ └── main_dispatcher.js ├── index.js ├── index.jsx ├── store.js └── stylesheets │ ├── _layout.scss │ ├── application.scss │ └── postcss.config.js ├── config ├── application.json ├── development.json ├── env.json ├── integration.json ├── production.json ├── test.json └── webpack │ ├── development.js │ ├── production.js │ └── test.js ├── gulpfile.js ├── helpers ├── application_helper.js └── fetch_helper.js ├── index.js ├── manifest.yml ├── package.json ├── server ├── app.js ├── bootstrap.js └── env.js ├── spec ├── app │ ├── api │ │ └── fake_posts_api_spec.js │ ├── components │ │ ├── api_page_spec.js │ │ ├── application_spec.js │ │ ├── router_spec.js │ │ ├── todo_adder_spec.js │ │ ├── todo_item_spec.js │ │ ├── todo_list_spec.js │ │ ├── todo_page_spec.js │ │ ├── use_router_spec.js │ │ ├── user_create_page_spec.js │ │ └── user_list_page_spec.js │ ├── dispatchers │ │ ├── api_dispatcher_spec.js │ │ └── main_dispatcher_spec.js │ ├── index.js │ ├── spec_helper.js │ └── support │ │ ├── dispatcher_matchers.js │ │ ├── mock_router.js │ │ └── mock_router_spec.js ├── factories │ └── user.js ├── integration │ ├── features_spec.js │ ├── helpers │ │ └── webdriver_helper.js │ ├── spec_helper.js │ └── support │ │ ├── jasmine_webdriver.js │ │ └── selenium.js ├── spec_helper.js └── support │ ├── bluebird.js │ ├── deferred.js │ └── mock_fetch.js ├── tasks ├── default.js ├── deploy.js ├── dev_server.js ├── integration.js ├── react_tools.js └── server.js ├── tmp └── .gitkeep └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "plugins": [ 5 | "react-hot-loader/babel" 6 | ] 7 | } 8 | }, 9 | "presets": [["es2015", {"loose": true}], "react", "stage-0"], 10 | "plugins": [ 11 | "add-module-exports", 12 | "transform-object-assign", 13 | "transform-react-display-name" 14 | ] 15 | } -------------------------------------------------------------------------------- /.cfignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | /app/* 4 | /config/* 5 | /lib/* 6 | /logs/* 7 | /node_modules 8 | /scripts/* 9 | /server/* 10 | /spec/* 11 | /tasks/* 12 | /tmp/* 13 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | { 2 | "NODE_ENV": "development", 3 | "PORT": 3000, 4 | "API_PORT": 3001 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "browser": true, 5 | "phantomjs": true, 6 | "node": true, 7 | "jasmine": true 8 | }, 9 | 10 | "ecmaFeatures": { 11 | "modules": true 12 | }, 13 | 14 | "globals": { 15 | "MyReactStarter": true, 16 | "root": true, 17 | "Bluebird": true, 18 | "Dispatcher": true, 19 | "jQuery": true, 20 | "MockPromises": true, 21 | "MockRouter": true, 22 | "React": true, 23 | "ReactDOM": true, 24 | "setProps": true, 25 | "Factory": true, 26 | "$": true, 27 | "click": true, 28 | "describeWithWebdriver": true, 29 | "setValue": true, 30 | "sleep": true, 31 | "visit": true, 32 | "waitForExist": true, 33 | "waitForText": true 34 | }, 35 | 36 | "parser": "babel-eslint", 37 | 38 | "plugins": [ 39 | "react" 40 | ], 41 | 42 | "rules": { 43 | "block-scoped-var": 2, 44 | "camelcase": 0, 45 | "complexity": 2, 46 | "consistent-return": 0, 47 | "curly": 0, 48 | "default-case": 2, 49 | "dot-notation": 2, 50 | "eqeqeq": 2, 51 | "eol-last": 0, 52 | "guard-for-in": 2, 53 | "jsx-quotes": 2, 54 | "no-alert": 2, 55 | "no-caller": 2, 56 | "no-console": 2, 57 | "no-debugger": 2, 58 | "no-div-regex": 2, 59 | "no-else-return": 2, 60 | "no-eq-null": 2, 61 | "no-eval": 2, 62 | "no-extend-native": 2, 63 | "no-extra-bind": 2, 64 | "no-fallthrough": 2, 65 | "no-floating-decimal": 2, 66 | "no-implied-eval": 2, 67 | "no-iterator": 2, 68 | "no-labels": 2, 69 | "no-lone-blocks": 2, 70 | "no-loop-func": 2, 71 | "no-multi-spaces": 2, 72 | "no-multi-str": 2, 73 | "no-native-reassign": 2, 74 | "no-new": 2, 75 | "no-new-func": 2, 76 | "no-new-wrappers": 2, 77 | "no-octal": 2, 78 | "no-octal-escape": 2, 79 | "no-path-concat": 0, 80 | "no-process-env": 0, 81 | "no-proto": 2, 82 | "no-redeclare": 2, 83 | "no-return-assign": 0, 84 | "no-script-url": 2, 85 | "no-self-compare": 2, 86 | "no-sequences": 0, 87 | "no-shadow": 0, 88 | "no-undef": 2, 89 | "no-underscore-dangle": 0, 90 | "no-unused-expressions": 0, 91 | "no-unused-vars": 2, 92 | "no-var": 2, 93 | "no-void": 2, 94 | "no-warning-comments": 1, 95 | "no-with": 2, 96 | "quotes": [2, "single"], 97 | "radix": 2, 98 | "react/jsx-uses-vars": 2, 99 | "react/jsx-no-undef": 2, 100 | "react/jsx-uses-react": 2, 101 | "react/no-is-mounted": 2, 102 | "react/react-in-jsx-scope": 2, 103 | "react/prefer-es6-class": 1, 104 | "react/no-did-mount-set-state": 0, 105 | "react/no-did-update-set-state": 2, 106 | "react/prop-types": [2, {ignore: ['children', 'className', 'id', 'style']}], 107 | "react/self-closing-comp": 2, 108 | "react/sort-comp": 2, 109 | "react/jsx-wrap-multilines": 2, 110 | "semi": 2, 111 | "strict": 0, 112 | "vars-on-top": 0, 113 | "wrap-iife": [2, "inside"], 114 | "yoda": 0 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env.json 2 | .idea 3 | /config/local.json 4 | /dist/* 5 | /node_modules 6 | /public/* 7 | /logs/* 8 | /tmp/* 9 | .DS_Store 10 | npm-debug.log 11 | -------------------------------------------------------------------------------- /.react-tools: -------------------------------------------------------------------------------- 1 | const base = require('pui-react-tools/webpack/base'); 2 | const development = require('pui-react-tools/webpack/development'); 3 | 4 | module.exports = { 5 | webpack: { 6 | base: {...base, entry: {application: './app/index.js'}}, 7 | development: { 8 | entry: { 9 | application: ['react-hot-loader/patch', 'webpack-hot-middleware/client', './app/index.js'] 10 | }, 11 | plugins: development.plugins 12 | }, 13 | integration: { 14 | devtool: 'source-map', 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 6.9.1 3 | before_script: 4 | - "export DISPLAY=:99.0" 5 | - "sh -e /etc/init.d/xvfb start" 6 | - sleep 3 # give xvfb some time to start 7 | script: 8 | - BROWSER=firefox gulp 9 | cache: 10 | yarn: true 11 | directories: 12 | - node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Pivotal Software, Inc. 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 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: gulp s 2 | jasmine: gulp jasmine 3 | assets: PORT=3000 gulp dev-server -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | Use [Create React App](https://github.com/facebook/create-react-app) 4 | 5 | # React Starter 6 | 7 | [![Build Status](https://travis-ci.org/pivotal-cf/react-starter.svg?branch=master)](https://travis-ci.org/pivotal-cf/react-starter) 8 | 9 | React Starter is a todoApp project with much of the tooling in place you would need for a fully-featured React application. 10 | [Click here](http://react-starter.cfapps.io/) to see it in action. 11 | 12 | # Table of Contents 13 | 1. [Getting Started](#getting-started) 14 | 1. [Testing](#testing) 15 | 1. [Linting](#linting) 16 | 1. [Assets](#assets) 17 | 1. [Patterns](#patterns) 18 | 1. [Troubleshooting](#troubleshooting) 19 | 20 | ## Getting Started 21 | 22 | Install gulp: 23 | ```bash 24 | brew install gulp 25 | ``` 26 | 27 | Checkout the project, install dependencies, and start foreman: 28 | ```bash 29 | git clone git@github.com:pivotal-cf/react-starter.git && cd react-starter 30 | npm install 31 | gulp foreman 32 | ``` 33 | 34 | This will start up the development server at [3000](http://localhost:3000) and the Jasmine server at [8888](http://localhost:8888). 35 | The app includes example React architecture, along with Jasmine unit tests and a WebdriverIO integration test. 36 | 37 | ## Deploying 38 | 39 | To deploy to cloud foundry: 40 | 41 | 1. choose a unique name for your application and change `name: react-starter` in `manifest.yml` to your unique name 42 | 1. login to cf, target your org and space 43 | 1. `gulp deploy` 44 | 45 | Note that `cf push` by itself will not work. The `gulp deploy` task will compile your assets and configure the staticfile for the buildpack before doing `cf push` 46 | 47 | ## Testing 48 | 49 | ### Unit Testing 50 | 51 | Any files matching `spec/app/**/*_spec.js` will be run as part of [Jasmine](jasmine.github.io). There are some example tests included in `spec/app/components/`. 52 | 53 | To run the tests headlessly in phantomjs: 54 | ``` 55 | gulp spec-app 56 | ``` 57 | 58 | To run a Jasmine server (on port 8888): 59 | ``` 60 | gulp jasmine 61 | ``` 62 | The jasmine server will watch for file changes and update appropriately. 63 | Note that `gulp foreman` will start a jasmine server for you. 64 | 65 | In general, testing a React component will need the line `require('../spec_helper')` as the first line. 66 | The test will also probably have lines like 67 | ``` 68 | const MyComponent = require('../../../app/components/my_component'); 69 | ReactDom.render(, root) 70 | ``` 71 | where `props` is an object representing the props passed into the React component. 72 | The spec_helper re-creates a div with id="root" (referenced by `root`) where you can render your components. 73 | 74 | Testing the results of rendering is made easier with [jasmine_dom_matchers](https://github.com/charleshansen/jasmine_dom_matchers), 75 | this is where `toHaveText` is defined. 76 | 77 | We have also provided some custom matchers with [pivotal-js-jasmine-matchers](https://github.com/pivotal-cf/pivotal-js/tree/master/packages/pivotal-js-jasmine-matchers). 78 | 79 | #### Factories 80 | 81 | React starter sets up Factories using [Rosie](https://github.com/rosiejs/rosie). 82 | Factories are defined in the `spec/factories` folder. 83 | The easiest way to create a new factory is to create a new file in `spec/factories`. 84 | See `spec/factories/user.js` as an example. 85 | 86 | 87 | ### Integration Testing 88 | 89 | Integration tests use [selenium-standalone](https://github.com/vvo/selenium-standalone) and [WebdriverIO](http://webdriver.io/). 90 | 91 | Selenium requires Java, so make sure this is installed. Run: 92 | ``` 93 | gulp spec-integration 94 | ``` 95 | 96 | This will take any files matching `spec/integration/**/*_spec.js` and run them through Jasmine. 97 | We provide a `describeWithWebdriver` function, inside of which you have access to WebdriverIO functionality. 98 | 99 | WebdriverIO is based on promises. Any time you interact with the browser in any way, this will be asynchronous and return a promise. 100 | To make this more readable, we use `async`/`await` syntax (from EcmaScript 2016) and the `done` callback from Jasmine. 101 | 102 | There are also a number of functions provided in `spec/integration/helpers/webdriver_helper.js`. 103 | 104 | An example integration test is provided at `spec/integration/features_spec.js`. 105 | 106 | ## Linting 107 | 108 | To lint your JavaScript code using [ESLint](http://eslint.org/): 109 | 110 | ``` 111 | gulp lint 112 | ``` 113 | 114 | The linting rules are set in `.eslintrc` 115 | 116 | 117 | ## Assets 118 | 119 | The JavaScript is compiled using [Babel](https://babeljs.io/) and [Webpack](https://webpack.github.io/). 120 | Additional webpack loaders and webpack plugins are used to compile the sass and html. By default, the entry point for your browser JavaScript is `app/index.js`. 121 | 122 | Webpack configurations are in `config/webpack/`. For example, if NODE_ENV is 'production', webpack is configured with `config/webpack/production.js` 123 | 124 | ```bash 125 | NODE_ENV=production gulp assets 126 | ``` 127 | will output `application.js`, `application.css`, and `index.html` into the public folder. 128 | ```bash 129 | NODE_ENV=production gulp assets-config 130 | ``` 131 | will output `config.js` into the public folder. These assets can then be served statically. 132 | 133 | React starter is in development mode if `NODE_ENV=development` or undefined. 134 | In development mode, the express server serves up `index.html`, `application.js` and `application.css`, using `webpack-dev-middleware`. `config.js` is served separately. This uses the webpack config in `config/webpack/development.js` 135 | 136 | ## Patterns 137 | 138 | #### Flux 139 | 140 | We have provided an example flux implementation in this application. 141 | 142 | * A component calls an action 143 | * The action calls the dispatcher 144 | * The corresponding method in the dispatcher updates the global store 145 | 146 | The flux patterns used in React starter have been extracted into [p-flux](https://github.com/pivotal-cf/p-flux). 147 | Look into p-flux documentation for best practices on storing and updating data. 148 | 149 | #### Router 150 | 151 | We have provided an example router in this application. The router is using [Grapnel](https://github.com/bytecipher/grapnel). 152 | 153 | Router callbacks should be responsible for changing the page. 154 | This can be accomplished by storing a page component in the router, as in `app/components/router.js`. 155 | Router callbacks also have access to props and Actions to save route params in the store. 156 | 157 | We recommend having a `setRoute` dispatch event for easy debugging. We have provided an example in `app/dispatchers/main_dispatcher.js`. 158 | 159 | We have also provided a mock router for use in test in `spec/app/support/mock_router.js`. 160 | The mock router is installed in `spec/app/spec_helper.js`. 161 | If you do not mock the router, it will change your browser URL while running Jasmine. 162 | 163 | #### API 164 | 165 | We have provided an example workflow that talks to an api, using the JSONPlaceholder api and `window.fetch`. 166 | Using an api requires asynchronous testing, which can be difficult. 167 | We use [MockPromises](https://github.com/charleshansen/mock-promises) to deal with most of it. 168 | 169 | ## Troubleshooting 170 | 171 | ### node 172 | 173 | React Starter requires: 174 | * Node version 4+ (it may work with older versions of node, but node-sass less likely to install correctly). 175 | * Npm version 3+ 176 | 177 | If either of these is an earlier version, you will likely see errors when you run the code. 178 | If you have installed and then realize you need to change either of these, you will need to `rm -rf node_modules` and `npm install` to make sure dependencies are correctly updated and installed. 179 | 180 | Windows Users: To install node-sass, you will need a C compiler like Visual Studio installed, and probably also Python 2.x 181 | -------------------------------------------------------------------------------- /Staticfile: -------------------------------------------------------------------------------- 1 | pushstate: enabled 2 | 3 | -------------------------------------------------------------------------------- /app/api/fake_posts_api.js: -------------------------------------------------------------------------------- 1 | const {fetchJson} = require('../../helpers/fetch_helper'); 2 | 3 | const apiUrl = 'http://jsonplaceholder.typicode.com'; 4 | 5 | const FakePostsApi = { 6 | fetch() { 7 | return fetchJson(`${apiUrl}/posts`); 8 | } 9 | }; 10 | 11 | module.exports = FakePostsApi; -------------------------------------------------------------------------------- /app/components/api_page.js: -------------------------------------------------------------------------------- 1 | const {Actions} = require('p-flux'); 2 | const React = require('react'); 3 | import PropTypes from 'prop-types'; 4 | 5 | class ApiPage extends React.Component { 6 | static propTypes = { 7 | posts: PropTypes.array 8 | }; 9 | 10 | componentDidMount() { 11 | Actions.fetchPosts(); 12 | } 13 | 14 | render() { 15 | const {posts = []} = this.props; 16 | const titles = posts.map(({title, id}) =>
{title}
); 17 | return ( 18 |
19 |

This page talks to an api

20 |

The api has posts with titles:

21 | {titles} 22 |
23 | ); 24 | } 25 | } 26 | 27 | module.exports = ApiPage; -------------------------------------------------------------------------------- /app/components/application.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | import PropTypes from 'prop-types'; 3 | const {useStore} = require('p-flux'); 4 | const {useRouter} = require('./use_router'); 5 | const Router = require('./router'); 6 | 7 | if (typeof document !== 'undefined') { 8 | require('../stylesheets/application.scss'); 9 | } 10 | 11 | class Application extends React.Component { 12 | static propTypes = { 13 | config: PropTypes.object.isRequired, 14 | store: PropTypes.object.isRequired, 15 | router: PropTypes.oneOfType([PropTypes.object, PropTypes.func]) 16 | }; 17 | 18 | render() { 19 | const {config, store, router} = this.props; 20 | return ( 21 |
22 | {e.preventDefault(); router.navigate('/todoList');}}>Todo List! 23 |
24 | {e.preventDefault(); router.navigate('/apiPage');}}>Page that hits an api 25 |
26 | {e.preventDefault(); router.navigate('/users/new');}}>Create New User 27 |
28 | {e.preventDefault(); router.navigate('/users/list');}}> All Users 29 | 30 |
31 | ); 32 | } 33 | } 34 | 35 | const EnhancedApplication = useStore(useRouter(Application), 36 | { 37 | store: require('../store'), 38 | actions: [], 39 | dispatcherHandlers: [ 40 | require('../dispatchers/main_dispatcher'), 41 | require('../dispatchers/api_dispatcher') 42 | ], 43 | /* eslint-disable no-console */ 44 | onDispatch: (event) => {console.info('dispatching event', event);} 45 | /* eslint-enable no-console */ 46 | } 47 | ); 48 | 49 | module.exports = EnhancedApplication; 50 | -------------------------------------------------------------------------------- /app/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ReactDOMServer from 'react-dom/server'; 4 | 5 | export default function Layout({config, children}) { 6 | const configJs = `window.${config.globalNamespace} = {animation: true, config: ${JSON.stringify(config)}}`; 7 | const metas = Layout.metas.map((props, key) => ); 8 | return ( 9 | 10 | {metas} 11 | 12 |
13 |