29 | );
30 | }
31 | }
32 |
33 | export default {{ properCase name }};
34 |
--------------------------------------------------------------------------------
/internals/templates/notFoundPage/notFoundPage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | *
6 | * NOTE: while this component should technically be a stateless functional
7 | * component (SFC), hot reloading does not currently support SFCs. If hot
8 | * reloading is not a neccessity for you then you can refactor it and remove
9 | * the linting exception.
10 | */
11 |
12 | import React from 'react';
13 | import { FormattedMessage } from 'react-intl';
14 | import messages from './messages';
15 |
16 | export default class NotFound extends React.Component { // eslint-disable-line react/prefer-stateless-function
17 |
18 | render() {
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './styles.css';
4 |
5 | function List(props) {
6 | const ComponentToRender = props.component;
7 | let content = ();
8 |
9 | // If we have items, render them
10 | if (props.items) {
11 | content = props.items.map((item, index) => (
12 |
13 | ));
14 | } else {
15 | // Otherwise render a single component
16 | content = ();
17 | }
18 |
19 | return (
20 |
21 |
22 | {content}
23 |
24 |
25 | );
26 | }
27 |
28 | List.propTypes = {
29 | component: React.PropTypes.func.isRequired,
30 | items: React.PropTypes.array,
31 | };
32 |
33 | export default List;
34 |
--------------------------------------------------------------------------------
/docs/general/gotchas.md:
--------------------------------------------------------------------------------
1 | # Gotchas
2 |
3 | These are some things to be aware of when using this boilerplate.
4 |
5 | ## Special images in HTML files
6 |
7 | If you specify your images in the `.html` files using the `` tag, everything
8 | will work fine. The problem comes up if you try to include images using anything
9 | except that tag, like meta tags:
10 |
11 | ```HTML
12 |
13 | ```
14 |
15 | The webpack `html-loader` does not recognise this as an image file and will not
16 | transfer the image to the build folder. To get webpack to transfer them, you
17 | have to import them with the file loader in your JavaScript somewhere, e.g.:
18 |
19 | ```JavaScript
20 | import 'file?name=[name].[ext]!../img/yourimg.png';
21 | ```
22 |
23 | Then webpack will correctly transfer the image to the build folder.
24 |
--------------------------------------------------------------------------------
/internals/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | React.js Boilerplate
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/components/ToggleOption/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import ToggleOption from '../index';
2 |
3 | import expect from 'expect';
4 | import { shallow } from 'enzyme';
5 | import { IntlProvider, defineMessages } from 'react-intl';
6 | import React from 'react';
7 |
8 | describe('', () => {
9 | it('should render default language messages', () => {
10 | const defaultEnMessage = 'someContent';
11 | const message = defineMessages({
12 | enMessage: {
13 | id: 'app.components.LocaleToggle.en',
14 | defaultMessage: defaultEnMessage,
15 | },
16 | });
17 | const renderedComponent = shallow(
18 |
19 |
20 |
21 | );
22 | expect(renderedComponent.contains()).toEqual(true);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 | import expect from 'expect';
3 |
4 | import {
5 | selectHome,
6 | selectUsername,
7 | } from '../selectors';
8 |
9 | describe('selectHome', () => {
10 | const homeSelector = selectHome();
11 | it('should select the home state', () => {
12 | const homeState = fromJS({
13 | userData: {},
14 | });
15 | const mockedState = fromJS({
16 | home: homeState,
17 | });
18 | expect(homeSelector(mockedState)).toEqual(homeState);
19 | });
20 | });
21 |
22 | describe('selectUsername', () => {
23 | const usernameSelector = selectUsername();
24 | it('should select the username', () => {
25 | const username = 'mxstbr';
26 | const mockedState = fromJS({
27 | home: {
28 | username,
29 | },
30 | });
31 | expect(usernameSelector(mockedState)).toEqual(username);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/internals/scripts/analyze.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var shelljs = require('shelljs');
4 | var animateProgress = require('./helpers/progress');
5 | var chalk = require('chalk');
6 | var addCheckMark = require('./helpers/checkmark');
7 |
8 | var progress = animateProgress('Generating stats');
9 |
10 | // Generate stats.json file with webpack
11 | shelljs.exec(
12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json',
13 | addCheckMark.bind(null, callback) // Output a checkmark on completion
14 | );
15 |
16 | // Called after webpack has finished generating the stats.json file
17 | function callback() {
18 | clearInterval(progress);
19 | process.stdout.write(
20 | '\n\nOpen ' + chalk.magenta('http://webpack.github.io/analyse/') + ' in your browser and upload the stats.json file!' +
21 | chalk.blue('\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n')
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/app/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import messages from './messages';
4 | import A from 'components/A';
5 | import styles from './styles.css';
6 | import { FormattedMessage } from 'react-intl';
7 | import LocaleToggle from 'containers/LocaleToggle';
8 |
9 | function Footer() {
10 | return (
11 |
31 | );
32 | }
33 |
34 | export default Footer;
35 |
--------------------------------------------------------------------------------
/app/containers/App/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import { shallow } from 'enzyme';
3 | import React from 'react';
4 |
5 | import App from '../index';
6 | import Footer from 'components/Footer';
7 |
8 | describe('', () => {
9 | it('should render the logo', () => {
10 | const renderedComponent = shallow(
11 |
12 | );
13 | expect(renderedComponent.find('Img').length).toEqual(1);
14 | });
15 |
16 | it('should render its children', () => {
17 | const children = (
Test
);
18 | const renderedComponent = shallow(
19 |
20 | {children}
21 |
22 | );
23 | expect(renderedComponent.contains(children)).toEqual(true);
24 | });
25 |
26 | it('should render the footer', () => {
27 | const renderedComponent = shallow(
28 |
29 | );
30 | expect(renderedComponent.find(Footer).length).toEqual(1);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/app/components/Toggle/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * LocaleToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 |
9 | // import { FormattedMessage } from 'react-intl';
10 | import styles from './styles.css';
11 | import ToggleOption from '../ToggleOption';
12 |
13 | function Toggle(props) { // eslint-disable-line react/prefer-stateless-function
14 | let content = ();
15 |
16 | // If we have items, render them
17 | if (props.values) {
18 | content = props.values.map((value) => (
19 |
20 | ));
21 | }
22 |
23 | return (
24 |
27 | );
28 | }
29 |
30 | Toggle.propTypes = {
31 | onToggle: React.PropTypes.func,
32 | values: React.PropTypes.array,
33 | messages: React.PropTypes.object,
34 | };
35 |
36 | export default Toggle;
37 |
--------------------------------------------------------------------------------
/app/containers/HomePage/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomeReducer
3 | *
4 | * The reducer takes care of our data. Using actions, we can change our
5 | * application state.
6 | * To add a new action, add it to the switch statement in the reducer function
7 | *
8 | * Example:
9 | * case YOUR_ACTION_CONSTANT:
10 | * return state.set('yourStateVariable', true);
11 | */
12 |
13 | import {
14 | CHANGE_USERNAME,
15 | } from './constants';
16 | import { fromJS } from 'immutable';
17 |
18 | // The initial state of the App
19 | const initialState = fromJS({
20 | username: '',
21 | });
22 |
23 | function homeReducer(state = initialState, action) {
24 | switch (action.type) {
25 | case CHANGE_USERNAME:
26 |
27 | // Delete prefixed '@' from the github username
28 | return state
29 | .set('username', action.name.replace(/@/gi, ''));
30 | default:
31 | return state;
32 | }
33 | }
34 |
35 | export default homeReducer;
36 |
--------------------------------------------------------------------------------
/internals/generators/route/routeWithReducer.hbs:
--------------------------------------------------------------------------------
1 | {
2 | path: '{{ path }}',
3 | name: '{{ camelCase component }}',
4 | getComponent(nextState, cb) {
5 | const importModules = Promise.all([
6 | System.import('containers/{{ properCase component }}/reducer'),
7 | {{#if useSagas}}
8 | System.import('containers/{{ properCase component }}/sagas'),
9 | {{/if}}
10 | System.import('containers/{{ properCase component }}'),
11 | ]);
12 |
13 | const renderRoute = loadModule(cb);
14 |
15 | importModules.then(([reducer,{{#if useSagas}} sagas,{{/if}} component]) => {
16 | injectReducer('{{ camelCase component }}', reducer.default);
17 | {{#if useSagas}}
18 | injectSagas(sagas.default);
19 | {{/if}}
20 | renderRoute(component);
21 | });
22 |
23 | importModules.catch(errorLoading);
24 | },
25 | },$1
26 |
--------------------------------------------------------------------------------
/internals/templates/appContainer.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App.react.js
4 | *
5 | * This component is the skeleton around the actual pages, and should only
6 | * contain code that should be seen on all pages. (e.g. navigation bar)
7 | *
8 | * NOTE: while this component should technically be a stateless functional
9 | * component (SFC), hot reloading does not currently support SFCs. If hot
10 | * reloading is not a neccessity for you then you can refactor it and remove
11 | * the linting exception.
12 | */
13 |
14 | import React from 'react';
15 |
16 | import styles from './styles.css';
17 |
18 | export default class App extends React.Component { // eslint-disable-line react/prefer-stateless-function
19 |
20 | static propTypes = {
21 | children: React.PropTypes.node,
22 | };
23 |
24 | render() {
25 | return (
26 |
36 |
37 | )).toEqual(true);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/internals/webpack/webpack.dll.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WEBPACK DLL GENERATOR
3 | *
4 | * This profile is used to cache webpack's module
5 | * contexts for external library and framework type
6 | * dependencies which will usually not change often enough
7 | * to warrant building them from scratch every time we use
8 | * the webpack process.
9 | */
10 |
11 | const { join } = require('path');
12 | const defaults = require('lodash/defaultsDeep');
13 | const webpack = require('webpack');
14 | const pkg = require(join(process.cwd(), 'package.json'));
15 | const dllPlugin = require('../config').dllPlugin;
16 |
17 | if (!pkg.dllPlugin) { process.exit(0); }
18 |
19 | const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults);
20 | const outputPath = join(process.cwd(), dllConfig.path);
21 |
22 | module.exports = {
23 | context: process.cwd(),
24 | entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg),
25 | devtool: 'eval',
26 | output: {
27 | filename: '[name].dll.js',
28 | path: outputPath,
29 | library: '[name]',
30 | },
31 | plugins: [
32 | new webpack.DllPlugin({ name: '[name]', path: join(outputPath, '[name].json') }), // eslint-disable-line no-new
33 | ],
34 | };
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # React Boilerplate
2 |
3 | Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved.
4 |
5 | Please direct redux-saga related questions to stack overflow:
6 | http://stackoverflow.com/questions/tagged/redux-saga
7 |
8 | For questions related to the boilerplate itself, you can also find answers on our gitter chat:
9 | https://gitter.im/mxstbr/react-boilerplate
10 |
11 | **Before opening a new issue, you may find an answer in already closed issues**:
12 | https://github.com/mxstbr/react-boilerplate/issues?q=is%3Aissue+is%3Aclosed
13 |
14 | ## Issue Type
15 |
16 | - [ ] Bug (https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md#bug-reports)
17 | - [ ] Feature (https://github.com/mxstbr/react-boilerplate/blob/master/.github/CONTRIBUTING.md#feature-requests)
18 |
19 | ## Description
20 |
21 | (Add images if possible)
22 |
23 | ## Steps to reproduce
24 |
25 | (Add link to a demo on https://jsfiddle.net or similar if possible)
26 |
27 | # Versions
28 |
29 | - Node/NPM:
30 | - Browser:
31 |
--------------------------------------------------------------------------------
/server/logger.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const chalk = require('chalk');
4 | const ip = require('ip');
5 |
6 | const divider = chalk.gray('\n-----------------------------------');
7 |
8 | /**
9 | * Logger middleware, you can customize it to make messages more personal
10 | */
11 | const logger = {
12 |
13 | // Called whenever there's an error on the server we want to print
14 | error: err => {
15 | console.error(chalk.red(err));
16 | },
17 |
18 | // Called when express.js app starts on given port w/o errors
19 | appStarted: (port, tunnelStarted) => {
20 | console.log(`Server started ${chalk.green('✓')}`);
21 |
22 | // If the tunnel started, log that and the URL it's available at
23 | if (tunnelStarted) {
24 | console.log(`Tunnel initialised ${chalk.green('✓')}`);
25 | }
26 |
27 | console.log(`
28 | ${chalk.bold('Access URLs:')}${divider}
29 | Localhost: ${chalk.magenta(`http://localhost:${port}`)}
30 | LAN: ${chalk.magenta(`http://${ip.address()}:${port}`) +
31 | (tunnelStarted ? `\n Proxy: ${chalk.magenta(tunnelStarted)}` : '')}${divider}
32 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}
33 | `);
34 | },
35 | };
36 |
37 | module.exports = logger;
38 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # http://www.appveyor.com/docs/appveyor-yml
2 |
3 | # Set build version format here instead of in the admin panel
4 | version: "{build}"
5 |
6 | # Do not build on gh tags
7 | skip_tags: true
8 |
9 | # Test against these versions of Node.js
10 | environment:
11 |
12 | matrix:
13 | # Node versions to run
14 | - nodejs_version: "5.0"
15 |
16 | # Fix line endings in Windows. (runs before repo cloning)
17 | init:
18 | - git config --global core.autocrlf input
19 |
20 | # Install scripts--runs after repo cloning
21 | install:
22 | # Install chrome
23 | - choco install -y googlechrome
24 | # Install the latest stable version of Node
25 | - ps: Install-Product node $env:nodejs_version
26 | - npm -g install npm
27 | - set PATH=%APPDATA%\npm;%PATH%
28 | - npm install
29 |
30 | # Disable automatic builds
31 | build: off
32 |
33 | # Post-install test scripts
34 | test_script:
35 | # Output debugging info
36 | - node --version
37 | - npm --version
38 | # run build and run tests
39 | - npm run build
40 |
41 | # remove, as appveyor doesn't support secure variables on pr builds
42 | # so `COVERALLS_REPO_TOKEN` cannot be set, without hard-coding in this file
43 | #on_success:
44 | #- npm run coveralls
45 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | */
6 |
7 | import React from 'react';
8 | import { connect } from 'react-redux';
9 | import { push } from 'react-router-redux';
10 |
11 | import messages from './messages';
12 | import { FormattedMessage } from 'react-intl';
13 | import Button from 'components/Button';
14 | import H1 from 'components/H1';
15 |
16 | export function NotFound(props) {
17 | return (
18 |
19 |
20 |
21 |
22 |
29 |
30 | );
31 | }
32 |
33 | NotFound.propTypes = {
34 | changeRoute: React.PropTypes.func,
35 | };
36 |
37 | // react-redux stuff
38 | function mapDispatchToProps(dispatch) {
39 | return {
40 | changeRoute: (url) => dispatch(push(url)),
41 | };
42 | }
43 |
44 | // Wrap the component to inject dispatch and state into it
45 | export default connect(null, mapDispatchToProps)(NotFound);
46 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import LanguageProvider from '../index';
2 |
3 | import expect from 'expect';
4 | import { shallow } from 'enzyme';
5 | import { FormattedMessage, defineMessages } from 'react-intl';
6 | import configureStore from '../../../store';
7 | import React from 'react';
8 | import { Provider } from 'react-redux';
9 | import { browserHistory } from 'react-router';
10 | import { translatedMessages } from '../../../i18n';
11 |
12 | describe('', () => {
13 | let store;
14 |
15 | before(() => {
16 | store = configureStore({}, browserHistory);
17 | });
18 |
19 | it('should render the default language messages', () => {
20 | const messages = defineMessages({
21 | someMessage: {
22 | id: 'some.id',
23 | defaultMessage: 'This is some default message',
24 | },
25 | });
26 | const renderedComponent = shallow(
27 |
28 |
29 |
30 |
31 |
32 | );
33 | expect(renderedComponent.contains()).toEqual(true);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/docs/testing/README.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | - [Unit Testing](unit-testing.md)
4 | - [Component Testing](component-testing.md)
5 | - [Remote Testing](remote-testing.md)
6 |
7 | Testing your application is a vital part of serious development. There are a few
8 | things you should test. If you've never done this before start with [unit testing](unit-testing.md).
9 | Move on to [component testing](component-testing.md) when you feel like you
10 | understand that!
11 |
12 | We also support [remote testing](remote-testing.md) your local application,
13 | which is quite awesome, so definitely check that out!
14 |
15 | ## Usage with this boilerplate
16 |
17 | To test your application started with this boilerplate do the following:
18 |
19 | 1. Sprinkle `.test.js` files directly next to the parts of your application you
20 | want to test. (Or in `test/` subdirectories, it doesn't really matter as long
21 | as they are directly next to those parts and end in `.test.js`)
22 |
23 | 1. Write your unit and component tests in those files.
24 |
25 | 1. Run `npm run test` in your terminal and see all the tests pass! (hopefully)
26 |
27 | There are a few more commands related to testing, checkout the [commands documentation](../general/commands.md#testing)
28 | for the full list!
29 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageToggle
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { connect } from 'react-redux';
9 | import { selectLocale } from '../LanguageProvider/selectors';
10 | import { changeLocale } from '../LanguageProvider/actions';
11 | import { appLocales } from '../../i18n';
12 | import { createSelector } from 'reselect';
13 | import styles from './styles.css';
14 | import messages from './messages';
15 | import Toggle from 'components/Toggle';
16 |
17 | export class LocaleToggle extends React.Component { // eslint-disable-line
18 | render() {
19 | return (
20 |