18 | );
19 | }
20 | }
21 |
22 | export default ListItem;
23 |
--------------------------------------------------------------------------------
/app/components/A/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * A link to a certain page, an anchor tag
3 | */
4 |
5 | import * as React from 'react';
6 |
7 | const styles = require('./styles.module.css');
8 |
9 | interface IAProps {
10 | className?: string;
11 | href?: string;
12 | target?: string;
13 | children?: React.ReactNode;
14 | }
15 |
16 | class A extends React.Component {
17 | public render() {
18 | return(
19 |
20 | );
21 | }
22 | }
23 |
24 |
25 | export default A;
26 |
--------------------------------------------------------------------------------
/internals/templates/selectors.js:
--------------------------------------------------------------------------------
1 | // selectLocationState expects a plain JS object for the routing state
2 | const selectLocationState = () => {
3 | let prevRoutingState;
4 | let prevRoutingStateJS;
5 |
6 | return (state) => {
7 | const routingState = state.get('route'); // or state.route
8 |
9 | if (!routingState.equals(prevRoutingState)) {
10 | prevRoutingState = routingState;
11 | prevRoutingStateJS = routingState.toJS();
12 | }
13 |
14 | return prevRoutingStateJS;
15 | };
16 | };
17 |
18 | export {
19 | selectLocationState,
20 | };
21 |
--------------------------------------------------------------------------------
/app/containers/HomePage/constants.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * HomeConstants
3 | * Each action has a corresponding type, which the reducer knows and picks up on.
4 | * To avoid weird typos between the reducer and the actions, we save them as
5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid
6 | * reducers accidentally picking up actions they shouldn't.
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | export const CHANGE_USERNAME = 'boilerplate/Home/CHANGE_USERNAME';
13 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/reducer.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 | import {
9 | CHANGE_LOCALE,
10 | } from './constants';
11 |
12 | const initialState = fromJS({
13 | locale: 'en',
14 | });
15 |
16 | function languageProviderReducer(state = initialState, action) {
17 | switch (action.type) {
18 | case CHANGE_LOCALE:
19 | return state
20 | .set('locale', action.locale);
21 | default:
22 | return state;
23 | }
24 | }
25 |
26 | export default languageProviderReducer;
27 |
--------------------------------------------------------------------------------
/internals/templates/languageProvider/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 | import {
9 | CHANGE_LOCALE,
10 | } from './constants';
11 |
12 | const initialState = fromJS({
13 | locale: 'en',
14 | });
15 |
16 | function languageProviderReducer(state = initialState, action) {
17 | switch (action.type) {
18 | case CHANGE_LOCALE:
19 | return state
20 | .set('locale', action.locale);
21 | default:
22 | return state;
23 | }
24 | }
25 |
26 | export default languageProviderReducer;
27 |
--------------------------------------------------------------------------------
/internals/testing/test-bundler.js:
--------------------------------------------------------------------------------
1 | // needed for regenerator-runtime
2 | // (ES7 generator support is required by redux-saga)
3 | import 'babel-polyfill';
4 |
5 | // If we need to use Chai, we'll have already chaiEnzyme loaded
6 | import chai from 'chai';
7 | import chaiEnzyme from 'chai-enzyme';
8 | chai.use(chaiEnzyme());
9 |
10 | // Include all .(j|t)sx? files under `app`, except app.ts, reducers.ts, and routes.ts.
11 | // This is for code coverage
12 | const context = require.context('../../app', true, /^^((?!(app|reducers|routes)|.*\.d).)*\.(j|t)sx?$/);
13 | context.keys().forEach(context);
14 |
--------------------------------------------------------------------------------
/app/components/Img/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Img.react.js
4 | *
5 | * Renders an image, enforcing the usage of the alt="" tag
6 | */
7 |
8 | import * as React from 'react';
9 |
10 | // We require the use of src and alt, only enforced by react in dev mode
11 | interface IImgProps {
12 | src: string;
13 | alt: string;
14 | className?: string;
15 | }
16 |
17 | class Img extends React.Component {
18 | public render() {
19 | return (
20 |
21 | );
22 | }
23 | }
24 |
25 | export default Img;
26 |
--------------------------------------------------------------------------------
/app/containers/HomePage/tests/actions.test.ts:
--------------------------------------------------------------------------------
1 | import * as expect from 'expect';
2 |
3 | import {
4 | CHANGE_USERNAME,
5 | } from '../constants';
6 |
7 | import {
8 | changeUsername,
9 | } from '../actions';
10 |
11 | describe('Home Actions', () => {
12 | describe('changeUsername', () => {
13 | it('should return the correct type and the passed name', () => {
14 | const fixture = 'Max';
15 | const expectedResult = {
16 | type: CHANGE_USERNAME,
17 | name: fixture,
18 | };
19 |
20 | expect(changeUsername(fixture)).toEqual(expectedResult);
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/messages.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * LocaleToggle Messages
3 | *
4 | * This contains all the text for the LanguageToggle component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 | import { appLocales } from '../../i18n';
8 |
9 | export function getLocaleMessages(locales) {
10 | return locales.reduce((messages, locale) =>
11 | Object.assign(messages, {
12 | [locale]: {
13 | id: `app.components.LocaleToggle.${locale}`,
14 | defaultMessage: `${locale}`,
15 | },
16 | }), {});
17 | }
18 |
19 | export default defineMessages(
20 | getLocaleMessages(appLocales),
21 | );
22 |
--------------------------------------------------------------------------------
/app/components/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import A from 'app/components/A';
4 | const styles = require('./styles.module.css');
5 |
6 | class Footer extends React.Component<{}, {}> {
7 | public render() {
8 | return (
9 |
17 | );
18 | }
19 | }
20 |
21 | export default Footer;
22 |
--------------------------------------------------------------------------------
/docs/css/sanitize.md:
--------------------------------------------------------------------------------
1 | # `sanitize.css`
2 |
3 | Sanitize.css makes browsers render elements more in
4 | line with developer expectations (e.g. having the box model set to a cascading
5 | `box-sizing: border-box`) and preferences (its defaults can be individually
6 | overridden).
7 |
8 | It was selected over older projects like `normalize.css` and `reset.css` due
9 | to its greater flexibility and better alignment with CSSNext features like CSS
10 | variables.
11 |
12 | See the [official documentation](https://github.com/10up/sanitize.css) for more
13 | information.
14 |
15 | ---
16 |
17 | _Don't like this feature? [Click here](remove.md)_
18 |
--------------------------------------------------------------------------------
/app/containers/LocaleToggle/tests/messages.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import { getLocaleMessages } from '../messages';
3 |
4 | describe('getLocaleMessages', () => {
5 | it('should create i18n messages for all locales', () => {
6 | const expected = {
7 | en: {
8 | id: 'app.components.LocaleToggle.en',
9 | defaultMessage: 'en',
10 | },
11 | fr: {
12 | id: 'app.components.LocaleToggle.fr',
13 | defaultMessage: 'fr',
14 | },
15 | };
16 |
17 | const actual = getLocaleMessages(['en', 'fr']);
18 |
19 | assert.deepEqual(expected, actual);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:latest", "tslint-react"],
3 | "rules": {
4 | "indent": [ true, "spaces" ],
5 | "quotemark": [ true, "single", "jsx-double" ],
6 | "no-var-requires": false,
7 | "ordered-imports": false,
8 | "no-unused-variable": [false, "react"],
9 | "member-ordering": [false],
10 | "object-literal-sort-keys": false,
11 | "no-shadowed-variable": false,
12 | "no-console": [false],
13 | "max-line-length": [true, 180],
14 | "no-consecutive-blank-lines": [false],
15 | "no-string-literal": false,
16 | "jsx-no-multiline-js": false,
17 | "jsx-boolean-value": false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/tests/reducer.test.ts:
--------------------------------------------------------------------------------
1 | import * as expect from 'expect';
2 | import languageProviderReducer from '../reducer';
3 | import { fromJS } from 'immutable';
4 | import {
5 | CHANGE_LOCALE,
6 | } from '../constants';
7 |
8 | describe('languageProviderReducer', () => {
9 | it('returns the initial state', () => {
10 | expect(languageProviderReducer(undefined, {})).toEqual(fromJS({
11 | locale: 'en',
12 | }));
13 | });
14 |
15 | it('changes the locale', () => {
16 | expect(languageProviderReducer(undefined, { type: CHANGE_LOCALE, locale: 'de' }).toJS()).toEqual({
17 | locale: 'de',
18 | });
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/internals/templates/homePage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage
3 | *
4 | * This is the first thing users see of our App, at the '/' 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 |
14 | export default class HomePage extends React.Component { // eslint-disable-line react/prefer-stateless-function
15 |
16 | render() {
17 | return (
18 |
This is the Homepage!
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/internals/generators/container/selectors.js.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the {{ camelCase name }} state domain
5 | */
6 | const select{{ properCase name }}Domain = () => (state) => state.get('{{ camelCase name }}');
7 |
8 | /**
9 | * Other specific selectors
10 | */
11 |
12 |
13 | /**
14 | * Default selector used by {{ properCase name }}
15 | */
16 |
17 | const select{{ properCase name }} = () => createSelector(
18 | select{{ properCase name }}Domain(),
19 | (substate) => substate.toJS()
20 | );
21 |
22 | export default select{{ properCase name }};
23 | export {
24 | select{{ properCase name }}Domain,
25 | };
26 |
--------------------------------------------------------------------------------
/internals/templates/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 |
14 | export default class NotFound extends React.Component { // eslint-disable-line react/prefer-stateless-function
15 |
16 | render() {
17 | return (
18 |
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 necessity 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/components/List/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import * as expect from 'expect';
2 | import { mount } from 'enzyme';
3 | import * as React from 'react';
4 |
5 | import List from 'app/components/List';
6 | import ListItem from 'app/components/ListItem';
7 |
8 | describe('', () => {
9 | it('should render the component if no items are passed', () => {
10 | const renderedComponent = mount(
11 | ,
12 | );
13 | expect(renderedComponent.find(ListItem)).toExist();
14 | });
15 |
16 | it('should render the items', () => {
17 | const items = [
18 | 'Hello',
19 | 'World',
20 | ];
21 | const renderedComponent = mount(
22 | ,
23 | );
24 | expect(renderedComponent.containsAllMatchingElements(items.map((item) => ))).toExist();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/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.tsx:
--------------------------------------------------------------------------------
1 | import ToggleOption from '../index';
2 |
3 | import * as expect from 'expect';
4 | import { shallow } from 'enzyme';
5 | import { IntlProvider, defineMessages } from 'react-intl';
6 | import * as 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/components/Footer/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import * as expect from 'expect';
2 | import { shallow } from 'enzyme';
3 | import * as React from 'react';
4 |
5 | import Footer from 'app/components/Footer';
6 | import A from 'app/components/A';
7 |
8 | describe('', () => {
9 | it('should render the copyright notice', () => {
10 | const renderedComponent = shallow(
11 | ,
12 | );
13 | expect(renderedComponent.contains(
14 |
15 |
32 | );
33 | }
34 | }
35 |
36 | export default List;
37 |
--------------------------------------------------------------------------------
/docs/css/remove.md:
--------------------------------------------------------------------------------
1 | ## Removing CSS modules
2 |
3 | To remove this feature from your setup, stop importing `.css` files in your
4 | components and delete the `modules` option from the `css-loader` declaration in
5 | [`webpack.prod.babel.js`](/internals/webpack/webpack.prod.babel.js) and
6 | [`webpack.base.babel.js`](/internals/webpack/webpack.base.babel.js)!
7 |
8 | ## Removing PostCSS
9 |
10 | To remove PostCSS, delete the `postcssPlugins` option and remove all occurences
11 | of the `postcss-loader` from
12 |
13 | - [`webpack.dev.babel.js`](/internals/webpack/webpack.dev.babel.js)
14 | - [`webpack.prod.babel.js`](/internals/webpack/webpack.prod.babel.js)
15 | - [`webpack.base.babel.js`](/internals/webpack/webpack.base.babel.js)
16 |
17 | When that is done - and you've verified that everything is still working - remove
18 | all related dependencies from [`package.json`](/package.json)!
19 |
20 | ## Removing `sanitize.css`
21 |
22 | Delete [lines 44 and 45 in `app.js`](../../app/app.js#L44-L45) and remove it
23 | from the `dependencies` in [`package.json`](../../package.json)!
24 |
--------------------------------------------------------------------------------
/app/components/Toggle/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LocaleToggle
4 | *
5 | */
6 |
7 | import * as React from 'react';
8 |
9 | // import { FormattedMessage } from 'react-intl';
10 | const styles = require('./styles.module.css');
11 | import ToggleOption from '../ToggleOption';
12 |
13 | interface IMessageMap {
14 | [locale: string]: ReactIntl.FormattedMessage.MessageDescriptor;
15 | }
16 |
17 | interface IProps {
18 | values: any[];
19 | messages: IMessageMap;
20 | onToggle?: (e: any) => any;
21 | }
22 |
23 | class Toggle extends React.Component { // eslint-disable-line react/prefer-stateless-function
24 | public render() {
25 | let content = [()];
26 |
27 | // If we have items, render them
28 | if (this.props.values) {
29 | content = this.props.values.map((value) => (
30 |
31 | ));
32 | }
33 |
34 | return (
35 |
38 | );
39 | }
40 | }
41 |
42 | export default Toggle;
43 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Maximilian Stoiber
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.
--------------------------------------------------------------------------------
/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: 6
15 | - nodejs_version: 5
16 | - nodejs_version: 4
17 |
18 | # Install scripts--runs after repo cloning
19 | install:
20 | # Install chrome
21 | - choco install -y googlechrome
22 | # Install the latest stable version of Node
23 | - ps: Install-Product node $env:nodejs_version
24 | - npm -g install npm
25 | - set PATH=%APPDATA%\npm;%PATH%
26 | - npm install
27 |
28 | # Disable automatic builds
29 | build: off
30 |
31 | # Post-install test scripts
32 | test_script:
33 | # Output debugging info
34 | - node --version
35 | - npm --version
36 | # run build and run tests
37 | - npm run build
38 |
39 | # remove, as appveyor doesn't support secure variables on pr builds
40 | # so `COVERALLS_REPO_TOKEN` cannot be set, without hard-coding in this file
41 | #on_success:
42 | #- npm run coveralls
43 |
44 |
--------------------------------------------------------------------------------
/app/containers/FeaturePage/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import * as expect from 'expect';
2 | import { shallow } from 'enzyme';
3 | import * as React from 'react';
4 |
5 | import Button from 'app/components/Button';
6 | import { FormattedMessage } from 'react-intl';
7 | import messages from '../messages';
8 | import { FeaturePage } from '../index';
9 | import H1 from 'app/components/H1';
10 |
11 | describe('', () => {
12 | it('should render its heading', () => {
13 | const renderedComponent = shallow(
14 | ,
15 | );
16 | expect(renderedComponent.contains(
17 |
18 |
19 |
,
20 | )).toEqual(true);
21 | });
22 |
23 | it('should link to "/"', (done) => {
24 | // Spy on the openRoute method of the FeaturePage
25 | const dispatch = (action) => {
26 | expect(action.payload.args).toEqual('/');
27 | done();
28 | };
29 |
30 | const renderedComponent = shallow(
31 | ,
32 | );
33 | const button = renderedComponent.find(Button);
34 | button.prop<() => void>('handleRoute')();
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/app/containers/HomePage/messages.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage Messages
3 | *
4 | * This contains all the text for the HomePage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | startProjectHeader: {
10 | id: 'boilerplate.containers.HomePage.start_project.header',
11 | defaultMessage: 'Start your next react project in seconds',
12 | },
13 | startProjectMessage: {
14 | id: 'boilerplate.containers.HomePage.start_project.message',
15 | defaultMessage: 'A highly scalable, offline-first foundation with the best DX and a focus on performance and best practices',
16 | },
17 | trymeHeader: {
18 | id: 'boilerplate.containers.HomePage.tryme.header',
19 | defaultMessage: 'Try me!',
20 | },
21 | trymeMessage: {
22 | id: 'boilerplate.containers.HomePage.tryme.message',
23 | defaultMessage: 'Show Github repositories by',
24 | },
25 | trymeAtPrefix: {
26 | id: 'boilerplate.containers.HomePage.tryme.atPrefix',
27 | defaultMessage: '@',
28 | },
29 | featuresButton: {
30 | id: 'boilerplate.containers.HomePage.features.Button',
31 | defaultMessage: 'Features',
32 | },
33 | });
34 |
--------------------------------------------------------------------------------
/docs/css/css-modules.md:
--------------------------------------------------------------------------------
1 | # CSS Modules
2 |
3 | With CSS Modules, all class names are locally scoped by default. This means
4 | no more bugs from classname clashes. Being able to compose primitives to build
5 | up behaviour also lets us bring programming best practice to CSS: DRY, reusable,
6 | modular code FTW!
7 |
8 | For a detailed explanation see the
9 | [official documentation](https://github.com/css-modules/css-modules).
10 |
11 | ## Usage
12 |
13 | Write your CSS normally in the `styles.css` file in the component folder.
14 |
15 | ```css
16 | /* styles.css */
17 |
18 | .saveBtn {
19 | composes: btn from '../components/btn'; // Yay for composition!
20 |
21 | background-color: green;
22 | color: white;
23 | }
24 | ```
25 |
26 | Then `import` the CSS file in your component JavaScript file, and reference the
27 | class name in the `className` prop.
28 |
29 | ```javascript
30 | // index.js
31 |
32 | import styles from './styles.css';
33 |
34 | // ...inside the render()...
35 |
36 | return (
37 |
40 | );
41 | ```
42 |
43 | ---
44 |
45 | _Don't like this feature? [Click here](remove.md)_
46 |
--------------------------------------------------------------------------------
/app/components/IssueIcon/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IIssueIconProps {
4 | className?: string;
5 | }
6 |
7 | class IssueIcon extends React.Component {
9 | public render() {
10 | const d = [
11 | 'M7',
12 | '2.3c3.14',
13 | '0',
14 | '5.7',
15 | '2.56',
16 | '5.7',
17 | '5.7S10.14',
18 | '13.7',
19 | '7',
20 | '13.7',
21 | '1.3',
22 | '11.14',
23 | '1.3',
24 | '8s2.56-5.7',
25 | '5.7-5.7m0-1.3C3.14',
26 | '1',
27 | '0',
28 | '4.14',
29 | '0',
30 | '8s3.14',
31 | '7',
32 | '7',
33 | '7',
34 | '7-3.14',
35 | '7-7S10.86',
36 | '1',
37 | '7',
38 | '1z',
39 | 'm1',
40 | '3H6v5h2V4z',
41 | 'm0',
42 | '6H6v2h2V10z',
43 | ].join(' ');
44 |
45 | return (
46 |
55 | );
56 | }
57 | }
58 |
59 | export default IssueIcon;
60 |
--------------------------------------------------------------------------------
/app/containers/LanguageProvider/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider
4 | *
5 | * this component connects the redux state language locale to the
6 | * IntlProvider component and i18n messages (loaded from `app/translations`)
7 | */
8 |
9 | import * as React from 'react';
10 | import { connect } from 'react-redux';
11 | import { createStructuredSelector } from 'reselect';
12 | import { IntlProvider } from 'react-intl';
13 | import { selectLocale } from './selectors';
14 |
15 | interface IProps {
16 | locale?: string;
17 | messages: { [locale: string]: { [id: string]: string; }; };
18 | children?: React.ReactNode;
19 | }
20 |
21 | export class LanguageProvider extends React.Component { // eslint-disable-line react/prefer-stateless-function
22 | public render() {
23 | return (
24 |
25 | {React.Children.only(this.props.children)}
26 |
27 | );
28 | }
29 | }
30 |
31 | const mapStateToProps = createStructuredSelector({
32 | locale: selectLocale(),
33 | });
34 |
35 | export default connect<{}, {}, IProps>(mapStateToProps)(LanguageProvider);
36 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 = require('./webpack.base.babel')({
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 |
--------------------------------------------------------------------------------
/docs/css/postcss.md:
--------------------------------------------------------------------------------
1 | # PostCSS
2 |
3 | PostCSS is a modular CSS preprocessor based on JavaScript. It comes pre-
4 | configured with the plugins listed below.
5 |
6 | See the [official documentation](https://github.com/postcss/postcss) for more
7 | information!
8 |
9 | ## Plugins
10 |
11 | This boilerplate bundles a few of the most useful PostCSS plugins by default:
12 |
13 | - [`postcss-focus`](https://github.com/postcss/postcss-focus): Adds a `:focus`
14 | selector to every `:hover` selector for keyboard accessibility.
15 | - [`autoprefixer`](https://github.com/postcss/autoprefixer): Prefixes your CSS
16 | automatically for the last two versions of all major browsers and IE10+.
17 | - [`cssnext`](https://github.com/moox/postcss-cssnext): Use tomorrow's CSS
18 | features today. Transpiles CSS4 features down to CSS3.
19 | - [`cssnano`](https://github.com/ben-eb/cssnano): Optimizes your CSS file. For a
20 | full list of optimizations check [the offical website](http://cssnano.co/optimisations/).
21 |
22 | For more awesome features that the PostCSS ecosystem offers, check out the
23 | comprehensive, fully-searchable catalog of available plugins at [postcss.parts](http://postcss.parts).
24 |
25 | ---
26 |
27 | _Don't like this feature? [Click here](remove.md)_
28 |
--------------------------------------------------------------------------------
/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/LanguageProvider/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import LanguageProvider from '../index';
2 |
3 | import * as expect from 'expect';
4 | import { shallow } from 'enzyme';
5 | import { FormattedMessage, defineMessages } from 'react-intl';
6 | import configureStore from '../../../store';
7 | import * as React from 'react';
8 | import { Provider } from 'react-redux';
9 | import { browserHistory } from 'react-router';
10 | import { translationMessages } 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 |
--------------------------------------------------------------------------------
/server/logger.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import * as chalk from 'chalk';
4 | import * as ip from 'ip';
5 |
6 | const divider = chalk.gray('\n-----------------------------------');
7 |
8 | /**
9 | * Logger middleware, you can customize it to make messages more personal
10 | */
11 | export class Logger {
12 |
13 | // Called whenever there's an error on the server we want to print
14 | public static error(err) {
15 | console.error(chalk.red(err));
16 | }
17 |
18 | // Called when express.js app starts on given port w/o errors
19 | public static appStarted(port: number, tunnelStarted?: string) {
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 | export default Logger;
38 |
--------------------------------------------------------------------------------
/internals/templates/languageProvider/languageProvider.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider
4 | *
5 | * this component connects the redux state language locale to the
6 | * IntlProvider component and i18n messages (loaded from `app/translations`)
7 | */
8 |
9 | import React from 'react';
10 | import { connect } from 'react-redux';
11 | import { createSelector } from 'reselect';
12 | import { IntlProvider } from 'react-intl';
13 | import { selectLocale } from './selectors';
14 |
15 | export class LanguageProvider extends React.Component { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | return (
18 |
19 | {React.Children.only(this.props.children)}
20 |
21 | );
22 | }
23 | }
24 |
25 | LanguageProvider.propTypes = {
26 | locale: React.PropTypes.string,
27 | messages: React.PropTypes.object,
28 | children: React.PropTypes.element.isRequired,
29 | };
30 |
31 |
32 | const mapStateToProps = createSelector(
33 | selectLocale(),
34 | (locale) => ({ locale })
35 | );
36 |
37 | function mapDispatchToProps(dispatch) {
38 | return {
39 | dispatch,
40 | };
41 | }
42 |
43 | export default connect(mapStateToProps, mapDispatchToProps)(LanguageProvider);
44 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/index.tsx:
--------------------------------------------------------------------------------
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 * as React from 'react';
8 | import { connect } from 'react-redux';
9 | import { push, RouterAction} from 'react-router-redux';
10 |
11 | import messages from './messages';
12 | import { FormattedMessage } from 'react-intl';
13 | import Button from 'app/components/Button';
14 | import H1 from 'app/components/H1';
15 |
16 | interface INotFoundProps {
17 | dispatch?: (action: RouterAction) => void;
18 | }
19 |
20 | export class NotFound extends React.Component {
21 |
22 | constructor(props) {
23 | super(props);
24 | this.redirect = this.redirect.bind(this);
25 | }
26 |
27 | private redirect() {
28 | this.props.dispatch(push('/'));
29 | }
30 |
31 | public render() {
32 | return (
33 |
34 |
28 | );
29 | }
30 | }
31 |
32 | const mapStateToProps = createSelector(
33 | selectLocale(),
34 | (locale) => ({ locale }),
35 | );
36 |
37 | export function mapDispatchToProps(dispatch) {
38 | return {
39 | onLocaleToggle: (evt) => dispatch(changeLocale(evt.target.value)),
40 | dispatch,
41 | };
42 | }
43 |
44 | export default connect(mapStateToProps, mapDispatchToProps)(LocaleToggle as any); // TODO: fix when using normal import for `connect`
45 |
--------------------------------------------------------------------------------
/docs/general/deployment.md:
--------------------------------------------------------------------------------
1 | # Deployment
2 |
3 | ## Heroku
4 |
5 | ### Easy 5-Step Deployment Process
6 |
7 | *Step 1:* Create a Procfile with the following line: `web: npm run start:prod`. We are doing this because heroku runs `npm run start` by default, so we need this setting to override the default run command.
8 |
9 | *Step 2:* Install heroku's buildpack on your heroku app by running the following command: `heroku buildpacks:set https://github.com/heroku/heroku-buildpack-nodejs#v90 -a [your app name]`. Make sure to replace `#v90` with whatever the latest buildpack is which you can [find here](https://github.com/heroku/heroku-buildpack-nodejs/releases).
10 |
11 | *Step 3:* Add this line to your Package.json file in the scripts area: `"postinstall": "npm run build:clean",`. This is because Heroku runs this as part of their build process (more of which you can [read about here](https://devcenter.heroku.com/articles/nodejs-support#build-behavior)).
12 |
13 | *Step 4:* Run `heroku config:set NPM_CONFIG_PRODUCTION=false` so that Heroku can compile the NPM Modules included in your devDependencies (since many of these packages are required for the build process).
14 |
15 | *Step 5:* Follow the standard Heroku deploy process at this point:
16 |
17 | 1. `git add .`
18 | 2. `git commit -m 'Made some epic changes as per usual'`
19 | 3. `git push heroku master`
20 |
--------------------------------------------------------------------------------
/docs/css/sass.md:
--------------------------------------------------------------------------------
1 | # Can I use Sass with this boilerplate?
2 |
3 | Yes, although we advise against it and **do not support this**. We selected
4 | PostCSS over Sass because its approach is more powerful: instead of trying to
5 | give a styling language programmatic abilities, it pulls logic and configuration
6 | out into JS where we believe those features belong.
7 |
8 | As an alternative, consider installing a PostCSS plugin called [`PreCSS`](https://github.com/jonathantneal/precss):
9 | it lets you use familiar syntax - $variables, nesting, mixins, etc. - but retain
10 | the advantages (speed, memory efficiency, extensibility, etc) of PostCSS.
11 |
12 | If you _really_ still want (or need) to use Sass then...
13 |
14 | 1. Change `internals/webpack/webpack.base.babel.js` so that line 22 reads
15 | ```JavaScript
16 | test: /\.s?css$/,
17 | ```
18 |
19 | This means that both `.scss` and `.css` will be picked up by the compiler
20 |
21 | 1. Update each of
22 |
23 | - `internals/webpack/webpack.dev.babel.js`
24 | - `internals/webpack/webpack.prod.babel.js`
25 |
26 | changing the config option for cssLoaders to
27 |
28 | ```JavaScript
29 | cssLoaders: 'style-loader!css-loader?modules&importLoaders=1&sourceMap!postcss-loader!sass-loader',
30 | ```
31 |
32 | Then run `npm i -D sass-loader`
33 |
34 | ...and you should be good to go!
35 |
--------------------------------------------------------------------------------
/docs/js/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript
2 |
3 | ## State management
4 |
5 | This boilerplate manages application state using [Redux](redux.md), makes it
6 | immutable with [`ImmutableJS`](immutablejs.md) and keeps access performant
7 | via [`reselect`](reselect.md).
8 |
9 | For managing asynchronous flows (e.g. logging in) we use [`redux-saga`](redux-saga.md).
10 |
11 | For routing, we use [`react-router` in combination with `react-router-redux`](routing.md).
12 |
13 | We include a generator for components, containers, sagas, routes and selectors.
14 | Run `npm run generate` to choose from the available generators, and automatically
15 | add new parts of your application!
16 |
17 | > Note: If you want to skip the generator selection process,
18 | `npm run generate ` also works. (e.g. `npm run generate route`)
19 |
20 | ### Learn more
21 |
22 | - [Redux](redux.md)
23 | - [ImmutableJS](immutablejs.md)
24 | - [reselect](reselect.md)
25 | - [redux-saga](redux-saga.md)
26 | - [routing](routing.md)
27 |
28 | ## Architecture: `components` and `containers`
29 |
30 | We adopted a split between stateless, reusable components called (wait for it...)
31 | `components` and stateful parent components called `containers`.
32 |
33 | ### Learn more
34 |
35 | See [this article](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
36 | by Dan Abramov for a great introduction to this approach.
37 |
--------------------------------------------------------------------------------
/app/components/Img/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import Img from 'app/components/Img';
2 |
3 | import { expect } from 'chai';
4 | import { shallow } from 'enzyme';
5 | import * as React from 'react';
6 |
7 | const src = 'test.png';
8 | const alt = 'test';
9 | const renderComponent = (props = {}) => shallow(
10 | ,
11 | );
12 |
13 | describe('', () => {
14 | it('should render an tag', () => {
15 | const renderedComponent = renderComponent();
16 | expect(renderedComponent).to.have.tagName('img');
17 | });
18 |
19 | it('should have an src attribute', () => {
20 | const renderedComponent = renderComponent();
21 | expect(renderedComponent).to.have.attr('src', src);
22 | });
23 |
24 | it('should have an alt attribute', () => {
25 | const renderedComponent = renderComponent();
26 | expect(renderedComponent).to.have.attr('alt', alt);
27 | });
28 |
29 | it('should adopt a className attribute', () => {
30 | const className = 'test';
31 | const renderedComponent = renderComponent({ className });
32 | expect(renderedComponent).to.have.attr('class', className);
33 | });
34 |
35 | it('should not adopt a srcset attribute', () => {
36 | const srcset = 'test-HD.png 2x';
37 | const renderedComponent = renderComponent({ srcset });
38 | expect(renderedComponent).to.not.have.attr('srcset', srcset);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/app/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Button.react.js
4 | *
5 | * A common button, if you pass it a prop "route" it'll render a link to a react-router route
6 | * otherwise it'll render a link with an onclick
7 | */
8 |
9 | import * as React from 'react';
10 |
11 | const styles = require('./styles.module.css');
12 |
13 | interface IButtonProps extends React.ClassAttributes