29 | );
30 | }
31 |
32 | export default {{ properCase name }};
33 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/component/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import {{ properCase name }} from '../index';
2 |
3 | import expect from 'expect';
4 | // import { shallow } from 'enzyme';
5 | // import React from 'react';
6 |
7 | describe('<{{ properCase name }} />', () => {
8 | it('Expect to have unit tests specified', () => {
9 | expect(true).toEqual(false);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/actions.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} actions
4 | *
5 | */
6 |
7 | import {
8 | DEFAULT_ACTION,
9 | } from './constants';
10 |
11 | export function defaultAction() {
12 | return {
13 | type: DEFAULT_ACTION,
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/actions.test.js.hbs:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import {
3 | defaultAction,
4 | } from '../actions';
5 | import {
6 | DEFAULT_ACTION,
7 | } from '../constants';
8 |
9 | describe('{{ properCase name }} actions', () => {
10 | describe('Default Action', () => {
11 | it('has a type of DEFAULT_ACTION', () => {
12 | const expected = {
13 | type: DEFAULT_ACTION,
14 | };
15 | expect(defaultAction()).toEqual(expected);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/constants.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} constants
4 | *
5 | */
6 |
7 | export const DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION';
8 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Container Generator
3 | */
4 |
5 | const componentExists = require('../utils/componentExists');
6 |
7 | module.exports = {
8 | description: 'Add a container component',
9 | prompts: [{
10 | type: 'input',
11 | name: 'name',
12 | message: 'What should it be called?',
13 | default: 'Form',
14 | validate: (value) => {
15 | if ((/.+/).test(value)) {
16 | return componentExists(value) ? 'A component or container with this name already exists' : true;
17 | }
18 |
19 | return 'The name is required';
20 | },
21 | }, {
22 | type: 'list',
23 | name: 'component',
24 | message: 'Select a base component:',
25 | default: 'PureComponent',
26 | choices: () => ['PureComponent', 'Component'],
27 | }, {
28 | type: 'confirm',
29 | name: 'wantHeaders',
30 | default: false,
31 | message: 'Do you want headers?',
32 | }, {
33 | type: 'confirm',
34 | name: 'wantActionsAndReducer',
35 | default: true,
36 | message: 'Do you want an actions/constants/selectors/reducer tupel for this container?',
37 | }, {
38 | type: 'confirm',
39 | name: 'wantSagas',
40 | default: true,
41 | message: 'Do you want sagas for asynchronous flows? (e.g. fetching data)',
42 | }, {
43 | type: 'confirm',
44 | name: 'wantMessages',
45 | default: true,
46 | message: 'Do you want i18n messages (i.e. will this component use text)?',
47 | }],
48 | actions: (data) => {
49 | // Generate index.js and index.test.js
50 | const actions = [{
51 | type: 'add',
52 | path: '../../app/containers/{{properCase name}}/index.js',
53 | templateFile: './container/index.js.hbs',
54 | abortOnFail: true,
55 | }, {
56 | type: 'add',
57 | path: '../../app/containers/{{properCase name}}/tests/index.test.js',
58 | templateFile: './container/test.js.hbs',
59 | abortOnFail: true,
60 | }];
61 |
62 | // If component wants messages
63 | if (data.wantMessages) {
64 | actions.push({
65 | type: 'add',
66 | path: '../../app/containers/{{properCase name}}/messages.js',
67 | templateFile: './container/messages.js.hbs',
68 | abortOnFail: true,
69 | });
70 | }
71 |
72 | // If they want actions and a reducer, generate actions.js, constants.js,
73 | // reducer.js and the corresponding tests for actions and the reducer
74 | if (data.wantActionsAndReducer) {
75 | // Actions
76 | actions.push({
77 | type: 'add',
78 | path: '../../app/containers/{{properCase name}}/actions.js',
79 | templateFile: './container/actions.js.hbs',
80 | abortOnFail: true,
81 | });
82 | actions.push({
83 | type: 'add',
84 | path: '../../app/containers/{{properCase name}}/tests/actions.test.js',
85 | templateFile: './container/actions.test.js.hbs',
86 | abortOnFail: true,
87 | });
88 |
89 | // Constants
90 | actions.push({
91 | type: 'add',
92 | path: '../../app/containers/{{properCase name}}/constants.js',
93 | templateFile: './container/constants.js.hbs',
94 | abortOnFail: true,
95 | });
96 |
97 | // Selectors
98 | actions.push({
99 | type: 'add',
100 | path: '../../app/containers/{{properCase name}}/selectors.js',
101 | templateFile: './container/selectors.js.hbs',
102 | abortOnFail: true,
103 | });
104 | actions.push({
105 | type: 'add',
106 | path: '../../app/containers/{{properCase name}}/tests/selectors.test.js',
107 | templateFile: './container/selectors.test.js.hbs',
108 | abortOnFail: true,
109 | });
110 |
111 | // Reducer
112 | actions.push({
113 | type: 'add',
114 | path: '../../app/containers/{{properCase name}}/reducer.js',
115 | templateFile: './container/reducer.js.hbs',
116 | abortOnFail: true,
117 | });
118 | actions.push({
119 | type: 'add',
120 | path: '../../app/containers/{{properCase name}}/tests/reducer.test.js',
121 | templateFile: './container/reducer.test.js.hbs',
122 | abortOnFail: true,
123 | });
124 | }
125 |
126 | // Sagas
127 | if (data.wantSagas) {
128 | actions.push({
129 | type: 'add',
130 | path: '../../app/containers/{{properCase name}}/sagas.js',
131 | templateFile: './container/sagas.js.hbs',
132 | abortOnFail: true,
133 | });
134 | actions.push({
135 | type: 'add',
136 | path: '../../app/containers/{{properCase name}}/tests/sagas.test.js',
137 | templateFile: './container/sagas.test.js.hbs',
138 | abortOnFail: true,
139 | });
140 | }
141 |
142 | return actions;
143 | },
144 | };
145 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/index.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | import { connect } from 'react-redux';
9 | {{#if wantHeaders}}
10 | import Helmet from 'react-helmet';
11 | {{/if}}
12 | {{#if wantActionsAndReducer}}
13 | import select{{properCase name}} from './selectors';
14 | {{/if}}
15 | {{#if wantMessages}}
16 | import { FormattedMessage } from 'react-intl';
17 | import messages from './messages';
18 | {{/if}}
19 | {{#if wantCSS}}
20 | import styles from './styles.css';
21 | {{/if}}
22 |
23 | export class {{ properCase name }} extends React.{{{ component }}} { // eslint-disable-line react/prefer-stateless-function
24 | render() {
25 | return (
26 | {{#if wantCSS}}
27 |
28 | {{else}}
29 |
30 | {{/if}}
31 | {{#if wantHeaders}}
32 |
38 | {{/if}}
39 | {{#if wantMessages}}
40 |
41 | {{/if}}
42 |
43 | );
44 | }
45 | }
46 |
47 | {{#if wantActionsAndReducer}}
48 | const mapStateToProps = select{{properCase name}}();
49 | {{/if}}
50 |
51 | function mapDispatchToProps(dispatch) {
52 | return {
53 | dispatch,
54 | };
55 | }
56 |
57 | {{#if wantActionsAndReducer}}
58 | export default connect(mapStateToProps, mapDispatchToProps)({{ properCase name }});
59 | {{else}}
60 | export default connect(null, mapDispatchToProps)({{ properCase name }});
61 | {{/if}}
62 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/messages.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | * {{properCase name }} Messages
3 | *
4 | * This contains all the text for the {{properCase name }} component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.{{properCase name }}.header',
11 | defaultMessage: 'This is {{properCase name}} container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/reducer.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 | import {
9 | DEFAULT_ACTION,
10 | } from './constants';
11 |
12 | const initialState = fromJS({});
13 |
14 | function {{ camelCase name }}Reducer(state = initialState, action) {
15 | switch (action.type) {
16 | case DEFAULT_ACTION:
17 | return state;
18 | default:
19 | return state;
20 | }
21 | }
22 |
23 | export default {{ camelCase name }}Reducer;
24 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/reducer.test.js.hbs:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import {{ camelCase name }}Reducer from '../reducer';
3 | import { fromJS } from 'immutable';
4 |
5 | describe('{{ camelCase name }}Reducer', () => {
6 | it('returns the initial state', () => {
7 | expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({}));
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/sagas.js.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select } from 'redux-saga/effects';
2 |
3 | // Individual exports for testing
4 | export function* defaultSaga() {
5 | return;
6 | }
7 |
8 | // All sagas to be loaded
9 | export default [
10 | defaultSaga,
11 | ];
12 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/sagas.test.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | * Test sagas
3 | */
4 |
5 | import expect from 'expect';
6 | // import { take, call, put, select } from 'redux-saga/effects';
7 | // import { defaultSaga } from '../sagas';
8 |
9 | // const generator = defaultSaga();
10 |
11 | describe('defaultSaga Saga', () => {
12 | it('Expect to have unit tests specified', () => {
13 | expect(true).toEqual(false);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/FrontEnd/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 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/selectors.test.js.hbs:
--------------------------------------------------------------------------------
1 | // import { select{{ properCase name }}Domain } from '../selectors';
2 | // import { fromJS } from 'immutable';
3 | import expect from 'expect';
4 |
5 | // const selector = select{{ properCase name}}Domain();
6 |
7 | describe('select{{ properCase name }}Domain', () => {
8 | it('Expect to have unit tests specified', () => {
9 | expect('Test case').toEqual(false);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/container/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import { {{ properCase name }} } from '../index';
2 |
3 | import expect from 'expect';
4 | // import { shallow } from 'enzyme';
5 | // import React from 'react';
6 |
7 | describe('<{{ properCase name }} />', () => {
8 | it('Expect to have unit tests specified', () => {
9 | expect(true).toEqual(false);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * generator/index.js
3 | *
4 | * Exports the generators so plop knows them
5 | */
6 |
7 | const fs = require('fs');
8 | const componentGenerator = require('./component/index.js');
9 | const containerGenerator = require('./container/index.js');
10 | const routeGenerator = require('./route/index.js');
11 | const languageGenerator = require('./language/index.js');
12 |
13 | module.exports = (plop) => {
14 | plop.setGenerator('component', componentGenerator);
15 | plop.setGenerator('container', containerGenerator);
16 | plop.setGenerator('route', routeGenerator);
17 | plop.setGenerator('language', languageGenerator);
18 | plop.addHelper('directory', (comp) => {
19 | try {
20 | fs.accessSync(`app/containers/${comp}`, fs.F_OK);
21 | return `containers/${comp}`;
22 | } catch (e) {
23 | return `components/${comp}`;
24 | }
25 | });
26 | plop.addHelper('curly', (object, open) => (open ? '{' : '}'));
27 | };
28 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/add-locale-data.hbs:
--------------------------------------------------------------------------------
1 | $1addLocaleData({{language}}LocaleData);
2 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/app-locale.hbs:
--------------------------------------------------------------------------------
1 | $1 '{{language}}',
2 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/format-translation-messages.hbs:
--------------------------------------------------------------------------------
1 | $1 {{language}}: formatTranslationMessages('{{language}}', {{language}}TranslationMessages),
2 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Language Generator
3 | */
4 | const exec = require('child_process').exec;
5 |
6 | module.exports = {
7 | description: 'Add a language',
8 | prompts: [{
9 | type: 'input',
10 | name: 'language',
11 | message: 'What is the language you want to add i18n support for (e.g. "fr", "de")?',
12 | default: 'fr',
13 | validate: (value) => {
14 | if ((/.+/).test(value) && value.length === 2) {
15 | return true;
16 | }
17 |
18 | return '2 character language specifier is required';
19 | },
20 | }],
21 |
22 | actions: () => {
23 | const actions = [];
24 | actions.push({
25 | type: 'modify',
26 | path: '../../app/i18n.js',
27 | pattern: /('react-intl\/locale-data\/[a-z]+';\n)(?!.*'react-intl\/locale-data\/[a-z]+';)/g,
28 | templateFile: './language/intl-locale-data.hbs',
29 | });
30 | actions.push({
31 | type: 'modify',
32 | path: '../../app/i18n.js',
33 | pattern: /(\s+'[a-z]+',\n)(?!.*\s+'[a-z]+',)/g,
34 | templateFile: './language/app-locale.hbs',
35 | });
36 | actions.push({
37 | type: 'modify',
38 | path: '../../app/i18n.js',
39 | pattern: /(from\s'.\/translations\/[a-z]+.json';\n)(?!.*from\s'.\/translations\/[a-z]+.json';)/g,
40 | templateFile: './language/translation-messages.hbs',
41 | });
42 | actions.push({
43 | type: 'modify',
44 | path: '../../app/i18n.js',
45 | pattern: /(addLocaleData\([a-z]+LocaleData\);\n)(?!.*addLocaleData\([a-z]+LocaleData\);)/g,
46 | templateFile: './language/add-locale-data.hbs',
47 | });
48 | actions.push({
49 | type: 'modify',
50 | path: '../../app/i18n.js',
51 | pattern: /([a-z]+:\sformatTranslationMessages\('[a-z]+',\s[a-z]+TranslationMessages\),\n)(?!.*[a-z]+:\sformatTranslationMessages\('[a-z]+',\s[a-z]+TranslationMessages\),)/g,
52 | templateFile: './language/format-translation-messages.hbs',
53 | });
54 | actions.push({
55 | type: 'add',
56 | path: '../../app/translations/{{language}}.json',
57 | templateFile: './language/translations-json.hbs',
58 | abortOnFail: true,
59 | });
60 | actions.push({
61 | type: 'modify',
62 | path: '../../app/app.js',
63 | pattern: /(System\.import\('intl\/locale-data\/jsonp\/[a-z]+\.js'\),\n)(?!.*System\.import\('intl\/locale-data\/jsonp\/[a-z]+\.js'\),)/g,
64 | templateFile: './language/polyfill-intl-locale.hbs',
65 | });
66 | actions.push(
67 | () => {
68 | const cmd = 'npm run extract-intl';
69 | exec(cmd, (err, result, stderr) => {
70 | if (err || stderr) {
71 | throw err || stderr;
72 | }
73 | process.stdout.write(result);
74 | });
75 | }
76 | );
77 |
78 | return actions;
79 | },
80 | };
81 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/intl-locale-data.hbs:
--------------------------------------------------------------------------------
1 | $1import {{language}}LocaleData from 'react-intl/locale-data/{{language}}';
2 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/polyfill-intl-locale.hbs:
--------------------------------------------------------------------------------
1 | $1 System.import('intl/locale-data/jsonp/{{language}}.js'),
2 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/translation-messages.hbs:
--------------------------------------------------------------------------------
1 | $1import {{language}}TranslationMessages from './translations/{{language}}.json';
2 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/language/translations-json.hbs:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/route/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Route Generator
3 | */
4 | const fs = require('fs');
5 | const componentExists = require('../utils/componentExists');
6 |
7 | function reducerExists(comp) {
8 | try {
9 | fs.accessSync(`app/containers/${comp}/reducer.js`, fs.F_OK);
10 | return true;
11 | } catch (e) {
12 | return false;
13 | }
14 | }
15 |
16 | function sagasExists(comp) {
17 | try {
18 | fs.accessSync(`app/containers/${comp}/sagas.js`, fs.F_OK);
19 | return true;
20 | } catch (e) {
21 | return false;
22 | }
23 | }
24 |
25 | function trimTemplateFile(template) {
26 | // Loads the template file and trims the whitespace and then returns the content as a string.
27 | return fs.readFileSync(`internals/generators/route/${template}`, 'utf8').replace(/\s*$/, '');
28 | }
29 |
30 | module.exports = {
31 | description: 'Add a route',
32 | prompts: [{
33 | type: 'input',
34 | name: 'component',
35 | message: 'Which component should the route show?',
36 | validate: (value) => {
37 | if ((/.+/).test(value)) {
38 | return componentExists(value) ? true : `"${value}" doesn't exist.`;
39 | }
40 |
41 | return 'The path is required';
42 | },
43 | }, {
44 | type: 'input',
45 | name: 'path',
46 | message: 'Enter the path of the route.',
47 | default: '/about',
48 | validate: (value) => {
49 | if ((/.+/).test(value)) {
50 | return true;
51 | }
52 |
53 | return 'path is required';
54 | },
55 | }],
56 |
57 | // Add the route to the routes.js file above the error route
58 | // TODO smarter route adding
59 | actions: (data) => {
60 | const actions = [];
61 | if (reducerExists(data.component)) {
62 | data.useSagas = sagasExists(data.component); // eslint-disable-line no-param-reassign
63 | actions.push({
64 | type: 'modify',
65 | path: '../../app/routes.js',
66 | pattern: /(\s{\n\s{0,}path: '\*',)/g,
67 | template: trimTemplateFile('routeWithReducer.hbs'),
68 | });
69 | } else {
70 | actions.push({
71 | type: 'modify',
72 | path: '../../app/routes.js',
73 | pattern: /(\s{\n\s{0,}path: '\*',)/g,
74 | template: trimTemplateFile('route.hbs'),
75 | });
76 | }
77 |
78 | return actions;
79 | },
80 | };
81 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/route/route.hbs:
--------------------------------------------------------------------------------
1 | {
2 | path: '{{ path }}',
3 | name: '{{ camelCase component }}',
4 | getComponent(location, cb) {
5 | System.import('{{{directory (properCase component)}}}')
6 | .then(loadModule(cb))
7 | .catch(errorLoading);
8 | },
9 | },$1
10 |
--------------------------------------------------------------------------------
/FrontEnd/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 |
--------------------------------------------------------------------------------
/FrontEnd/internals/generators/utils/componentExists.js:
--------------------------------------------------------------------------------
1 | /**
2 | * componentExists
3 | *
4 | * Check whether the given component exist in either the components or containers directory
5 | */
6 |
7 | const fs = require('fs');
8 | const pageComponents = fs.readdirSync('app/components');
9 | const pageContainers = fs.readdirSync('app/containers');
10 | const components = pageComponents.concat(pageContainers);
11 |
12 | function componentExists(comp) {
13 | return components.indexOf(comp) >= 0;
14 | }
15 |
16 | module.exports = componentExists;
17 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/analyze.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const shelljs = require('shelljs');
4 | const animateProgress = require('./helpers/progress');
5 | const chalk = require('chalk');
6 | const addCheckMark = require('./helpers/checkmark');
7 |
8 | const 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 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/clean.js:
--------------------------------------------------------------------------------
1 | require('shelljs/global');
2 | const addCheckMark = require('./helpers/checkmark.js');
3 |
4 | if (!which('git')) {
5 | echo('Sorry, this script requires git');
6 | exit(1);
7 | }
8 |
9 | if (!test('-e', 'internals/templates')) {
10 | echo('The example is deleted already.');
11 | exit(1);
12 | }
13 |
14 | process.stdout.write('Cleanup started...');
15 |
16 | // Cleanup components folder
17 | rm('-rf', 'app/components/*');
18 |
19 | // Cleanup containers folder
20 | rm('-rf', 'app/containers/*');
21 | mkdir('-p', 'app/containers/App');
22 | mkdir('-p', 'app/containers/NotFoundPage');
23 | mkdir('-p', 'app/containers/HomePage');
24 | cp('internals/templates/appContainer.js', 'app/containers/App/index.js');
25 | cp('internals/templates/constants.js', 'app/containers/App/constants.js');
26 | cp('internals/templates/notFoundPage/notFoundPage.js', 'app/containers/NotFoundPage/index.js');
27 | cp('internals/templates/notFoundPage/messages.js', 'app/containers/NotFoundPage/messages.js');
28 | cp('internals/templates/homePage/homePage.js', 'app/containers/HomePage/index.js');
29 | cp('internals/templates/homePage/messages.js', 'app/containers/HomePage/messages.js');
30 |
31 | // Handle Translations
32 | rm('-rf', 'app/translations/*')
33 | mkdir('-p', 'app/translations');
34 | cp('internals/templates/translations/en.json',
35 | 'app/translations/en.json');
36 |
37 | // move i18n file
38 | cp('internals/templates/i18n.js',
39 | 'app/i18n.js');
40 |
41 | // Copy LanguageProvider
42 | mkdir('-p', 'app/containers/LanguageProvider');
43 | mkdir('-p', 'app/containers/LanguageProvider/tests');
44 | cp('internals/templates/languageProvider/actions.js',
45 | 'app/containers/LanguageProvider/actions.js');
46 | cp('internals/templates/languageProvider/constants.js',
47 | 'app/containers/LanguageProvider/constants.js');
48 | cp('internals/templates/languageProvider/languageProvider.js',
49 | 'app/containers/LanguageProvider/index.js');
50 | cp('internals/templates/languageProvider/reducer.js',
51 | 'app/containers/LanguageProvider/reducer.js');
52 | cp('internals/templates/languageProvider/selectors.js',
53 | 'app/containers/LanguageProvider/selectors.js');
54 |
55 | // Copy selectors
56 | mkdir('app/containers/App/tests');
57 | cp('internals/templates/selectors.js',
58 | 'app/containers/App/selectors.js');
59 | cp('internals/templates/selectors.test.js',
60 | 'app/containers/App/tests/selectors.test.js');
61 |
62 | // Utils
63 | rm('-rf', 'app/utils');
64 | mkdir('app/utils');
65 | mkdir('app/utils/tests');
66 | cp('internals/templates/asyncInjectors.js',
67 | 'app/utils/asyncInjectors.js');
68 | cp('internals/templates/asyncInjectors.test.js',
69 | 'app/utils/tests/asyncInjectors.test.js');
70 |
71 | // Replace the files in the root app/ folder
72 | cp('internals/templates/app.js', 'app/app.js');
73 | cp('internals/templates/index.html', 'app/index.html');
74 | cp('internals/templates/reducers.js', 'app/reducers.js');
75 | cp('internals/templates/routes.js', 'app/routes.js');
76 | cp('internals/templates/store.js', 'app/store.js');
77 | cp('internals/templates/store.test.js', 'app/tests/store.test.js');
78 |
79 | // Remove the templates folder
80 | rm('-rf', 'internals/templates');
81 |
82 | addCheckMark();
83 |
84 | // Commit the changes
85 | if (exec('git add . --all && git commit -qm "Remove default example"').code !== 0) {
86 | echo('\nError: Git commit failed');
87 | exit(1);
88 | }
89 |
90 | echo('\nCleanup done. Happy Coding!!!');
91 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/dependencies.js:
--------------------------------------------------------------------------------
1 | // No need to build the DLL in production
2 | if (process.env.NODE_ENV === 'production') {
3 | process.exit(0);
4 | }
5 |
6 | require('shelljs/global');
7 |
8 | const path = require('path');
9 | const fs = require('fs');
10 | const exists = fs.existsSync;
11 | const writeFile = fs.writeFileSync;
12 |
13 | const defaults = require('lodash/defaultsDeep');
14 | const pkg = require(path.join(process.cwd(), 'package.json'));
15 | const config = require('../config');
16 | const dllConfig = defaults(pkg.dllPlugin, config.dllPlugin.defaults);
17 | const outputPath = path.join(process.cwd(), dllConfig.path);
18 | const dllManifestPath = path.join(outputPath, 'package.json');
19 |
20 | /**
21 | * I use node_modules/react-boilerplate-dlls by default just because
22 | * it isn't going to be version controlled and babel wont try to parse it.
23 | */
24 | mkdir('-p', outputPath);
25 |
26 | echo('Building the Webpack DLL...');
27 |
28 | /**
29 | * Create a manifest so npm install doesn't warn us
30 | */
31 | if (!exists(dllManifestPath)) {
32 | writeFile(
33 | dllManifestPath,
34 | JSON.stringify(defaults({
35 | name: 'react-boilerplate-dlls',
36 | private: true,
37 | author: pkg.author,
38 | repository: pkg.repository,
39 | version: pkg.version,
40 | }), null, 2),
41 | 'utf8'
42 | );
43 | }
44 |
45 | // the BUILDING_DLL env var is set to avoid confusing the development environment
46 | exec('cross-env BUILDING_DLL=true webpack --display-chunks --color --config internals/webpack/webpack.dll.babel.js');
47 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/extract-intl.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * This script will extract the internationalization messages from all components
4 | and package them in the translation json files in the translations file.
5 | */
6 | const fs = require('fs');
7 | const nodeGlob = require('glob');
8 | const transform = require('babel-core').transform;
9 |
10 | const animateProgress = require('./helpers/progress');
11 | const addCheckmark = require('./helpers/checkmark');
12 |
13 | const pkg = require('../../package.json');
14 | const i18n = require('../../app/i18n');
15 | import { DEFAULT_LOCALE } from '../../app/containers/App/constants';
16 |
17 | require('shelljs/global');
18 |
19 | // Glob to match all js files except test files
20 | const FILES_TO_PARSE = 'app/**/!(*.test).js';
21 | const locales = i18n.appLocales;
22 |
23 | const newLine = () => process.stdout.write('\n');
24 |
25 | // Progress Logger
26 | let progress;
27 | const task = (message) => {
28 | progress = animateProgress(message);
29 | process.stdout.write(message);
30 |
31 | return (error) => {
32 | if (error) {
33 | process.stderr.write(error);
34 | }
35 | clearTimeout(progress);
36 | return addCheckmark(() => newLine());
37 | }
38 | }
39 |
40 | // Wrap async functions below into a promise
41 | const glob = (pattern) => new Promise((resolve, reject) => {
42 | nodeGlob(pattern, (error, value) => (error ? reject(error) : resolve(value)));
43 | });
44 |
45 | const readFile = (fileName) => new Promise((resolve, reject) => {
46 | fs.readFile(fileName, (error, value) => (error ? reject(error) : resolve(value)));
47 | });
48 |
49 | const writeFile = (fileName, data) => new Promise((resolve, reject) => {
50 | fs.writeFile(fileName, data, (error, value) => (error ? reject(error) : resolve(value)));
51 | });
52 |
53 | // Store existing translations into memory
54 | const oldLocaleMappings = [];
55 | const localeMappings = [];
56 | // Loop to run once per locale
57 | for (const locale of locales) {
58 | oldLocaleMappings[locale] = {};
59 | localeMappings[locale] = {};
60 | // File to store translation messages into
61 | const translationFileName = `app/translations/${locale}.json`;
62 | try {
63 | // Parse the old translation message JSON files
64 | const messages = JSON.parse(fs.readFileSync(translationFileName));
65 | const messageKeys = Object.keys(messages);
66 | for (const messageKey of messageKeys) {
67 | oldLocaleMappings[locale][messageKey] = messages[messageKey];
68 | }
69 | } catch (error) {
70 | if (error.code !== 'ENOENT') {
71 | process.stderr.write(
72 | `There was an error loading this translation file: ${translationFileName}
73 | \n${error}`
74 | );
75 | }
76 | }
77 | }
78 |
79 | const extractFromFile = async (fileName) => {
80 | try {
81 | const code = await readFile(fileName);
82 | // Use babel plugin to extract instances where react-intl is used
83 | const { metadata: result } = await transform(code, {
84 | presets: pkg.babel.presets,
85 | plugins: [
86 | ['react-intl'],
87 | ],
88 | });
89 | for (const message of result['react-intl'].messages) {
90 | for (const locale of locales) {
91 | const oldLocaleMapping = oldLocaleMappings[locale][message.id];
92 | // Merge old translations into the babel extracted instances where react-intl is used
93 | const newMsg = ( locale === DEFAULT_LOCALE) ? message.defaultMessage : '';
94 | localeMappings[locale][message.id] = (oldLocaleMapping)
95 | ? oldLocaleMapping
96 | : newMsg;
97 | }
98 | }
99 | } catch (error) {
100 | process.stderr.write(`Error transforming file: ${fileName}\n${error}`);
101 | }
102 | };
103 |
104 | (async function main() {
105 | const memoryTaskDone = task('Storing language files in memory');
106 | const files = await glob(FILES_TO_PARSE);
107 | memoryTaskDone()
108 |
109 | const extractTaskDone = task('Run extraction on all files');
110 | // Run extraction on all files that match the glob on line 16
111 | await Promise.all(files.map((fileName) => extractFromFile(fileName)));
112 | extractTaskDone()
113 |
114 | // Make the directory if it doesn't exist, especially for first run
115 | mkdir('-p', 'app/translations');
116 | for (const locale of locales) {
117 | const translationFileName = `app/translations/${locale}.json`;
118 |
119 | try {
120 | const localeTaskDone = task(
121 | `Writing translation messages for ${locale} to: ${translationFileName}`
122 | );
123 |
124 | // Sort the translation JSON file so that git diffing is easier
125 | // Otherwise the translation messages will jump around every time we extract
126 | let messages = {};
127 | Object.keys(localeMappings[locale]).sort().forEach(function(key) {
128 | messages[key] = localeMappings[locale][key];
129 | });
130 |
131 | // Write to file the JSON representation of the translation messages
132 | const prettified = `${JSON.stringify(messages, null, 2)}\n`;
133 |
134 | await writeFile(translationFileName, prettified);
135 |
136 | localeTaskDone();
137 | } catch (error) {
138 | localeTaskDone(
139 | `There was an error saving this translation file: ${translationFileName}
140 | \n${error}`
141 | );
142 | }
143 | }
144 |
145 | process.exit()
146 | }());
147 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/helpers/checkmark.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | /**
4 | * Adds mark check symbol
5 | */
6 | function addCheckMark(callback) {
7 | process.stdout.write(chalk.green(' ✓'));
8 | if (callback) callback();
9 | }
10 |
11 | module.exports = addCheckMark;
12 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/helpers/progress.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const readline = require('readline');
4 |
5 | /**
6 | * Adds an animated progress indicator
7 | *
8 | * @param {string} message The message to write next to the indicator
9 | * @param {number} amountOfDots The amount of dots you want to animate
10 | */
11 | function animateProgress(message, amountOfDots) {
12 | if (typeof amountOfDots !== 'number') {
13 | amountOfDots = 3;
14 | }
15 |
16 | let i = 0;
17 | return setInterval(function() {
18 | readline.cursorTo(process.stdout, 0);
19 | i = (i + 1) % (amountOfDots + 1);
20 | const dots = new Array(i + 1).join('.');
21 | process.stdout.write(message + dots);
22 | }, 500);
23 | }
24 |
25 | module.exports = animateProgress;
26 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/npmcheckversion.js:
--------------------------------------------------------------------------------
1 | const exec = require('child_process').exec;
2 | exec('npm -v', function (err, stdout, stderr) {
3 | if (err) throw err;
4 | if (parseFloat(stdout) < 3) {
5 | throw new Error('[ERROR: React Boilerplate] You need npm version @>=3');
6 | process.exit(1);
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/pagespeed.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | process.stdin.resume();
4 | process.stdin.setEncoding('utf8');
5 |
6 | const ngrok = require('ngrok');
7 | const psi = require('psi');
8 | const chalk = require('chalk');
9 |
10 | log('\nStarting ngrok tunnel');
11 |
12 | startTunnel(runPsi);
13 |
14 | function runPsi(url) {
15 | log('\nStarting PageSpeed Insights');
16 | psi.output(url).then(function (err) {
17 | process.exit(0);
18 | });
19 | }
20 |
21 | function startTunnel(cb) {
22 | ngrok.connect(3000, function (err, url) {
23 | if (err) {
24 | log(chalk.red('\nERROR\n' + err));
25 | process.exit(0);
26 | }
27 |
28 | log('\nServing tunnel from: ' + chalk.magenta(url));
29 | cb(url);
30 | });
31 | }
32 |
33 | function log(string) {
34 | process.stdout.write(string);
35 | }
36 |
--------------------------------------------------------------------------------
/FrontEnd/internals/scripts/setup.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | const shell = require('shelljs');
6 | const exec = require('child_process').exec;
7 | const path = require('path');
8 | const fs = require('fs');
9 | const animateProgress = require('./helpers/progress');
10 | const addCheckMark = require('./helpers/checkmark');
11 | const readline = require('readline');
12 |
13 | process.stdin.resume();
14 | process.stdin.setEncoding('utf8');
15 |
16 | process.stdout.write('\n');
17 | let interval = animateProgress('Cleaning old repository');
18 | process.stdout.write('Cleaning old repository');
19 |
20 | cleanRepo(function () {
21 | clearInterval(interval);
22 | process.stdout.write('\nInstalling dependencies... (This might take a while)');
23 | setTimeout(function () {
24 | readline.cursorTo(process.stdout, 0);
25 | interval = animateProgress('Installing dependencies');
26 | }, 500);
27 |
28 | process.stdout.write('Installing dependencies');
29 | installDeps();
30 | });
31 |
32 | /**
33 | * Deletes the .git folder in dir
34 | */
35 | function cleanRepo(callback) {
36 | shell.rm('-rf', '.git/');
37 | addCheckMark(callback);
38 | }
39 |
40 | /**
41 | * Initializes git again
42 | */
43 | function initGit(callback) {
44 | exec('git init && git add . && git commit -m "Initial commit"', addCheckMark.bind(null, callback));
45 | }
46 |
47 | /**
48 | * Deletes a file in the current directory
49 | */
50 | function deleteFileInCurrentDir(file, callback) {
51 | fs.unlink(path.join(__dirname, file), callback);
52 | }
53 |
54 | /**
55 | * Installs dependencies
56 | */
57 | function installDeps() {
58 | exec('yarn --version', function (err, stdout, stderr) {
59 | if (parseFloat(stdout) < 0.15 || err || process.env.USE_YARN === 'false') {
60 | exec('npm install', addCheckMark.bind(null, installDepsCallback));
61 | } else {
62 | exec('yarn install', addCheckMark.bind(null, installDepsCallback));
63 | }
64 | });
65 | }
66 |
67 | /**
68 | * Callback function after installing dependencies
69 | */
70 | function installDepsCallback(error) {
71 | clearInterval(interval);
72 | if (error) {
73 | process.stdout.write(error);
74 | }
75 |
76 | process.stdout.write('\n');
77 | interval = animateProgress('Initialising new repository');
78 | process.stdout.write('Initialising new repository');
79 | initGit(function () {
80 | clearInterval(interval);
81 | process.stdout.write('\nDone!');
82 | process.exit(0);
83 | });
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/FrontEnd/internals/testing/karma.conf.js:
--------------------------------------------------------------------------------
1 | const webpackConfig = require('../webpack/webpack.test.babel');
2 | const argv = require('minimist')(process.argv.slice(2));
3 | const path = require('path');
4 |
5 | module.exports = (config) => {
6 | config.set({
7 | frameworks: ['mocha'],
8 | reporters: ['coverage', 'mocha'],
9 | browsers: process.env.TRAVIS // eslint-disable-line no-nested-ternary
10 | ? ['ChromeTravis']
11 | : process.env.APPVEYOR
12 | ? ['IE'] : ['Chrome'],
13 |
14 | autoWatch: false,
15 | singleRun: true,
16 |
17 | client: {
18 | mocha: {
19 | grep: argv.grep,
20 | },
21 | },
22 |
23 | files: [
24 | {
25 | pattern: './test-bundler.js',
26 | watched: false,
27 | served: true,
28 | included: true,
29 | },
30 | ],
31 |
32 | preprocessors: {
33 | ['./test-bundler.js']: ['webpack', 'sourcemap'], // eslint-disable-line no-useless-computed-key
34 | },
35 |
36 | webpack: webpackConfig,
37 |
38 | // make Webpack bundle generation quiet
39 | webpackMiddleware: {
40 | noInfo: true,
41 | stats: 'errors-only',
42 | },
43 |
44 | customLaunchers: {
45 | ChromeTravis: {
46 | base: 'Chrome',
47 | flags: ['--no-sandbox'],
48 | },
49 | },
50 |
51 | coverageReporter: {
52 | dir: path.join(process.cwd(), 'coverage'),
53 | reporters: [
54 | { type: 'lcov', subdir: 'lcov' },
55 | { type: 'html', subdir: 'html' },
56 | { type: 'text-summary' },
57 | ],
58 | },
59 |
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/FrontEnd/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 .js files under `app`, except app.js, reducers.js, and routes.js.
11 | // This is for code coverage
12 | const context = require.context('../../app', true, /^^((?!(app|reducers|routes)).)*\.js$/);
13 | context.keys().forEach(context);
14 |
--------------------------------------------------------------------------------
/FrontEnd/internals/webpack/webpack.base.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * COMMON WEBPACK CONFIGURATION
3 | */
4 |
5 | const path = require('path');
6 | const webpack = require('webpack');
7 |
8 | module.exports = (options) => ({
9 | entry: options.entry,
10 | output: Object.assign({ // Compile into js/build.js
11 | path: path.resolve(process.cwd(), 'build'),
12 | publicPath: '/',
13 | }, options.output), // Merge with env dependent settings
14 | module: {
15 | loaders: [{
16 | test: /\.js$/, // Transform all .js files required somewhere with Babel
17 | loader: 'babel',
18 | exclude: /node_modules/,
19 | query: options.babelQuery,
20 | }, {
21 | // Do not transform vendor's CSS with CSS-modules
22 | // The point is that they remain in global scope.
23 | // Since we require these CSS files in our JS or CSS files,
24 | // they will be a part of our compilation either way.
25 | // So, no need for ExtractTextPlugin here.
26 | test: /\.css$/,
27 | include: /node_modules/,
28 | loaders: ['style-loader', 'css-loader'],
29 | }, {
30 | test: /\.(eot|svg|ttf|woff|woff2)$/,
31 | loader: 'file-loader',
32 | }, {
33 | test: /\.(jpg|png|gif)$/,
34 | loaders: [
35 | 'file-loader',
36 | 'image-webpack?{progressive:true, optimizationLevel: 7, interlaced: false, pngquant:{quality: "65-90", speed: 4}}',
37 | ],
38 | }, {
39 | test: /\.html$/,
40 | loader: 'html-loader',
41 | }, {
42 | test: /\.json$/,
43 | loader: 'json-loader',
44 | }, {
45 | test: /\.(mp4|webm)$/,
46 | loader: 'url-loader?limit=10000',
47 | }],
48 | },
49 | plugins: options.plugins.concat([
50 | new webpack.ProvidePlugin({
51 | // make fetch available
52 | fetch: 'exports?self.fetch!whatwg-fetch',
53 | }),
54 |
55 | // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
56 | // inside your code for any environment checks; UglifyJS will automatically
57 | // drop any unreachable code.
58 | new webpack.DefinePlugin({
59 | 'process.env': {
60 | NODE_ENV: JSON.stringify(process.env.NODE_ENV),
61 | },
62 | }),
63 | new webpack.NamedModulesPlugin(),
64 | ]),
65 | resolve: {
66 | modules: ['app', 'node_modules'],
67 | extensions: [
68 | '.js',
69 | '.jsx',
70 | '.react.js',
71 | ],
72 | mainFields: [
73 | 'browser',
74 | 'jsnext:main',
75 | 'main',
76 | ],
77 | },
78 | devtool: options.devtool,
79 | target: 'web', // Make web variables accessible to webpack, e.g. window
80 | });
81 |
--------------------------------------------------------------------------------
/FrontEnd/internals/webpack/webpack.dev.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * DEVELOPMENT WEBPACK CONFIGURATION
3 | */
4 |
5 | const path = require('path');
6 | const fs = require('fs');
7 | const webpack = require('webpack');
8 | const HtmlWebpackPlugin = require('html-webpack-plugin');
9 | const logger = require('../../server/logger');
10 | const cheerio = require('cheerio');
11 | const pkg = require(path.resolve(process.cwd(), 'package.json'));
12 | const dllPlugin = pkg.dllPlugin;
13 |
14 | const plugins = [
15 | new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
16 | new webpack.NoErrorsPlugin(),
17 | new HtmlWebpackPlugin({
18 | inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
19 | templateContent: templateContent(), // eslint-disable-line no-use-before-define
20 | }),
21 | ];
22 |
23 | module.exports = require('./webpack.base.babel')({
24 | // Add hot reloading in development
25 | entry: [
26 | 'eventsource-polyfill', // Necessary for hot reloading with IE
27 | 'webpack-hot-middleware/client',
28 | path.join(process.cwd(), 'app/app.js'), // Start with js/app.js
29 | ],
30 |
31 | // Don't use hashes in dev mode for better performance
32 | output: {
33 | filename: '[name].js',
34 | chunkFilename: '[name].chunk.js',
35 | },
36 |
37 | // Add development plugins
38 | plugins: dependencyHandlers().concat(plugins), // eslint-disable-line no-use-before-define
39 |
40 | // Tell babel that we want to hot-reload
41 | babelQuery: {
42 | presets: ['react-hmre'],
43 | },
44 |
45 | // Emit a source map for easier debugging
46 | devtool: 'cheap-module-eval-source-map',
47 | });
48 |
49 | /**
50 | * Select which plugins to use to optimize the bundle's handling of
51 | * third party dependencies.
52 | *
53 | * If there is a dllPlugin key on the project's package.json, the
54 | * Webpack DLL Plugin will be used. Otherwise the CommonsChunkPlugin
55 | * will be used.
56 | *
57 | */
58 | function dependencyHandlers() {
59 | // Don't do anything during the DLL Build step
60 | if (process.env.BUILDING_DLL) { return []; }
61 |
62 | // If the package.json does not have a dllPlugin property, use the CommonsChunkPlugin
63 | if (!dllPlugin) {
64 | return [
65 | new webpack.optimize.CommonsChunkPlugin({
66 | name: 'vendor',
67 | children: true,
68 | minChunks: 2,
69 | async: true,
70 | }),
71 | ];
72 | }
73 |
74 | const dllPath = path.resolve(process.cwd(), dllPlugin.path || 'node_modules/react-boilerplate-dlls');
75 |
76 | /**
77 | * If DLLs aren't explicitly defined, we assume all production dependencies listed in package.json
78 | * Reminder: You need to exclude any server side dependencies by listing them in dllConfig.exclude
79 | *
80 | * @see https://github.com/mxstbr/react-boilerplate/tree/master/docs/general/webpack.md
81 | */
82 | if (!dllPlugin.dlls) {
83 | const manifestPath = path.resolve(dllPath, 'reactBoilerplateDeps.json');
84 |
85 | if (!fs.existsSync(manifestPath)) {
86 | logger.error('The DLL manifest is missing. Please run `npm run build:dll`');
87 | process.exit(0);
88 | }
89 |
90 | return [
91 | new webpack.DllReferencePlugin({
92 | context: process.cwd(),
93 | manifest: require(manifestPath), // eslint-disable-line global-require
94 | }),
95 | ];
96 | }
97 |
98 | // If DLLs are explicitly defined, we automatically create a DLLReferencePlugin for each of them.
99 | const dllManifests = Object.keys(dllPlugin.dlls).map((name) => path.join(dllPath, `/${name}.json`));
100 |
101 | return dllManifests.map((manifestPath) => {
102 | if (!fs.existsSync(path)) {
103 | if (!fs.existsSync(manifestPath)) {
104 | logger.error(`The following Webpack DLL manifest is missing: ${path.basename(manifestPath)}`);
105 | logger.error(`Expected to find it in ${dllPath}`);
106 | logger.error('Please run: npm run build:dll');
107 |
108 | process.exit(0);
109 | }
110 | }
111 |
112 | return new webpack.DllReferencePlugin({
113 | context: process.cwd(),
114 | manifest: require(manifestPath), // eslint-disable-line global-require
115 | });
116 | });
117 | }
118 |
119 | /**
120 | * We dynamically generate the HTML content in development so that the different
121 | * DLL Javascript files are loaded in script tags and available to our application.
122 | */
123 | function templateContent() {
124 | const html = fs.readFileSync(
125 | path.resolve(process.cwd(), 'app/index.html')
126 | ).toString();
127 |
128 | if (!dllPlugin) { return html; }
129 |
130 | const doc = cheerio(html);
131 | const body = doc.find('body');
132 | const dllNames = !dllPlugin.dlls ? ['reactBoilerplateDeps'] : Object.keys(dllPlugin.dlls);
133 |
134 | dllNames.forEach((dllName) => body.append(``));
135 |
136 | return doc.toString();
137 | }
138 |
--------------------------------------------------------------------------------
/FrontEnd/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 |
--------------------------------------------------------------------------------
/FrontEnd/internals/webpack/webpack.prod.babel.js:
--------------------------------------------------------------------------------
1 | // Important modules this config uses
2 | const path = require('path');
3 | const webpack = require('webpack');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 | const OfflinePlugin = require('offline-plugin');
6 |
7 | module.exports = require('./webpack.base.babel')({
8 | // In production, we skip all hot-reloading stuff
9 | entry: [
10 | path.join(process.cwd(), 'app/app.js'),
11 | ],
12 |
13 | // Utilize long-term caching by adding content hashes (not compilation hashes) to compiled assets
14 | output: {
15 | filename: '[name].[chunkhash].js',
16 | chunkFilename: '[name].[chunkhash].chunk.js',
17 | },
18 |
19 | plugins: [
20 | new webpack.optimize.CommonsChunkPlugin({
21 | name: 'vendor',
22 | children: true,
23 | minChunks: 2,
24 | async: true,
25 | }),
26 |
27 | // Merge all duplicate modules
28 | new webpack.optimize.DedupePlugin(),
29 |
30 | // Minify and optimize the index.html
31 | new HtmlWebpackPlugin({
32 | template: 'app/index.html',
33 | minify: {
34 | removeComments: true,
35 | collapseWhitespace: true,
36 | removeRedundantAttributes: true,
37 | useShortDoctype: true,
38 | removeEmptyAttributes: true,
39 | removeStyleLinkTypeAttributes: true,
40 | keepClosingSlash: true,
41 | minifyJS: true,
42 | minifyCSS: true,
43 | minifyURLs: true,
44 | },
45 | inject: true,
46 | }),
47 |
48 | // Put it in the end to capture all the HtmlWebpackPlugin's
49 | // assets manipulations and do leak its manipulations to HtmlWebpackPlugin
50 | new OfflinePlugin({
51 | relativePaths: false,
52 | publicPath: '/',
53 |
54 | // No need to cache .htaccess. See http://mxs.is/googmp,
55 | // this is applied before any match in `caches` section
56 | excludes: ['.htaccess'],
57 |
58 | caches: {
59 | main: [':rest:'],
60 |
61 | // All chunks marked as `additional`, loaded after main section
62 | // and do not prevent SW to install. Change to `optional` if
63 | // do not want them to be preloaded at all (cached only when first loaded)
64 | additional: ['*.chunk.js'],
65 | },
66 |
67 | // Removes warning for about `additional` section usage
68 | safeToUseOptionalCaches: true,
69 |
70 | AppCache: false,
71 | }),
72 | ],
73 | });
74 |
--------------------------------------------------------------------------------
/FrontEnd/internals/webpack/webpack.test.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TEST WEBPACK CONFIGURATION
3 | */
4 |
5 | const webpack = require('webpack');
6 | const modules = [
7 | 'app',
8 | 'node_modules',
9 | ];
10 |
11 | module.exports = {
12 | devtool: 'inline-source-map',
13 | module: {
14 | // Some libraries don't like being run through babel.
15 | // If they gripe, put them here.
16 | noParse: [
17 | /node_modules(\\|\/)sinon/,
18 | /node_modules(\\|\/)acorn/,
19 | ],
20 | loaders: [
21 | { test: /\.json$/, loader: 'json-loader' },
22 | { test: /\.css$/, loader: 'null-loader' },
23 |
24 | // sinon.js--aliased for enzyme--expects/requires global vars.
25 | // imports-loader allows for global vars to be injected into the module.
26 | // See https://github.com/webpack/webpack/issues/304
27 | { test: /sinon(\\|\/)pkg(\\|\/)sinon\.js/,
28 | loader: 'imports?define=>false,require=>false',
29 | },
30 | { test: /\.js$/,
31 | loader: 'babel',
32 | exclude: [/node_modules/],
33 | },
34 | { test: /\.jpe?g$|\.gif$|\.png$|\.svg$/i,
35 | loader: 'null-loader',
36 | },
37 | ],
38 | },
39 |
40 | plugins: [
41 |
42 | // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
43 | // inside your code for any environment checks; UglifyJS will automatically
44 | // drop any unreachable code.
45 | new webpack.DefinePlugin({
46 | 'process.env': {
47 | NODE_ENV: JSON.stringify(process.env.NODE_ENV),
48 | },
49 | })],
50 |
51 | // Some node_modules pull in Node-specific dependencies.
52 | // Since we're running in a browser we have to stub them out. See:
53 | // https://webpack.github.io/docs/configuration.html#node
54 | // https://github.com/webpack/node-libs-browser/tree/master/mock
55 | // https://github.com/webpack/jade-loader/issues/8#issuecomment-55568520
56 | node: {
57 | fs: 'empty',
58 | child_process: 'empty',
59 | net: 'empty',
60 | tls: 'empty',
61 | },
62 |
63 | // required for enzyme to work properly
64 | externals: {
65 | jsdom: 'window',
66 | 'react/addons': true,
67 | 'react/lib/ExecutionEnvironment': true,
68 | 'react/lib/ReactContext': 'window',
69 | },
70 | resolve: {
71 | modules,
72 | alias: {
73 | // required for enzyme to work properly
74 | sinon: 'sinon/pkg/sinon',
75 | },
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/FrontEnd/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "version": "",
4 | "description": "",
5 | "repository": {
6 | "type": "git",
7 | "url": ""
8 | },
9 | "engines": {
10 | "npm": ">=3"
11 | },
12 | "license": "MIT",
13 | "scripts": {
14 | "analyze:clean": "rimraf stats.json",
15 | "preanalyze": "npm run analyze:clean",
16 | "analyze": "node ./internals/scripts/analyze.js",
17 | "extract-intl": "babel-node --presets latest,stage-0 -- ./internals/scripts/extract-intl.js",
18 | "npmcheckversion": "node ./internals/scripts/npmcheckversion.js",
19 | "preinstall": "npm run npmcheckversion",
20 | "postinstall": "npm run build:dll",
21 | "prebuild": "npm run build:clean && npm run test",
22 | "build": "cross-env NODE_ENV=production webpack --config internals/webpack/webpack.prod.babel.js --color -p --progress",
23 | "build:clean": "npm run test:clean && rimraf ./build",
24 | "build:dll": "node ./internals/scripts/dependencies.js",
25 | "start": "cross-env NODE_ENV=development node server",
26 | "start:tunnel": "cross-env NODE_ENV=development ENABLE_TUNNEL=true node server",
27 | "start:production": "npm run build && npm run start:prod",
28 | "start:prod": "cross-env NODE_ENV=production node server",
29 | "pagespeed": "node ./internals/scripts/pagespeed.js",
30 | "presetup": "npm i chalk shelljs",
31 | "setup": "node ./internals/scripts/setup.js",
32 | "postsetup": "npm run build:dll",
33 | "clean": "shjs ./internals/scripts/clean.js",
34 | "clean:all": "npm run analyze:clean && npm run test:clean && npm run build:clean",
35 | "generate": "plop --plopfile internals/generators/index.js",
36 | "lint": "npm run lint:js",
37 | "lint:eslint": "eslint --ignore-path .gitignore --ignore-pattern internals/scripts",
38 | "lint:js": "npm run lint:eslint -- . ",
39 | "lint:staged": "lint-staged",
40 | "pretest": "npm run test:clean",
41 | "test:clean": "rimraf ./coverage",
42 | "test": "cross-env NODE_ENV=test karma start internals/testing/karma.conf.js --single-run",
43 | "test:watch": "npm run test -- --auto-watch --no-single-run",
44 | "test:firefox": "npm run test -- --browsers Firefox",
45 | "test:safari": "npm run test -- --browsers Safari",
46 | "test:ie": "npm run test -- --browsers IE",
47 | "coveralls": "cat ./coverage/lcov/lcov.info | coveralls"
48 | },
49 | "lint-staged": {
50 | "*.js": "lint:eslint"
51 | },
52 | "pre-commit": "lint:staged",
53 | "babel": {
54 | "presets": [
55 | [
56 | "latest",
57 | {
58 | "es2015": {
59 | "modules": false
60 | }
61 | }
62 | ],
63 | "react",
64 | "stage-0"
65 | ],
66 | "env": {
67 | "production": {
68 | "only": [
69 | "app"
70 | ],
71 | "plugins": [
72 | "transform-react-remove-prop-types",
73 | "transform-react-constant-elements",
74 | "transform-react-inline-elements"
75 | ]
76 | },
77 | "test": {
78 | "plugins": [
79 | "istanbul"
80 | ]
81 | }
82 | }
83 | },
84 | "eslintConfig": {
85 | "parser": "babel-eslint",
86 | "extends": "airbnb",
87 | "env": {
88 | "browser": true,
89 | "node": true,
90 | "mocha": true,
91 | "es6": true
92 | },
93 | "plugins": [
94 | "redux-saga",
95 | "react",
96 | "jsx-a11y"
97 | ],
98 | "parserOptions": {
99 | "ecmaVersion": 6,
100 | "sourceType": "module",
101 | "ecmaFeatures": {
102 | "jsx": true
103 | }
104 | },
105 | "rules": {
106 | "arrow-parens": [
107 | "error",
108 | "always"
109 | ],
110 | "arrow-body-style": [
111 | 2,
112 | "as-needed"
113 | ],
114 | "comma-dangle": [
115 | 2,
116 | "always-multiline"
117 | ],
118 | "import/imports-first": 0,
119 | "import/newline-after-import": 0,
120 | "import/no-dynamic-require": 0,
121 | "import/no-extraneous-dependencies": 0,
122 | "import/no-named-as-default": 0,
123 | "import/no-unresolved": 2,
124 | "import/prefer-default-export": 0,
125 | "indent": [
126 | 2,
127 | 2,
128 | {
129 | "SwitchCase": 1
130 | }
131 | ],
132 | "jsx-a11y/aria-props": 2,
133 | "jsx-a11y/heading-has-content": 0,
134 | "jsx-a11y/href-no-hash": 2,
135 | "jsx-a11y/label-has-for": 2,
136 | "jsx-a11y/mouse-events-have-key-events": 2,
137 | "jsx-a11y/role-has-required-aria-props": 2,
138 | "jsx-a11y/role-supports-aria-props": 2,
139 | "max-len": 0,
140 | "newline-per-chained-call": 0,
141 | "no-console": 1,
142 | "no-use-before-define": 0,
143 | "prefer-template": 2,
144 | "class-methods-use-this": 0,
145 | "react/forbid-prop-types": 0,
146 | "react/jsx-first-prop-new-line": [
147 | 2,
148 | "multiline"
149 | ],
150 | "react/jsx-filename-extension": 0,
151 | "react/jsx-no-target-blank": 0,
152 | "react/require-extension": 0,
153 | "react/self-closing-comp": 0,
154 | "redux-saga/no-yield-in-race": 2,
155 | "redux-saga/yield-effects": 2,
156 | "require-yield": 0
157 | },
158 | "settings": {
159 | "import/resolver": {
160 | "webpack": {
161 | "config": "./internals/webpack/webpack.test.babel.js"
162 | }
163 | }
164 | }
165 | },
166 | "dllPlugin": {
167 | "path": "node_modules/react-boilerplate-dlls",
168 | "exclude": [
169 | "chalk",
170 | "compression",
171 | "cross-env",
172 | "express",
173 | "ip",
174 | "minimist",
175 | "sanitize.css"
176 | ],
177 | "include": [
178 | "core-js",
179 | "lodash",
180 | "eventsource-polyfill"
181 | ]
182 | },
183 | "dependencies": {
184 | "babel-polyfill": "6.16.0",
185 | "chalk": "1.1.3",
186 | "compression": "1.6.2",
187 | "cross-env": "3.1.3",
188 | "express": "4.14.0",
189 | "fontfaceobserver": "2.0.5",
190 | "http-proxy-middleware": "^0.17.3",
191 | "immutable": "3.8.1",
192 | "intl": "1.2.5",
193 | "invariant": "2.2.1",
194 | "ip": "1.1.3",
195 | "jwt-decode": "^2.1.0",
196 | "lodash": "4.16.4",
197 | "minimist": "1.2.0",
198 | "react": "15.3.2",
199 | "react-dom": "15.3.2",
200 | "react-helmet": "3.1.0",
201 | "react-intl": "2.1.5",
202 | "react-redux": "4.4.5",
203 | "react-router": "3.0.0",
204 | "react-router-redux": "4.0.6",
205 | "react-router-scroll": "0.3.3",
206 | "redux": "3.6.0",
207 | "redux-auth-wrapper": "^0.9.0",
208 | "redux-immutable": "3.0.8",
209 | "redux-saga": "0.12.0",
210 | "reselect": "2.5.4",
211 | "sanitize.css": "4.1.0",
212 | "styled-components": "1.0.3",
213 | "warning": "3.0.0",
214 | "whatwg-fetch": "1.0.0"
215 | },
216 | "devDependencies": {
217 | "babel-cli": "6.18.0",
218 | "babel-core": "6.18.0",
219 | "babel-eslint": "7.1.0",
220 | "babel-loader": "6.2.7",
221 | "babel-plugin-istanbul": "2.0.3",
222 | "babel-plugin-react-intl": "2.2.0",
223 | "babel-plugin-react-transform": "2.0.2",
224 | "babel-plugin-transform-react-constant-elements": "6.9.1",
225 | "babel-plugin-transform-react-inline-elements": "6.8.0",
226 | "babel-plugin-transform-react-remove-prop-types": "0.2.10",
227 | "babel-preset-latest": "6.16.0",
228 | "babel-preset-react": "6.16.0",
229 | "babel-preset-react-hmre": "1.1.1",
230 | "babel-preset-stage-0": "6.16.0",
231 | "chai": "3.5.0",
232 | "chai-enzyme": "0.5.2",
233 | "cheerio": "0.22.0",
234 | "coveralls": "2.11.14",
235 | "css-loader": "0.25.0",
236 | "enzyme": "2.5.1",
237 | "eslint": "3.9.0",
238 | "eslint-config-airbnb": "12.0.0",
239 | "eslint-config-airbnb-base": "9.0.0",
240 | "eslint-import-resolver-webpack": "0.6.0",
241 | "eslint-plugin-import": "2.0.1",
242 | "eslint-plugin-jsx-a11y": "2.2.3",
243 | "eslint-plugin-react": "6.4.1",
244 | "eslint-plugin-redux-saga": "0.1.5",
245 | "eventsource-polyfill": "0.9.6",
246 | "expect": "1.20.2",
247 | "expect-jsx": "2.6.0",
248 | "exports-loader": "0.6.3",
249 | "file-loader": "0.9.0",
250 | "html-loader": "0.4.4",
251 | "html-webpack-plugin": "2.24.0",
252 | "image-webpack-loader": "2.0.0",
253 | "imports-loader": "0.6.5",
254 | "json-loader": "0.5.4",
255 | "karma": "1.3.0",
256 | "karma-chrome-launcher": "2.0.0",
257 | "karma-coverage": "1.1.1",
258 | "karma-firefox-launcher": "1.0.0",
259 | "karma-ie-launcher": "1.0.0",
260 | "karma-mocha": "1.2.0",
261 | "karma-mocha-reporter": "2.2.0",
262 | "karma-safari-launcher": "1.0.0",
263 | "karma-sourcemap-loader": "0.3.7",
264 | "karma-webpack": "1.8.0",
265 | "lint-staged": "3.2.0",
266 | "mocha": "3.1.2",
267 | "ngrok": "2.2.3",
268 | "null-loader": "0.1.1",
269 | "offline-plugin": "3.4.2",
270 | "plop": "1.5.0",
271 | "pre-commit": "1.1.3",
272 | "psi": "2.0.4",
273 | "react-addons-test-utils": "15.3.2",
274 | "rimraf": "2.5.4",
275 | "shelljs": "0.7.5",
276 | "sinon": "2.0.0-pre",
277 | "style-loader": "0.13.1",
278 | "url-loader": "0.5.7",
279 | "webpack": "2.1.0-beta.25",
280 | "webpack-dev-middleware": "1.8.4",
281 | "webpack-hot-middleware": "2.13.1"
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/FrontEnd/server/index.js:
--------------------------------------------------------------------------------
1 | /* eslint consistent-return:0 */
2 |
3 | const express = require('express');
4 | const logger = require('./logger');
5 |
6 | const argv = require('minimist')(process.argv.slice(2));
7 | const setup = require('./middlewares/frontendMiddleware');
8 | const isDev = process.env.NODE_ENV !== 'production';
9 | const ngrok = (isDev && process.env.ENABLE_TUNNEL) || argv.tunnel ? require('ngrok') : false;
10 | const resolve = require('path').resolve;
11 | const app = express();
12 |
13 | // If you need a backend, e.g. an API, add your custom backend-specific middleware here
14 | // app.use('/api', myApi);
15 | var proxy = require('http-proxy-middleware');
16 | // proxy middleware options
17 | var options = {
18 | target: 'http://localhost:9000', // target host
19 | changeOrigin: true, // needed for virtual hosted sites
20 | pathRewrite: {
21 | '^/api/' : '/' // rewrite path
22 | }
23 | };
24 |
25 | // create the proxy (without context)
26 | var Proxy = proxy(options);
27 | app.use('/api', Proxy);
28 |
29 | // In production we need to pass these values in instead of relying on webpack
30 | setup(app, {
31 | outputPath: resolve(process.cwd(), 'build'),
32 | publicPath: '/',
33 | });
34 |
35 | // get the intended port number, use port 3000 if not provided
36 | const port = argv.port || process.env.PORT || 3000;
37 |
38 | // Start your app.
39 | app.listen(port, (err) => {
40 | if (err) {
41 | return logger.error(err.message);
42 | }
43 |
44 | // Connect to ngrok in dev mode
45 | if (ngrok) {
46 | ngrok.connect(port, (innerErr, url) => {
47 | if (innerErr) {
48 | return logger.error(innerErr);
49 | }
50 |
51 | logger.appStarted(port, url);
52 | });
53 | } else {
54 | logger.appStarted(port);
55 | }
56 | });
57 |
--------------------------------------------------------------------------------
/FrontEnd/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 |
--------------------------------------------------------------------------------
/FrontEnd/server/middlewares/frontendMiddleware.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const express = require('express');
3 | const path = require('path');
4 | const compression = require('compression');
5 | const pkg = require(path.resolve(process.cwd(), 'package.json'));
6 |
7 | // Dev middleware
8 | const addDevMiddlewares = (app, webpackConfig) => {
9 | const webpack = require('webpack');
10 | const webpackDevMiddleware = require('webpack-dev-middleware');
11 | const webpackHotMiddleware = require('webpack-hot-middleware');
12 | const compiler = webpack(webpackConfig);
13 | const middleware = webpackDevMiddleware(compiler, {
14 | noInfo: true,
15 | publicPath: webpackConfig.output.publicPath,
16 | silent: true,
17 | stats: 'errors-only',
18 | });
19 |
20 | app.use(middleware);
21 | app.use(webpackHotMiddleware(compiler));
22 |
23 | // Since webpackDevMiddleware uses memory-fs internally to store build
24 | // artifacts, we use it instead
25 | const fs = middleware.fileSystem;
26 |
27 | if (pkg.dllPlugin) {
28 | app.get(/\.dll\.js$/, (req, res) => {
29 | const filename = req.path.replace(/^\//, '');
30 | res.sendFile(path.join(process.cwd(), pkg.dllPlugin.path, filename));
31 | });
32 | }
33 |
34 | app.get('*', (req, res) => {
35 | fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => {
36 | if (err) {
37 | res.sendStatus(404);
38 | } else {
39 | res.send(file.toString());
40 | }
41 | });
42 | });
43 | };
44 |
45 | // Production middlewares
46 | const addProdMiddlewares = (app, options) => {
47 | const publicPath = options.publicPath || '/';
48 | const outputPath = options.outputPath || path.resolve(process.cwd(), 'build');
49 |
50 | // compression middleware compresses your server responses which makes them
51 | // smaller (applies also to assets). You can read more about that technique
52 | // and other good practices on official Express.js docs http://mxs.is/googmy
53 | app.use(compression());
54 | app.use(publicPath, express.static(outputPath));
55 |
56 | app.get('*', (req, res) => res.sendFile(path.resolve(outputPath, 'index.html')));
57 | };
58 |
59 | /**
60 | * Front-end middleware
61 | */
62 | module.exports = (app, options) => {
63 | const isProd = process.env.NODE_ENV === 'production';
64 |
65 | if (isProd) {
66 | addProdMiddlewares(app, options);
67 | } else {
68 | const webpackConfig = require('../../internals/webpack/webpack.dev.babel');
69 | addDevMiddlewares(app, webpackConfig);
70 | }
71 |
72 | return app;
73 | };
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 |
React.Js/Node.Js Project
6 |
7 |
8 | ## Quick start
9 |
10 | 1. FrontEnd part: `cd Frontend`
11 | Run `npm run setup` to install dependencies
12 | Run `npm start` for development
13 | Run `npm run build` for production
14 | Run `npm run test` for testing
15 | 1. BackEnd part: `cd Backend`
16 | Run `npm install` to install dependencies
17 | Run `npm start` for development
18 | Run `npm run prod` for production
19 | Run `npm run test` for testing
20 |
21 | ## Frontend
22 |
23 |
24 | - Authentication
25 | - Only User page needs authentication and `redux-auth-wrapper` high order component library is wrapped that page for authentication. Trying access to Users page without authentication, HOC will redirect to login page and login page validates token in sessionStorage if exist. If it not exist, client stays in login page. If re-login successful, re-direct again to desired page.
26 | - Home, Login and Contact pages validate jwt token in mount state.
27 | - Credentials are email:john@doe.com, password:secret
28 |
29 | - Styling
30 | - Styles are in global-styles.js file which provide css injection to `head` tag
31 |
32 | - Sagas
33 | - For side effects, `redux-saga` is implemented. All the actions are sync and sagas listens stores and actions and catch the desired requests then fires back the request results
34 |
35 | - Main saga is in App container.This saga runs all the time, but other sagas destroy themselves after page change.
-
36 |
37 |
- Proxy
38 | - All sagas uses '/api/*' paths for server. In development frontend node.js server rewrites all request to '/api/* -> /*' and backend server only handles '/*'.
39 |
40 |
41 | ## Backend
42 |
43 |
44 | - ES6
45 | - Babel compiler implemented for es6 features
46 | - Any DB system not included, only endpoints
47 | - /contact endpoint only logs datas, no saving or validation
48 |
49 |
50 | > Please note that there are only 2 React components and 3 endpoints have unit tests.
51 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hurkanyakay/reactjs-nodejs-rest-example/916b86e921753ba32db804006ba576a045bab9db/banner.png
--------------------------------------------------------------------------------