diff options
| author | Dayana31 <[email protected]> | 2022-04-21 17:27:08 -0500 |
|---|---|---|
| committer | Dayana31 <[email protected]> | 2022-04-21 17:27:08 -0500 |
| commit | 67c50667678dd0ce4709b29a854f6a47093a1ac5 (patch) | |
| tree | b6f9f39092ad54bf6b815984d32b37d7c7ca67ab /front/odiparpack/internals | |
| parent | 91140b24f0d49a9f89a080ee063e9eb023a4b73a (diff) | |
| parent | e13e630cd6e4fc0b1ff92098a28a770794c7bb9a (diff) | |
| download | DP1_project-67c50667678dd0ce4709b29a854f6a47093a1ac5.tar.gz DP1_project-67c50667678dd0ce4709b29a854f6a47093a1ac5.tar.bz2 DP1_project-67c50667678dd0ce4709b29a854f6a47093a1ac5.zip | |
Merge branch 'gabshr' into dayana
Diffstat (limited to 'front/odiparpack/internals')
50 files changed, 2073 insertions, 0 deletions
diff --git a/front/odiparpack/internals/.DS_Store b/front/odiparpack/internals/.DS_Store Binary files differnew file mode 100644 index 0000000..acaf70c --- /dev/null +++ b/front/odiparpack/internals/.DS_Store diff --git a/front/odiparpack/internals/config.js b/front/odiparpack/internals/config.js new file mode 100644 index 0000000..ffff2f0 --- /dev/null +++ b/front/odiparpack/internals/config.js @@ -0,0 +1,57 @@ +const { resolve } = require('path'); +const pullAll = require('lodash/pullAll'); +const uniq = require('lodash/uniq'); + +const ReactBoilerplate = { + // This refers to the react-boilerplate version this project is based on. + version: '3.6.0', + + /** + * The DLL Plugin provides a dramatic speed increase to webpack build and hot module reloading + * by caching the module metadata for all of our npm dependencies. We enable it by default + * in development. + * + * + * To disable the DLL Plugin, set this value to false. + */ + dllPlugin: { + defaults: { + /** + * we need to exclude dependencies which are not intended for the browser + * by listing them here. + */ + exclude: [ + '@date-io/date-fns', + 'chalk', + 'compression', + 'cross-env', + 'express', + 'ip', + 'minimist', + 'sanitize.css', + ], + + /** + * Specify any additional dependencies here. We include core-js and lodash + * since a lot of our dependencies depend on them and they get picked up by webpack. + */ + include: ['core-js', 'eventsource-polyfill', 'babel-polyfill', 'lodash'], + + // The path where the DLL manifest and bundle will get built + path: resolve('../node_modules/react-boilerplate-dlls'), + }, + + entry(pkg) { + const dependencyNames = Object.keys(pkg.dependencies); + const exclude = pkg.dllPlugin.exclude || ReactBoilerplate.dllPlugin.defaults.exclude; + const include = pkg.dllPlugin.include || ReactBoilerplate.dllPlugin.defaults.include; + const includeDependencies = uniq(dependencyNames.concat(include)); + + return { + reactBoilerplateDeps: pullAll(includeDependencies, exclude), + }; + }, + }, +}; + +module.exports = ReactBoilerplate; diff --git a/front/odiparpack/internals/generators/component/class.js.hbs b/front/odiparpack/internals/generators/component/class.js.hbs new file mode 100644 index 0000000..310239a --- /dev/null +++ b/front/odiparpack/internals/generators/component/class.js.hbs @@ -0,0 +1,31 @@ +/** + * + * {{ properCase name }} + * + */ + +import React from 'react'; +// import PropTypes from 'prop-types'; +// import styled from 'styled-components'; + +{{#if wantMessages}} +import { FormattedMessage } from 'react-intl'; +import messages from './messages'; +{{/if}} + +/* eslint-disable react/prefer-stateless-function */ +class {{ properCase name }} extends {{{ type }}} { + render() { + return ( + <div> + {{#if wantMessages}} + <FormattedMessage {...messages.header} /> + {{/if}} + </div> + ); + } +} + +{{ properCase name }}.propTypes = {}; + +export default {{ properCase name }}; diff --git a/front/odiparpack/internals/generators/component/index.js b/front/odiparpack/internals/generators/component/index.js new file mode 100644 index 0000000..4ac51d9 --- /dev/null +++ b/front/odiparpack/internals/generators/component/index.js @@ -0,0 +1,109 @@ +/** + * Component Generator + */ + +/* eslint strict: ["off"] */ + +'use strict'; + +const componentExists = require('../utils/componentExists'); + +module.exports = { + description: 'Add an unconnected component', + prompts: [ + { + type: 'list', + name: 'type', + message: 'Select the type of component', + default: 'Stateless Function', + choices: () => [ + 'Stateless Function', + 'React.PureComponent', + 'React.Component', + ], + }, + { + type: 'input', + name: 'name', + message: 'What should it be called?', + default: 'Button', + validate: value => { + if (/.+/.test(value)) { + return componentExists(value) + ? 'A component or container with this name already exists' + : true; + } + + return 'The name is required'; + }, + }, + { + type: 'confirm', + name: 'wantMessages', + default: true, + message: 'Do you want i18n messages (i.e. will this component use text)?', + }, + { + type: 'confirm', + name: 'wantLoadable', + default: false, + message: 'Do you want to load the component asynchronously?', + }, + ], + actions: data => { + // Generate index.js and index.test.js + let componentTemplate; + + switch (data.type) { + case 'Stateless Function': { + componentTemplate = './component/stateless.js.hbs'; + break; + } + default: { + componentTemplate = './component/class.js.hbs'; + } + } + + const actions = [ + { + type: 'add', + path: '../../app/components/{{properCase name}}/index.js', + templateFile: componentTemplate, + abortOnFail: true, + }, + { + type: 'add', + path: '../../app/components/{{properCase name}}/tests/index.test.js', + templateFile: './component/test.js.hbs', + abortOnFail: true, + }, + ]; + + // If they want a i18n messages file + if (data.wantMessages) { + actions.push({ + type: 'add', + path: '../../app/components/{{properCase name}}/messages.js', + templateFile: './component/messages.js.hbs', + abortOnFail: true, + }); + } + + // If want Loadable.js to load the component asynchronously + if (data.wantLoadable) { + actions.push({ + type: 'add', + path: '../../app/components/{{properCase name}}/Loadable.js', + templateFile: './component/loadable.js.hbs', + abortOnFail: true, + }); + } + + actions.push({ + type: 'prettify', + path: '/components/', + }); + + return actions; + }, +}; diff --git a/front/odiparpack/internals/generators/component/loadable.js.hbs b/front/odiparpack/internals/generators/component/loadable.js.hbs new file mode 100644 index 0000000..889bbf6 --- /dev/null +++ b/front/odiparpack/internals/generators/component/loadable.js.hbs @@ -0,0 +1,12 @@ +/** + * + * Asynchronously loads the component for {{ properCase name }} + * + */ + +import Loadable from 'react-loadable'; + +export default Loadable({ + loader: () => import('./index'), + loading: () => null, +}); diff --git a/front/odiparpack/internals/generators/component/messages.js.hbs b/front/odiparpack/internals/generators/component/messages.js.hbs new file mode 100644 index 0000000..f73ee90 --- /dev/null +++ b/front/odiparpack/internals/generators/component/messages.js.hbs @@ -0,0 +1,14 @@ +/* + * {{ properCase name }} Messages + * + * This contains all the text for the {{ properCase name }} component. + */ + +import { defineMessages } from 'react-intl'; + +export default defineMessages({ + header: { + id: 'app.components.{{ properCase name }}.header', + defaultMessage: 'This is the {{ properCase name}} component !', + }, +}); diff --git a/front/odiparpack/internals/generators/component/stateless.js.hbs b/front/odiparpack/internals/generators/component/stateless.js.hbs new file mode 100644 index 0000000..04ba918 --- /dev/null +++ b/front/odiparpack/internals/generators/component/stateless.js.hbs @@ -0,0 +1,28 @@ +/** + * + * {{ properCase name }} + * + */ + +import React from 'react'; +// import PropTypes from 'prop-types'; +// import styled from 'styled-components'; + +{{#if wantMessages}} +import { FormattedMessage } from 'react-intl'; +import messages from './messages'; +{{/if}} + +function {{ properCase name }}() { + return ( + <div> + {{#if wantMessages}} + <FormattedMessage {...messages.header} /> + {{/if}} + </div> + ); +} + +{{ properCase name }}.propTypes = {}; + +export default {{ properCase name }}; diff --git a/front/odiparpack/internals/generators/component/test.js.hbs b/front/odiparpack/internals/generators/component/test.js.hbs new file mode 100644 index 0000000..42c0e37 --- /dev/null +++ b/front/odiparpack/internals/generators/component/test.js.hbs @@ -0,0 +1,10 @@ +// import React from 'react'; +// import { shallow } from 'enzyme'; + +// import {{ properCase name }} from '../index'; + +describe('<{{ properCase name }} />', () => { + it('Expect to have unit tests specified', () => { + expect(true).toEqual(false); + }); +}); diff --git a/front/odiparpack/internals/generators/container/actions.js.hbs b/front/odiparpack/internals/generators/container/actions.js.hbs new file mode 100644 index 0000000..f48b10b --- /dev/null +++ b/front/odiparpack/internals/generators/container/actions.js.hbs @@ -0,0 +1,13 @@ +/* + * + * {{ properCase name }} actions + * + */ + +import { DEFAULT_ACTION } from './constants'; + +export function defaultAction() { + return { + type: DEFAULT_ACTION, + }; +} diff --git a/front/odiparpack/internals/generators/container/actions.test.js.hbs b/front/odiparpack/internals/generators/container/actions.test.js.hbs new file mode 100644 index 0000000..89ca2c7 --- /dev/null +++ b/front/odiparpack/internals/generators/container/actions.test.js.hbs @@ -0,0 +1,13 @@ +import { defaultAction } from '../actions'; +import { DEFAULT_ACTION } from '../constants'; + +describe('{{ properCase name }} actions', () => { + describe('Default Action', () => { + it('has a type of DEFAULT_ACTION', () => { + const expected = { + type: DEFAULT_ACTION, + }; + expect(defaultAction()).toEqual(expected); + }); + }); +}); diff --git a/front/odiparpack/internals/generators/container/class.js.hbs b/front/odiparpack/internals/generators/container/class.js.hbs new file mode 100644 index 0000000..95e265c --- /dev/null +++ b/front/odiparpack/internals/generators/container/class.js.hbs @@ -0,0 +1,93 @@ +/** + * + * {{properCase name }} + * + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +{{#if wantHeaders}} +import { Helmet } from 'react-helmet'; +{{/if}} +{{#if wantMessages}} +import { FormattedMessage } from 'react-intl'; +{{/if}} +{{#if wantActionsAndReducer}} +import { createStructuredSelector } from 'reselect'; +{{/if}} +import { compose } from 'redux'; + +{{#if wantSaga}} +import injectSaga from 'utils/injectSaga'; +{{/if}} +{{#if wantActionsAndReducer}} +import injectReducer from 'utils/injectReducer'; +import makeSelect{{properCase name}} from './selectors'; +import reducer from './reducer'; +{{/if}} +{{#if wantSaga}} +import saga from './saga'; +{{/if}} +{{#if wantMessages}} +import messages from './messages'; +{{/if}} + +/* eslint-disable react/prefer-stateless-function */ +export class {{ properCase name }} extends {{{ type }}} { + render() { + return ( + <div> + {{#if wantHeaders}} + <Helmet> + <title>{{properCase name}}</title> + <meta name="description" content="Description of {{properCase name}}" /> + </Helmet> + {{/if}} + {{#if wantMessages}} + <FormattedMessage {...messages.header} /> + {{/if}} + </div> + ); + } +} + +{{ properCase name }}.propTypes = { + dispatch: PropTypes.func.isRequired, +}; + +{{#if wantActionsAndReducer}} +const mapStateToProps = createStructuredSelector({ + {{ lowerCase name }}: makeSelect{{properCase name}}(), +}); +{{/if}} + +function mapDispatchToProps(dispatch) { + return { + dispatch, + }; +} + +{{#if wantActionsAndReducer}} +const withConnect = connect( + mapStateToProps, + mapDispatchToProps +); + +const withReducer = injectReducer({ key: '{{ camelCase name }}', reducer }); +{{else}} +const withConnect = connect(null, mapDispatchToProps); +{{/if}} +{{#if wantSaga}} +const withSaga = injectSaga({ key: '{{ camelCase name }}', saga }); +{{/if}} + +export default compose( +{{#if wantActionsAndReducer}} + withReducer, +{{/if}} +{{#if wantSaga}} + withSaga, +{{/if}} + withConnect +)({{ properCase name }}); diff --git a/front/odiparpack/internals/generators/container/constants.js.hbs b/front/odiparpack/internals/generators/container/constants.js.hbs new file mode 100644 index 0000000..0a37bd1 --- /dev/null +++ b/front/odiparpack/internals/generators/container/constants.js.hbs @@ -0,0 +1,7 @@ +/* + * + * {{ properCase name }} constants + * + */ + +export const DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION'; diff --git a/front/odiparpack/internals/generators/container/index.js b/front/odiparpack/internals/generators/container/index.js new file mode 100644 index 0000000..572a161 --- /dev/null +++ b/front/odiparpack/internals/generators/container/index.js @@ -0,0 +1,194 @@ +/** + * Container Generator + */ + +const componentExists = require('../utils/componentExists'); + +module.exports = { + description: 'Add a container component', + prompts: [ + { + type: 'list', + name: 'type', + message: 'Select the base component type:', + default: 'Stateless Function', + choices: () => [ + 'Stateless Function', + 'React.PureComponent', + 'React.Component', + ], + }, + { + type: 'input', + name: 'name', + message: 'What should it be called?', + default: 'Form', + validate: value => { + if (/.+/.test(value)) { + return componentExists(value) + ? 'A component or container with this name already exists' + : true; + } + + return 'The name is required'; + }, + }, + { + type: 'confirm', + name: 'wantHeaders', + default: false, + message: 'Do you want headers?', + }, + { + type: 'confirm', + name: 'wantActionsAndReducer', + default: true, + message: + 'Do you want an actions/constants/selectors/reducer tuple for this container?', + }, + { + type: 'confirm', + name: 'wantSaga', + default: true, + message: 'Do you want sagas for asynchronous flows? (e.g. fetching data)', + }, + { + type: 'confirm', + name: 'wantMessages', + default: true, + message: 'Do you want i18n messages (i.e. will this component use text)?', + }, + { + type: 'confirm', + name: 'wantLoadable', + default: true, + message: 'Do you want to load resources asynchronously?', + }, + ], + actions: data => { + // Generate index.js and index.test.js + var componentTemplate; // eslint-disable-line no-var + + switch (data.type) { + case 'Stateless Function': { + componentTemplate = './container/stateless.js.hbs'; + break; + } + default: { + componentTemplate = './container/class.js.hbs'; + } + } + + const actions = [ + { + type: 'add', + path: '../../app/containers/{{properCase name}}/index.js', + templateFile: componentTemplate, + abortOnFail: true, + }, + { + type: 'add', + path: '../../app/containers/{{properCase name}}/tests/index.test.js', + templateFile: './container/test.js.hbs', + abortOnFail: true, + }, + ]; + + // If component wants messages + if (data.wantMessages) { + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/messages.js', + templateFile: './container/messages.js.hbs', + abortOnFail: true, + }); + } + + // If they want actions and a reducer, generate actions.js, constants.js, + // reducer.js and the corresponding tests for actions and the reducer + if (data.wantActionsAndReducer) { + // Actions + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/actions.js', + templateFile: './container/actions.js.hbs', + abortOnFail: true, + }); + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/tests/actions.test.js', + templateFile: './container/actions.test.js.hbs', + abortOnFail: true, + }); + + // Constants + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/constants.js', + templateFile: './container/constants.js.hbs', + abortOnFail: true, + }); + + // Selectors + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/selectors.js', + templateFile: './container/selectors.js.hbs', + abortOnFail: true, + }); + actions.push({ + type: 'add', + path: + '../../app/containers/{{properCase name}}/tests/selectors.test.js', + templateFile: './container/selectors.test.js.hbs', + abortOnFail: true, + }); + + // Reducer + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/reducer.js', + templateFile: './container/reducer.js.hbs', + abortOnFail: true, + }); + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/tests/reducer.test.js', + templateFile: './container/reducer.test.js.hbs', + abortOnFail: true, + }); + } + + // Sagas + if (data.wantSaga) { + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/saga.js', + templateFile: './container/saga.js.hbs', + abortOnFail: true, + }); + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/tests/saga.test.js', + templateFile: './container/saga.test.js.hbs', + abortOnFail: true, + }); + } + + if (data.wantLoadable) { + actions.push({ + type: 'add', + path: '../../app/containers/{{properCase name}}/Loadable.js', + templateFile: './component/loadable.js.hbs', + abortOnFail: true, + }); + } + + actions.push({ + type: 'prettify', + path: '/containers/', + }); + + return actions; + }, +}; diff --git a/front/odiparpack/internals/generators/container/index.js.hbs b/front/odiparpack/internals/generators/container/index.js.hbs new file mode 100644 index 0000000..39117a1 --- /dev/null +++ b/front/odiparpack/internals/generators/container/index.js.hbs @@ -0,0 +1,66 @@ +/* + * + * {{properCase name }} + * + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +{{#if wantHeaders}} +import { Helmet } from 'react-helmet'; +{{/if}} +{{#if wantMessages}} +import { FormattedMessage } from 'react-intl'; +{{/if}} +{{#if wantActionsAndReducer}} +import { createStructuredSelector } from 'reselect'; +import makeSelect{{properCase name}} from './selectors'; +{{/if}} +{{#if wantMessages}} +import messages from './messages'; +{{/if}} + +/* eslint-disable react/prefer-stateless-function */ +export class {{ properCase name }} extends React.{{{ component }}} { + render() { + return ( + <div> + {{#if wantHeaders}} + <Helmet> + <title>{{properCase name}}</title> + <meta + name="description" + content="Description of {{properCase name}}" + /> + </Helmet> + {{/if}} + {{#if wantMessages}} + <FormattedMessage {...messages.header} /> + {{/if}} + </div> + ); + } +} + +{{ properCase name }}.propTypes = { + dispatch: PropTypes.func.isRequired, +}; + +{{#if wantActionsAndReducer}} +const mapStateToProps = createStructuredSelector({ + {{name}}: makeSelect{{properCase name}}(), +}); +{{/if}} + +function mapDispatchToProps(dispatch) { + return { + dispatch, + }; +} + +{{#if wantActionsAndReducer}} +export default connect(mapStateToProps, mapDispatchToProps)({{ properCase name }}); +{{else}} +export default connect(null, mapDispatchToProps)({{ properCase name }}); +{{/if}} diff --git a/front/odiparpack/internals/generators/container/messages.js.hbs b/front/odiparpack/internals/generators/container/messages.js.hbs new file mode 100644 index 0000000..9500820 --- /dev/null +++ b/front/odiparpack/internals/generators/container/messages.js.hbs @@ -0,0 +1,14 @@ +/* + * {{properCase name }} Messages + * + * This contains all the text for the {{properCase name }} component. + */ + +import { defineMessages } from 'react-intl'; + +export default defineMessages({ + header: { + id: 'app.containers.{{properCase name }}.header', + defaultMessage: 'This is {{properCase name}} container !', + }, +}); diff --git a/front/odiparpack/internals/generators/container/reducer.js.hbs b/front/odiparpack/internals/generators/container/reducer.js.hbs new file mode 100644 index 0000000..5af9e12 --- /dev/null +++ b/front/odiparpack/internals/generators/container/reducer.js.hbs @@ -0,0 +1,21 @@ +/* + * + * {{ properCase name }} reducer + * + */ + +import { fromJS } from 'immutable'; +import { DEFAULT_ACTION } from './constants'; + +export const initialState = fromJS({}); + +function {{ camelCase name }}Reducer(state = initialState, action) { + switch (action.type) { + case DEFAULT_ACTION: + return state; + default: + return state; + } +} + +export default {{ camelCase name }}Reducer; diff --git a/front/odiparpack/internals/generators/container/reducer.test.js.hbs b/front/odiparpack/internals/generators/container/reducer.test.js.hbs new file mode 100644 index 0000000..5ac10e3 --- /dev/null +++ b/front/odiparpack/internals/generators/container/reducer.test.js.hbs @@ -0,0 +1,8 @@ +import { fromJS } from 'immutable'; +import {{ camelCase name }}Reducer from '../reducer'; + +describe('{{ camelCase name }}Reducer', () => { + it('returns the initial state', () => { + expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({})); + }); +}); diff --git a/front/odiparpack/internals/generators/container/saga.js.hbs b/front/odiparpack/internals/generators/container/saga.js.hbs new file mode 100644 index 0000000..fc475fd --- /dev/null +++ b/front/odiparpack/internals/generators/container/saga.js.hbs @@ -0,0 +1,6 @@ +// import { take, call, put, select } from 'redux-saga/effects'; + +// Individual exports for testing +export default function* defaultSaga() { + // See example in containers/HomePage/saga.js +} diff --git a/front/odiparpack/internals/generators/container/saga.test.js.hbs b/front/odiparpack/internals/generators/container/saga.test.js.hbs new file mode 100644 index 0000000..9fcf7f5 --- /dev/null +++ b/front/odiparpack/internals/generators/container/saga.test.js.hbs @@ -0,0 +1,15 @@ +/** + * Test sagas + */ + +/* eslint-disable redux-saga/yield-effects */ +// import { take, call, put, select } from 'redux-saga/effects'; +// import { defaultSaga } from '../saga'; + +// const generator = defaultSaga(); + +describe('defaultSaga Saga', () => { + it('Expect to have unit tests specified', () => { + expect(true).toEqual(false); + }); +}); diff --git a/front/odiparpack/internals/generators/container/selectors.js.hbs b/front/odiparpack/internals/generators/container/selectors.js.hbs new file mode 100644 index 0000000..55e5a04 --- /dev/null +++ b/front/odiparpack/internals/generators/container/selectors.js.hbs @@ -0,0 +1,23 @@ +import { createSelector } from 'reselect'; +import { initialState } from './reducer'; + +/** + * Direct selector to the {{ camelCase name }} state domain + */ + +const select{{ properCase name }}Domain = state => + state.get('{{ camelCase name }}', initialState); + +/** + * Other specific selectors + */ + +/** + * Default selector used by {{ properCase name }} + */ + +const makeSelect{{ properCase name }} = () => + createSelector(select{{ properCase name }}Domain, substate => substate.toJS()); + +export default makeSelect{{ properCase name }}; +export { select{{ properCase name }}Domain }; diff --git a/front/odiparpack/internals/generators/container/selectors.test.js.hbs b/front/odiparpack/internals/generators/container/selectors.test.js.hbs new file mode 100644 index 0000000..4ad7530 --- /dev/null +++ b/front/odiparpack/internals/generators/container/selectors.test.js.hbs @@ -0,0 +1,8 @@ +// import { fromJS } from 'immutable'; +// import { select{{ properCase name }}Domain } from '../selectors'; + +describe('select{{ properCase name }}Domain', () => { + it('Expect to have unit tests specified', () => { + expect(true).toEqual(false); + }); +}); diff --git a/front/odiparpack/internals/generators/container/stateless.js.hbs b/front/odiparpack/internals/generators/container/stateless.js.hbs new file mode 100644 index 0000000..2f7994a --- /dev/null +++ b/front/odiparpack/internals/generators/container/stateless.js.hbs @@ -0,0 +1,87 @@ +/** + * + * {{properCase name }} + * + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +{{#if wantHeaders}} +import { Helmet } from 'react-helmet'; +{{/if}} +{{#if wantMessages}} +import { FormattedMessage } from 'react-intl'; +{{/if}} +{{#if wantActionsAndReducer}} +import { createStructuredSelector } from 'reselect'; +{{/if}} +import { compose } from 'redux'; + +{{#if wantSaga}} +import injectSaga from 'utils/injectSaga'; +{{/if}} +{{#if wantActionsAndReducer}} +import injectReducer from 'utils/injectReducer'; +import makeSelect{{properCase name}} from './selectors'; +import reducer from './reducer'; +{{/if}} +{{#if wantSaga}} +import saga from './saga'; +{{/if}} +{{#if wantMessages}} +import messages from './messages'; +{{/if}} + +function {{ properCase name }}() { + return ( + <div> + {{#if wantHeaders}} + <Helmet> + <title>{{properCase name}}</title> + <meta name="description" content="Description of {{properCase name}}" /> + </Helmet> + {{/if}} + {{#if wantMessages}} + <FormattedMessage {...messages.header} /> + {{/if}} + </div> + ); +} + +{{ properCase name }}.propTypes = { + dispatch: PropTypes.func.isRequired, +}; + +{{#if wantActionsAndReducer}} +const mapStateToProps = createStructuredSelector({ + {{ lowerCase name }}: makeSelect{{properCase name}}(), +}); +{{/if}} + +function mapDispatchToProps(dispatch) { + return { + dispatch, + }; +} + +{{#if wantActionsAndReducer}} +const withConnect = connect(mapStateToProps, mapDispatchToProps); + +const withReducer = injectReducer({ key: '{{ lowerCase name }}', reducer }); +{{else}} +const withConnect = connect(null, mapDispatchToProps); +{{/if}} +{{#if wantSaga}} +const withSaga = injectSaga({ key: '{{ lowerCase name }}', saga }); +{{/if}} + +export default compose( +{{#if wantActionsAndReducer}} + withReducer, +{{/if}} +{{#if wantSaga}} + withSaga, +{{/if}} + withConnect, +)({{ properCase name }}); diff --git a/front/odiparpack/internals/generators/container/test.js.hbs b/front/odiparpack/internals/generators/container/test.js.hbs new file mode 100644 index 0000000..8427a4a --- /dev/null +++ b/front/odiparpack/internals/generators/container/test.js.hbs @@ -0,0 +1,10 @@ +// import React from 'react'; +// import { shallow } from 'enzyme'; + +// import { {{ properCase name }} } from '../index'; + +describe('<{{ properCase name }} />', () => { + it('Expect to have unit tests specified', () => { + expect(true).toEqual(false); + }); +}); diff --git a/front/odiparpack/internals/generators/index.js b/front/odiparpack/internals/generators/index.js new file mode 100644 index 0000000..e00eeea --- /dev/null +++ b/front/odiparpack/internals/generators/index.js @@ -0,0 +1,41 @@ +/** + * generator/index.js + * + * Exports the generators so plop knows them + */ + +const fs = require('fs'); +const path = require('path'); +const { exec } = require('child_process'); +const componentGenerator = require('./component/index.js'); +const containerGenerator = require('./container/index.js'); +const languageGenerator = require('./language/index.js'); + +module.exports = plop => { + plop.setGenerator('component', componentGenerator); + plop.setGenerator('container', containerGenerator); + plop.setGenerator('language', languageGenerator); + plop.addHelper('directory', comp => { + try { + fs.accessSync( + path.join(__dirname, `../../app/containers/${comp}`), + fs.F_OK, + ); + return `containers/${comp}`; + } catch (e) { + return `components/${comp}`; + } + }); + plop.addHelper('curly', (object, open) => (open ? '{' : '}')); + plop.setActionType('prettify', (answers, config) => { + const folderPath = `${path.join( + __dirname, + '/../../app/', + config.path, + plop.getHelper('properCase')(answers.name), + '**.js', + )}`; + exec(`npm run prettify -- "${folderPath}"`); + return folderPath; + }); +}; diff --git a/front/odiparpack/internals/generators/language/add-locale-data.hbs b/front/odiparpack/internals/generators/language/add-locale-data.hbs new file mode 100644 index 0000000..80727c7 --- /dev/null +++ b/front/odiparpack/internals/generators/language/add-locale-data.hbs @@ -0,0 +1 @@ +$1addLocaleData({{language}}LocaleData); diff --git a/front/odiparpack/internals/generators/language/app-locale.hbs b/front/odiparpack/internals/generators/language/app-locale.hbs new file mode 100644 index 0000000..08753eb --- /dev/null +++ b/front/odiparpack/internals/generators/language/app-locale.hbs @@ -0,0 +1 @@ +$1 '{{language}}', diff --git a/front/odiparpack/internals/generators/language/format-translation-messages.hbs b/front/odiparpack/internals/generators/language/format-translation-messages.hbs new file mode 100644 index 0000000..143601f --- /dev/null +++ b/front/odiparpack/internals/generators/language/format-translation-messages.hbs @@ -0,0 +1 @@ +$1 {{language}}: formatTranslationMessages('{{language}}', {{language}}TranslationMessages), diff --git a/front/odiparpack/internals/generators/language/index.js b/front/odiparpack/internals/generators/language/index.js new file mode 100644 index 0000000..aaf91e8 --- /dev/null +++ b/front/odiparpack/internals/generators/language/index.js @@ -0,0 +1,92 @@ +/** + * Language Generator + */ +const fs = require('fs'); +const { exec } = require('child_process'); + +function languageIsSupported(language) { + try { + fs.accessSync(`app/translations/${language}.json`, fs.F_OK); + return true; + } catch (e) { + return false; + } +} + +module.exports = { + description: 'Add a language', + prompts: [ + { + type: 'input', + name: 'language', + message: + 'What is the language you want to add i18n support for (e.g. "fr", "de")?', + default: 'fr', + validate: value => { + if (/.+/.test(value) && value.length === 2) { + return languageIsSupported(value) + ? `The language "${value}" is already supported.` + : true; + } + + return '2 character language specifier is required'; + }, + }, + ], + + actions: () => { + const actions = []; + actions.push({ + type: 'modify', + path: '../../app/i18n.js', + pattern: /(const ..LocaleData = require\('react-intl\/locale-data\/..'\);\n)+/g, + templateFile: './language/intl-locale-data.hbs', + }); + actions.push({ + type: 'modify', + path: '../../app/i18n.js', + pattern: /(\s+'[a-z]+',\n)(?!.*\s+'[a-z]+',)/g, + templateFile: './language/app-locale.hbs', + }); + actions.push({ + type: 'modify', + path: '../../app/i18n.js', + pattern: /(const ..TranslationMessages = require\('\.\/translations\/..\.json'\);\n)(?!const ..TranslationMessages = require\('\.\/translations\/..\.json'\);\n)/g, + templateFile: './language/translation-messages.hbs', + }); + actions.push({ + type: 'modify', + path: '../../app/i18n.js', + pattern: /(addLocaleData\([a-z]+LocaleData\);\n)(?!.*addLocaleData\([a-z]+LocaleData\);)/g, + templateFile: './language/add-locale-data.hbs', + }); + actions.push({ + type: 'modify', + path: '../../app/i18n.js', + pattern: /([a-z]+:\sformatTranslationMessages\('[a-z]+',\s[a-z]+TranslationMessages\),\n)(?!.*[a-z]+:\sformatTranslationMessages\('[a-z]+',\s[a-z]+TranslationMessages\),)/g, + templateFile: './language/format-translation-messages.hbs', + }); + actions.push({ + type: 'add', + path: '../../app/translations/{{language}}.json', + templateFile: './language/translations-json.hbs', + abortOnFail: true, + }); + actions.push({ + type: 'modify', + path: '../../app/app.js', + pattern: /(import\('intl\/locale-data\/jsonp\/[a-z]+\.js'\),\n)(?!.*import\('intl\/locale-data\/jsonp\/[a-z]+\.js'\),)/g, + templateFile: './language/polyfill-intl-locale.hbs', + }); + actions.push(() => { + const cmd = 'npm run extract-intl'; + exec(cmd, (err, result) => { + if (err) throw err; + process.stdout.write(result); + }); + return 'modify translation messages'; + }); + + return actions; + }, +}; diff --git a/front/odiparpack/internals/generators/language/intl-locale-data.hbs b/front/odiparpack/internals/generators/language/intl-locale-data.hbs new file mode 100644 index 0000000..7114f82 --- /dev/null +++ b/front/odiparpack/internals/generators/language/intl-locale-data.hbs @@ -0,0 +1 @@ +$&const {{language}}LocaleData = require('react-intl/locale-data/{{language}}'); diff --git a/front/odiparpack/internals/generators/language/polyfill-intl-locale.hbs b/front/odiparpack/internals/generators/language/polyfill-intl-locale.hbs new file mode 100644 index 0000000..139b74c --- /dev/null +++ b/front/odiparpack/internals/generators/language/polyfill-intl-locale.hbs @@ -0,0 +1 @@ +$1 import('intl/locale-data/jsonp/{{language}}.js'), diff --git a/front/odiparpack/internals/generators/language/translation-messages.hbs b/front/odiparpack/internals/generators/language/translation-messages.hbs new file mode 100644 index 0000000..6764c6c --- /dev/null +++ b/front/odiparpack/internals/generators/language/translation-messages.hbs @@ -0,0 +1 @@ +$1const {{language}}TranslationMessages = require('./translations/{{language}}.json'); diff --git a/front/odiparpack/internals/generators/language/translations-json.hbs b/front/odiparpack/internals/generators/language/translations-json.hbs new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/front/odiparpack/internals/generators/language/translations-json.hbs @@ -0,0 +1 @@ +[] diff --git a/front/odiparpack/internals/generators/utils/componentExists.js b/front/odiparpack/internals/generators/utils/componentExists.js new file mode 100644 index 0000000..8cd13d9 --- /dev/null +++ b/front/odiparpack/internals/generators/utils/componentExists.js @@ -0,0 +1,21 @@ +/** + * componentExists + * + * Check whether the given component exist in either the components or containers directory + */ + +const fs = require('fs'); +const path = require('path'); +const pageComponents = fs.readdirSync( + path.join(__dirname, '../../../app/components'), +); +const pageContainers = fs.readdirSync( + path.join(__dirname, '../../../app/containers'), +); +const components = pageComponents.concat(pageContainers); + +function componentExists(comp) { + return components.indexOf(comp) >= 0; +} + +module.exports = componentExists; diff --git a/front/odiparpack/internals/mocks/cssModule.js b/front/odiparpack/internals/mocks/cssModule.js new file mode 100644 index 0000000..52cb086 --- /dev/null +++ b/front/odiparpack/internals/mocks/cssModule.js @@ -0,0 +1 @@ +module.exports = 'CSS_MODULE'; diff --git a/front/odiparpack/internals/mocks/image.js b/front/odiparpack/internals/mocks/image.js new file mode 100644 index 0000000..a566a76 --- /dev/null +++ b/front/odiparpack/internals/mocks/image.js @@ -0,0 +1 @@ +module.exports = 'IMAGE_MOCK'; diff --git a/front/odiparpack/internals/scripts/analyze.js b/front/odiparpack/internals/scripts/analyze.js new file mode 100644 index 0000000..2144e5a --- /dev/null +++ b/front/odiparpack/internals/scripts/analyze.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +const shelljs = require('shelljs'); +const animateProgress = require('./helpers/progress'); +const chalk = require('chalk'); +const addCheckMark = require('./helpers/checkmark'); + +const progress = animateProgress('Generating stats'); + +// Generate stats.json file with webpack +shelljs.exec( + 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json', + addCheckMark.bind(null, callback), // Output a checkmark on completion +); + +// Called after webpack has finished generating the stats.json file +function callback() { + clearInterval(progress); + process.stdout.write( + '\n\nOpen ' + + chalk.magenta('http://webpack.github.io/analyse/') + + ' in your browser and upload the stats.json file!' + + chalk.blue( + '\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n', + ), + ); +} diff --git a/front/odiparpack/internals/scripts/clean.js b/front/odiparpack/internals/scripts/clean.js new file mode 100644 index 0000000..52a93e0 --- /dev/null +++ b/front/odiparpack/internals/scripts/clean.js @@ -0,0 +1,63 @@ +const shell = require('shelljs'); +const addCheckMark = require('./helpers/checkmark.js'); + +if (!shell.which('git')) { + shell.echo('Sorry, this script requires git'); + shell.exit(1); +} + +if (!shell.test('-e', 'internals/templates')) { + shell.echo('The example is deleted already.'); + shell.exit(1); +} + +process.stdout.write('Cleanup started...'); + +// Reuse existing LanguageProvider and i18n tests +shell.mv( + 'app/containers/LanguageProvider/tests', + 'internals/templates/containers/LanguageProvider', +); +shell.cp('app/tests/i18n.test.js', 'internals/templates/tests/i18n.test.js'); + +// Cleanup components/ +shell.rm('-rf', 'app/components/*'); + +// Handle containers/ +shell.rm('-rf', 'app/containers'); +shell.mv('internals/templates/containers', 'app'); + +// Handle tests/ +shell.mv('internals/templates/tests', 'app'); + +// Handle translations/ +shell.rm('-rf', 'app/translations'); +shell.mv('internals/templates/translations', 'app'); + +// Handle utils/ +shell.rm('-rf', 'app/utils'); +shell.mv('internals/templates/utils', 'app'); + +// Replace the files in the root app/ folder +shell.cp('internals/templates/app.js', 'app/app.js'); +shell.cp('internals/templates/global-styles.js', 'app/global-styles.js'); +shell.cp('internals/templates/i18n.js', 'app/i18n.js'); +shell.cp('internals/templates/index.html', 'app/index.html'); +shell.cp('internals/templates/reducers.js', 'app/reducers.js'); +shell.cp('internals/templates/configureStore.js', 'app/configureStore.js'); + +// Remove the templates folder +shell.rm('-rf', 'internals/templates'); + +addCheckMark(); + +// Commit the changes +if ( + shell.exec('git add . --all && git commit -qm "Remove default example"') + .code !== 0 +) { + shell.echo('\nError: Git commit failed'); + shell.exit(1); +} + +shell.echo('\nCleanup done. Happy Coding!!!'); diff --git a/front/odiparpack/internals/scripts/dependencies.js b/front/odiparpack/internals/scripts/dependencies.js new file mode 100644 index 0000000..4f9f1ed --- /dev/null +++ b/front/odiparpack/internals/scripts/dependencies.js @@ -0,0 +1,52 @@ +// No need to build the DLL in production +if (process.env.NODE_ENV === 'production') { + process.exit(0); +} + +require('shelljs/global'); + +const path = require('path'); +const fs = require('fs'); +const exists = fs.existsSync; +const writeFile = fs.writeFileSync; + +const defaults = require('lodash/defaultsDeep'); +const pkg = require(path.join(process.cwd(), 'package.json')); +const config = require('../config'); +const dllConfig = defaults(pkg.dllPlugin, config.dllPlugin.defaults); +const outputPath = path.join(process.cwd(), dllConfig.path); +const dllManifestPath = path.join(outputPath, 'package.json'); + +/** + * I use node_modules/react-boilerplate-dlls by default just because + * it isn't going to be version controlled and babel wont try to parse it. + */ +mkdir('-p', outputPath); + +echo('Building the Webpack DLL...'); + +/** + * Create a manifest so npm install doesn't warn us + */ +if (!exists(dllManifestPath)) { + writeFile( + dllManifestPath, + JSON.stringify( + defaults({ + name: 'react-boilerplate-dlls', + private: true, + author: pkg.author, + repository: pkg.repository, + version: pkg.version, + }), + null, + 2, + ), + 'utf8', + ); +} + +// the BUILDING_DLL env var is set to avoid confusing the development environment +exec( + 'cross-env BUILDING_DLL=true webpack --display-chunks --color --config internals/webpack/webpack.dll.babel.js --hide-modules', +); diff --git a/front/odiparpack/internals/scripts/extract-intl.js b/front/odiparpack/internals/scripts/extract-intl.js new file mode 100644 index 0000000..087b04e --- /dev/null +++ b/front/odiparpack/internals/scripts/extract-intl.js @@ -0,0 +1,187 @@ +/* eslint-disable */ +/** + * This script will extract the internationalization messages from all components + and package them in the translation json files in the translations file. + */ +const fs = require('fs'); +const nodeGlob = require('glob'); +const transform = require('babel-core').transform; + +const animateProgress = require('./helpers/progress'); +const addCheckmark = require('./helpers/checkmark'); + +const pkg = require('../../package.json'); +const presets = pkg.babel.presets; +const plugins = pkg.babel.plugins || []; + +const i18n = require('../../app/i18n'); + +const DEFAULT_LOCALE = i18n.DEFAULT_LOCALE; + +require('shelljs/global'); + +// Glob to match all js files except test files +const FILES_TO_PARSE = 'app/**/!(*.test).js'; +const locales = i18n.appLocales; + +const newLine = () => process.stdout.write('\n'); + +// Progress Logger +let progress; +const task = message => { + progress = animateProgress(message); + process.stdout.write(message); + + return error => { + if (error) { + process.stderr.write(error); + } + clearTimeout(progress); + return addCheckmark(() => newLine()); + }; +}; + +// Wrap async functions below into a promise +const glob = pattern => + new Promise((resolve, reject) => { + nodeGlob( + pattern, + (error, value) => (error ? reject(error) : resolve(value)), + ); + }); + +const readFile = fileName => + new Promise((resolve, reject) => { + fs.readFile( + fileName, + (error, value) => (error ? reject(error) : resolve(value)), + ); + }); + +const writeFile = (fileName, data) => + new Promise((resolve, reject) => { + fs.writeFile( + fileName, + data, + (error, value) => (error ? reject(error) : resolve(value)), + ); + }); + +// Store existing translations into memory +const oldLocaleMappings = []; +const localeMappings = []; + +// Loop to run once per locale +for (const locale of locales) { + oldLocaleMappings[locale] = {}; + localeMappings[locale] = {}; + // File to store translation messages into + const translationFileName = `app/translations/${locale}.json`; + try { + // Parse the old translation message JSON files + const messages = JSON.parse(fs.readFileSync(translationFileName)); + const messageKeys = Object.keys(messages); + for (const messageKey of messageKeys) { + oldLocaleMappings[locale][messageKey] = messages[messageKey]; + } + } catch (error) { + if (error.code !== 'ENOENT') { + process.stderr.write( + `There was an error loading this translation file: ${translationFileName} + \n${error}`, + ); + } + } +} + +/* push `react-intl` plugin to the existing plugins that are already configured in `package.json` + Example: + ``` + "babel": { + "plugins": [ + ["transform-object-rest-spread", { "useBuiltIns": true }] + ], + "presets": [ + "env", + "react" + ] + } + ``` +*/ +plugins.push(['react-intl']); + +const extractFromFile = fileName => { + return readFile(fileName) + .then(code => { + // Use babel plugin to extract instances where react-intl is used + const { metadata: result } = transform(code, { presets, plugins }); + + for (const message of result['react-intl'].messages) { + for (const locale of locales) { + const oldLocaleMapping = oldLocaleMappings[locale][message.id]; + // Merge old translations into the babel extracted instances where react-intl is used + const newMsg = + locale === DEFAULT_LOCALE ? message.defaultMessage : ''; + localeMappings[locale][message.id] = oldLocaleMapping + ? oldLocaleMapping + : newMsg; + } + } + }) + .catch(error => { + process.stderr.write(`Error transforming file: ${fileName}\n${error}`); + }); +}; + +const memoryTask = glob(FILES_TO_PARSE); +const memoryTaskDone = task('Storing language files in memory'); + +memoryTask.then(files => { + memoryTaskDone(); + + const extractTask = Promise.all( + files.map(fileName => extractFromFile(fileName)), + ); + const extractTaskDone = task('Run extraction on all files'); + // Run extraction on all files that match the glob on line 16 + extractTask.then(result => { + extractTaskDone(); + + // Make the directory if it doesn't exist, especially for first run + mkdir('-p', 'app/translations'); + + let localeTaskDone; + let translationFileName; + + for (const locale of locales) { + translationFileName = `app/translations/${locale}.json`; + localeTaskDone = task( + `Writing translation messages for ${locale} to: ${translationFileName}`, + ); + + // Sort the translation JSON file so that git diffing is easier + // Otherwise the translation messages will jump around every time we extract + let messages = {}; + Object.keys(localeMappings[locale]) + .sort() + .forEach(function(key) { + messages[key] = localeMappings[locale][key]; + }); + + // Write to file the JSON representation of the translation messages + const prettified = `${JSON.stringify(messages, null, 2)}\n`; + + try { + fs.writeFileSync(translationFileName, prettified); + localeTaskDone(); + } catch (error) { + localeTaskDone( + `There was an error saving this translation file: ${translationFileName} + \n${error}`, + ); + } + } + + process.exit(); + }); +}); diff --git a/front/odiparpack/internals/scripts/generate-templates-for-linting.js b/front/odiparpack/internals/scripts/generate-templates-for-linting.js new file mode 100644 index 0000000..cb3904a --- /dev/null +++ b/front/odiparpack/internals/scripts/generate-templates-for-linting.js @@ -0,0 +1,119 @@ +/** + * This script is for internal `react-boilerplate`'s usage. The only purpose of generating all of these templates is + * to be able to lint them and detect critical errors. Every generated component's name has to start with + * 'RbGenerated' so it can be easily excluded from the test coverage reports. + */ + +const nodePlop = require('node-plop'); +const path = require('path'); +const chalk = require('chalk'); +const rimraf = require('rimraf'); + +const xmark = require('./helpers/xmark'); + +process.chdir(path.join(__dirname, '../generators')); + +const prettyStringify = data => JSON.stringify(data, null, 2); + +const checkForErrors = result => { + if (Array.isArray(result.failures) && result.failures.length > 0) { + throw result.failures; + } +}; + +const reportErrorsFor = title => err => { + // TODO Replace with our own helpers/log that is guaranteed to be blocking? + xmark(() => + console.error( + chalk.red(` ERROR generating '${title}': `), + prettyStringify(err), + ), + ); + process.exit(1); +}; + +// Generated tests are designed to fail, which would in turn fail CI builds +const removeTestsDirFrom = relativePath => () => + rimraf.sync(path.join(__dirname, '/../../app/', relativePath, '/tests')); + +const plop = nodePlop('./index.js'); + +const componentGen = plop.getGenerator('component'); +componentGen + .runActions({ + name: 'RbGeneratedComponentEsclass', + type: 'React.Component', + wantMessages: true, + wantLoadable: true, + }) + .then(checkForErrors) + .then(removeTestsDirFrom('components/RbGeneratedComponentEsclass')) + .catch(reportErrorsFor('component/React.Component')); + +componentGen + .runActions({ + name: 'RbGeneratedComponentEsclasspure', + type: 'React.PureComponent', + wantMessages: true, + wantLoadable: true, + }) + .then(checkForErrors) + .then(removeTestsDirFrom('components/RbGeneratedComponentEsclasspure')) + .catch(reportErrorsFor('component/React.PureComponent')); + +componentGen + .runActions({ + name: 'RbGeneratedComponentStatelessfunction', + type: 'Stateless Function', + wantMessages: true, + wantLoadable: true, + }) + .then(checkForErrors) + .then(removeTestsDirFrom('components/RbGeneratedComponentStatelessfunction')) + .catch(reportErrorsFor('component/Stateless Function')); + +const containerGen = plop.getGenerator('container'); +containerGen + .runActions({ + name: 'RbGeneratedContainerPureComponent', + type: 'React.PureComponent', + wantHeaders: true, + wantActionsAndReducer: true, + wantSagas: true, + wantMessages: true, + wantLoadable: true, + }) + .then(checkForErrors) + .then(removeTestsDirFrom('containers/RbGeneratedContainerPureComponent')) + .catch(reportErrorsFor('container/React.PureComponent')); + +containerGen + .runActions({ + name: 'RbGeneratedContainerComponent', + type: 'React.Component', + wantHeaders: true, + wantActionsAndReducer: true, + wantSagas: true, + wantMessages: true, + wantLoadable: true, + }) + .then(checkForErrors) + .then(removeTestsDirFrom('containers/RbGeneratedContainerComponent')) + .catch(reportErrorsFor('container/React.Component')); + +containerGen + .runActions({ + name: 'RbGeneratedContainerStateless', + type: 'Stateless Function', + wantHeaders: true, + wantActionsAndReducer: true, + wantSagas: true, + wantMessages: true, + wantLoadable: true, + }) + .then(checkForErrors) + .then(removeTestsDirFrom('containers/RbGeneratedContainerStateless')) + .catch(reportErrorsFor('container/Stateless')); + +const languageGen = plop.getGenerator('language'); +languageGen.runActions({ language: 'fr' }).catch(reportErrorsFor('language')); diff --git a/front/odiparpack/internals/scripts/helpers/checkmark.js b/front/odiparpack/internals/scripts/helpers/checkmark.js new file mode 100644 index 0000000..ac30dbc --- /dev/null +++ b/front/odiparpack/internals/scripts/helpers/checkmark.js @@ -0,0 +1,11 @@ +const chalk = require('chalk'); + +/** + * Adds mark check symbol + */ +function addCheckMark(callback) { + process.stdout.write(chalk.green(' ✓')); + if (callback) callback(); +} + +module.exports = addCheckMark; diff --git a/front/odiparpack/internals/scripts/helpers/progress.js b/front/odiparpack/internals/scripts/helpers/progress.js new file mode 100644 index 0000000..4353f20 --- /dev/null +++ b/front/odiparpack/internals/scripts/helpers/progress.js @@ -0,0 +1,25 @@ +'use strict'; + +const readline = require('readline'); + +/** + * Adds an animated progress indicator + * + * @param {string} message The message to write next to the indicator + * @param {number} amountOfDots The amount of dots you want to animate + */ +function animateProgress(message, amountOfDots) { + if (typeof amountOfDots !== 'number') { + amountOfDots = 3; + } + + let i = 0; + return setInterval(function() { + readline.cursorTo(process.stdout, 0); + i = (i + 1) % (amountOfDots + 1); + const dots = new Array(i + 1).join('.'); + process.stdout.write(message + dots); + }, 500); +} + +module.exports = animateProgress; diff --git a/front/odiparpack/internals/scripts/helpers/xmark.js b/front/odiparpack/internals/scripts/helpers/xmark.js new file mode 100644 index 0000000..59d137d --- /dev/null +++ b/front/odiparpack/internals/scripts/helpers/xmark.js @@ -0,0 +1,11 @@ +const chalk = require('chalk'); + +/** + * Adds mark cross symbol + */ +function addXMark(callback) { + process.stdout.write(chalk.red(' ✘')); + if (callback) callback(); +} + +module.exports = addXMark; diff --git a/front/odiparpack/internals/scripts/npmcheckversion.js b/front/odiparpack/internals/scripts/npmcheckversion.js new file mode 100644 index 0000000..e3ecd0c --- /dev/null +++ b/front/odiparpack/internals/scripts/npmcheckversion.js @@ -0,0 +1,8 @@ +const exec = require('child_process').exec; +exec('npm -v', function(err, stdout, stderr) { + if (err) throw err; + if (parseFloat(stdout) < 3) { + throw new Error('[ERROR: React Boilerplate] You need npm version @>=3'); + process.exit(1); + } +}); diff --git a/front/odiparpack/internals/testing/enzyme-setup.js b/front/odiparpack/internals/testing/enzyme-setup.js new file mode 100644 index 0000000..82edfc9 --- /dev/null +++ b/front/odiparpack/internals/testing/enzyme-setup.js @@ -0,0 +1,4 @@ +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); diff --git a/front/odiparpack/internals/testing/test-bundler.js b/front/odiparpack/internals/testing/test-bundler.js new file mode 100644 index 0000000..928d73e --- /dev/null +++ b/front/odiparpack/internals/testing/test-bundler.js @@ -0,0 +1,3 @@ +// needed for regenerator-runtime +// (ES7 generator support is required by redux-saga) +import '@babel/polyfill'; diff --git a/front/odiparpack/internals/webpack/webpack.base.babel.js b/front/odiparpack/internals/webpack/webpack.base.babel.js new file mode 100644 index 0000000..ca26c12 --- /dev/null +++ b/front/odiparpack/internals/webpack/webpack.base.babel.js @@ -0,0 +1,205 @@ +/** + * COMMON WEBPACK CONFIGURATION + */ + +const path = require('path'); +const webpack = require('webpack'); + +const HappyPack = require('happypack'); +const happyThreadPool = HappyPack.ThreadPool({ size: 5 }); + +module.exports = options => ({ + mode: options.mode, + entry: options.entry, + output: Object.assign( + { + // Compile into js/build.js + path: path.resolve(process.cwd(), 'build'), + publicPath: '/', + }, + options.output, + ), // Merge with env dependent settings + devServer: { + inline: false, + }, + optimization: options.optimization, + module: { + rules: [ + /* + Disabled eslint by default. + You can enable it to maintain and keep clean your code. + NOTE: By enable eslint running app process at beginning will slower + */ + // { + // enforce: 'pre', + // test: /\.js?$/, + // exclude: [/node_modules/], + // loader: 'eslint-loader', + // options: { + // quiet: true + // } + // }, + { + test: /\.jsx?$/, // Transform all .js files required somewhere with Babel + exclude: /node_modules/, + use: { + loader: 'happypack/loader?id=js', + options: options.babelQuery, + }, + }, + { + // Preprocess our own .css files + // This is the place to add your own loaders (e.g. sass/less etc.) + // for a list of loaders, see https://webpack.js.org/loaders/#styling + test: /\.css$/, + exclude: /node_modules/, + use: ['style-loader', 'css-loader'], + }, + { + // Preprocess 3rd party .css files located in node_modules + test: /\.css$/, + include: /node_modules/, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + use: 'file-loader', + }, + { + test: /\.(scss)$/, + use: [{ + loader: 'style-loader' + }, + { + loader: 'css-loader', + options: + { + sourceMap: false, + importLoaders: 2, + modules: true, + localIdentName: '[local]__[hash:base64:5]' + } + }, + { + loader: 'postcss-loader', + options: { + sourceMap: false + } + }, + { + loader: 'sass-loader', + options: { + outputStyle: 'expanded', + sourceMap: false + } + }], + }, + { + test: /\.md$/, + use: 'raw-loader' + }, + { + test: /\.(jpg|png|gif|svg)$/, + use: [ + { + loader: 'url-loader', + options: { + // Inline files smaller than 10 kB + limit: 10 * 1024, + }, + }, + /* + Disabled image compression by default, + due error in windows 10 because libpng not available. + The libpng avaible on Linux and Mac system only. + NOTE: To enable this, first you need to install image-webpack-loader. + npm install -i image-webpack-loader --save + */ + // { + // loader: 'image-webpack-loader', + // options: { + // mozjpeg: { + // enabled: false, + // // NOTE: mozjpeg is disabled as it causes errors in some Linux environments + // // Try enabling it in your environment by switching the config to: + // // enabled: true, + // // progressive: true, + // }, + // gifsicle: { + // interlaced: false, + // }, + // optipng: { + // optimizationLevel: 7, + // }, + // pngquant: { + // quality: '65-90', + // speed: 4, + // }, + // }, + // }, + ], + }, + { + test: /\.html$/, + use: 'html-loader', + }, + { + test: /\.(mp4|webm)$/, + use: { + loader: 'url-loader', + options: { + limit: 10000, + }, + }, + }, + ], + }, + node: { + fs: 'empty' + }, + plugins: options.plugins.concat([ + // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV` + // inside your code for any environment checks; Terser will automatically + // drop any unreachable code. + new HappyPack({ + id: 'js', + threadPool: happyThreadPool, + loaders: ['babel-loader?cacheDirectory=true'] + }), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(process.env.NODE_ENV), + }, + }), + new webpack.ContextReplacementPlugin(/^\.\/locale$/, context => { + if (!/\/moment\//.test(context.context)) { + return; + } + // context needs to be modified in place + Object.assign(context, { + // include only CJK + regExp: /^\.\/(ja|ko|zh)/, + // point to the locale data folder relative to moment's src/lib/locale + request: './locale' + }); + }) + ]), + resolve: { + modules: ['node_modules', 'app'], + extensions: ['.js', '.jsx', '.react.js'], + mainFields: ['browser', 'jsnext:main', 'main'], + alias: { + 'ba-components': path.resolve(__dirname, '../../app/components/'), + 'ba-containers': path.resolve(__dirname, '../../app/containers/'), + 'ba-actions': path.resolve(__dirname, '../../app/actions/'), + 'ba-styles': path.resolve(__dirname, '../../app/styles/components/'), + 'ba-helpers': path.resolve(__dirname, '../../app/styles/helpers/'), + 'ba-api': path.resolve(__dirname, '../../app/api/'), + 'ba-images': path.resolve(__dirname, '../../public/images/'), + 'ba-vendor': path.resolve(__dirname, '../../node_modules/'), + } + }, + devtool: options.devtool, + target: 'web', // Make web variables accessible to webpack, e.g. window + performance: options.performance || {}, +}); diff --git a/front/odiparpack/internals/webpack/webpack.dev.babel.js b/front/odiparpack/internals/webpack/webpack.dev.babel.js new file mode 100644 index 0000000..dda13b8 --- /dev/null +++ b/front/odiparpack/internals/webpack/webpack.dev.babel.js @@ -0,0 +1,144 @@ +/** + * DEVELOPMENT WEBPACK CONFIGURATION + */ + +const path = require('path'); +const fs = require('fs'); +const glob = require('glob'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); +const CircularDependencyPlugin = require('circular-dependency-plugin'); +const logger = require('../../server/logger'); +const pkg = require(path.resolve(process.cwd(), 'package.json')); // eslint-disable-line +const { dllPlugin } = pkg; + +const plugins = [ + new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading + new HtmlWebpackPlugin({ + inject: true, // Inject all files that are generated by webpack, e.g. bundle.js + template: 'app/index.html', + }), + new CircularDependencyPlugin({ + exclude: /a\.js|node_modules/, // exclude node_modules + failOnError: false, // show a warning when there is a circular dependency + }), +]; + +if (dllPlugin) { + glob.sync(`${dllPlugin.path}/*.dll.js`).forEach(dllPath => { + plugins.push( + new AddAssetHtmlPlugin({ + filepath: dllPath, + includeSourcemap: false, + }), + ); + }); +} + +module.exports = require('./webpack.base.babel')({ + mode: 'development', + + // Add hot reloading in development + entry: [ + 'eventsource-polyfill', // Necessary for hot reloading with IE + 'webpack-hot-middleware/client?reload=true', + path.join(process.cwd(), 'app/app.js'), // Start with js/app.js + ], + + // Don't use hashes in dev mode for better performance + output: { + filename: '[name].js', + chunkFilename: '[name].chunk.js', + }, + + optimization: { + splitChunks: { + chunks: 'all', + }, + }, + + // Add development plugins + plugins: dependencyHandlers().concat(plugins), // eslint-disable-line no-use-before-define + + // Emit a source map for easier debugging + // See https://webpack.js.org/configuration/devtool/#devtool + devtool: 'eval-source-map', + + performance: { + hints: false, + }, +}); + +/** + * Select which plugins to use to optimize the bundle's handling of + * third party dependencies. + * + * If there is a dllPlugin key on the project's package.json, the + * Webpack DLL Plugin will be used. + * + */ +function dependencyHandlers() { + // Don't do anything during the DLL Build step + if (process.env.BUILDING_DLL) { + return []; + } + + // Don't do anything if package.json does not have a dllPlugin property + // Code splitting now included by default in Webpack 4 + if (!dllPlugin) { + return []; + } + + const dllPath = path.resolve( + process.cwd(), + dllPlugin.path || 'node_modules/react-boilerplate-dlls', + ); + + /** + * If DLLs aren't explicitly defined, we assume all production dependencies listed in package.json + * Reminder: You need to exclude any server side dependencies by listing them in dllConfig.exclude + */ + if (!dllPlugin.dlls) { + const manifestPath = path.resolve(dllPath, 'reactBoilerplateDeps.json'); + + if (!fs.existsSync(manifestPath)) { + logger.error( + 'The DLL manifest is missing. Please run `npm run build:dll`', + ); + process.exit(0); + } + + return [ + new webpack.DllReferencePlugin({ + context: process.cwd(), + manifest: require(manifestPath), // eslint-disable-line + }), + ]; + } + + // If DLLs are explicitly defined, we automatically create a DLLReferencePlugin for each of them. + const dllManifests = Object.keys(dllPlugin.dlls) + .map(name => path.join(dllPath, `/${name}.json`)); + + return dllManifests.map(manifestPath => { + if (!fs.existsSync(path)) { + if (!fs.existsSync(manifestPath)) { + logger.error( + `The following Webpack DLL manifest is missing: ${path.basename( + manifestPath, + )}`, + ); + logger.error(`Expected to find it in ${dllPath}`); + logger.error('Please run: npm run build:dll'); + + process.exit(0); + } + } + + return new webpack.DllReferencePlugin({ + context: process.cwd(), + manifest: require(manifestPath), // eslint-disable-line + }); + }); +} diff --git a/front/odiparpack/internals/webpack/webpack.dll.babel.js b/front/odiparpack/internals/webpack/webpack.dll.babel.js new file mode 100644 index 0000000..d1ee114 --- /dev/null +++ b/front/odiparpack/internals/webpack/webpack.dll.babel.js @@ -0,0 +1,58 @@ +/** + * WEBPACK DLL GENERATOR + * + * This profile is used to cache webpack's module + * contexts for external library and framework type + * dependencies which will usually not change often enough + * to warrant building them from scratch every time we use + * the webpack process. + */ + +const { join } = require('path'); +const defaults = require('lodash/defaultsDeep'); +const webpack = require('webpack'); +const pkg = require(join(process.cwd(), 'package.json')); // eslint-disable-line +const { dllPlugin } = require('../config'); + +if (!pkg.dllPlugin) { + process.exit(0); +} + +const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults); +const outputPath = join(process.cwd(), dllConfig.path); + +module.exports = require('./webpack.base.babel')({ + mode: 'development', + context: process.cwd(), + entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg), + optimization: { + minimize: false, + }, + devtool: 'eval', + output: { + filename: '[name].dll.js', + path: outputPath, + library: '[name]', + }, + plugins: [ + new webpack.DllPlugin({ + name: '[name]', + path: join(outputPath, '[name].json'), + }), + new webpack.ContextReplacementPlugin(/^\.\/locale$/, context => { + if (!/\/moment\//.test(context.context)) { + return; + } + // context needs to be modified in place + Object.assign(context, { + // include only CJK + regExp: /^\.\/(ja|ko|zh)/, + // point to the locale data folder relative to moment's src/lib/locale + request: './locale' + }); + }) + ], + performance: { + hints: false, + }, +}); diff --git a/front/odiparpack/internals/webpack/webpack.prod.babel.js b/front/odiparpack/internals/webpack/webpack.prod.babel.js new file mode 100644 index 0000000..94ecac0 --- /dev/null +++ b/front/odiparpack/internals/webpack/webpack.prod.babel.js @@ -0,0 +1,154 @@ +// Important modules this config uses +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const WebpackPwaManifest = require('webpack-pwa-manifest'); +const OfflinePlugin = require('offline-plugin'); +const { HashedModuleIdsPlugin } = require('webpack'); +const TerserPlugin = require('terser-webpack-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); + +module.exports = require('./webpack.base.babel')({ + mode: 'production', + + // In production, we skip all hot-reloading stuff + entry: [ + require.resolve('react-app-polyfill/ie11'), + path.join(process.cwd(), 'app/app.js'), + ], + + // Utilize long-term caching by adding content hashes (not compilation hashes) to compiled assets + output: { + filename: '[name].[chunkhash].js', + chunkFilename: '[name].[chunkhash].chunk.js', + }, + + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + warnings: false, + compress: { + comparisons: false, + }, + parse: {}, + mangle: true, + output: { + comments: false, + ascii_only: true, + }, + }, + parallel: true, + cache: true, + sourceMap: true, + }), + ], + nodeEnv: 'production', + sideEffects: true, + concatenateModules: true, + splitChunks: { + chunks: 'all', + minSize: 30000, + minChunks: 1, + maxAsyncRequests: 5, + maxInitialRequests: 3, + name: true, + cacheGroups: { + commons: { + test: /[\\/]node_modules[\\/]/, + name: 'vendor', + chunks: 'all', + }, + main: { + chunks: 'all', + minChunks: 2, + reuseExistingChunk: true, + enforce: true, + }, + }, + }, + runtimeChunk: true, + }, + + plugins: [ + // Minify and optimize the index.html + new HtmlWebpackPlugin({ + template: 'app/index.html', + minify: { + removeComments: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + removeStyleLinkTypeAttributes: true, + keepClosingSlash: true, + minifyJS: true, + minifyCSS: true, + minifyURLs: true, + }, + inject: true, + }), + + // Put it in the end to capture all the HtmlWebpackPlugin's + // assets manipulations and do leak its manipulations to HtmlWebpackPlugin + new OfflinePlugin({ + relativePaths: false, + publicPath: '/', + appShell: '/', + + // No need to cache .htaccess. See http://mxs.is/googmp, + // this is applied before any match in `caches` section + excludes: ['.htaccess'], + + caches: { + main: [':rest:'], + + // All chunks marked as `additional`, loaded after main section + // and do not prevent SW to install. Change to `optional` if + // do not want them to be preloaded at all (cached only when first loaded) + additional: ['*.chunk.js'], + }, + + // Removes warning for about `additional` section usage + safeToUseOptionalCaches: true, + }), + + new CompressionPlugin({ + algorithm: 'gzip', + test: /\.js$|\.css$|\.html$/, + threshold: 10240, + minRatio: 0.8, + }), + + new WebpackPwaManifest({ + name: 'React Boilerplate', + short_name: 'React BP', + description: 'My React Boilerplate-based project!', + background_color: '#fafafa', + theme_color: '#b1624d', + inject: true, + ios: true, + cons: [ + { + src: path.resolve('public/images/logo.png'), + sizes: [72, 96, 128, 144, 192, 384, 512], + }, + { + src: path.resolve('public/images/logo.png'), + sizes: [120, 152, 167, 180], + ios: true, + }, + ], + }), + + new HashedModuleIdsPlugin({ + hashFunction: 'sha256', + hashDigest: 'hex', + hashDigestLength: 20, + }), + ], + + performance: { + assetFilter: assetFilename => !/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename), + }, +}); |
