diff --git a/bin/react-generate.js b/bin/react-generate.js
index 784eff2..2301014 100755
--- a/bin/react-generate.js
+++ b/bin/react-generate.js
@@ -13,7 +13,7 @@ const TEST_UTIL = 'tUtil';
const LOADABLE = 'loadable';
const INJECT_SAGA = 'injectSaga';
-const generator = path.join(__dirname, '../generators/index.js');
+const generator = path.join(__dirname, '../generators/react/index.js');
const plopGen = ['--plopfile', generator];
const [, , ...args] = process.argv;
@@ -155,7 +155,7 @@ switch (commandLineArgs[0]) {
`Creating a component by specifying path and name: react-generate gcon src/app Button\n` +
`Creating a container by specifying path and name: react-generate gcom src/app HomePage\n` +
`Generate test for all components in directory: react-generate --all component src/app/components\n` +
- `Generate test for all containers in directory: react-generate --all containers src/app/containers`,
+ `Generate test for all containers in directory: react-generate --all container src/app/containers`,
);
break;
case '--all':
@@ -177,8 +177,12 @@ switch (commandLineArgs[0]) {
shell.cd(cwd);
directories.forEach(component => {
if (!_.includes(component, '.')) {
- shell.exec(
+ shell.echo(`Component name: ${component}`);
+ childProcess.execSync(
`react-generate gtcomf ${_.drop(commandLineArgs)} ${component}`,
+ {
+ ...stdioInherit,
+ },
);
}
});
@@ -188,11 +192,11 @@ switch (commandLineArgs[0]) {
shell.cd(`./${commandLineArgs[1]}`);
directories = shell.ls();
shell.cd(cwd);
- directories.forEach(component => {
- if (!_.includes(component, '.')) {
- shell.echo(`Component name: ${component}`);
+ directories.forEach(container => {
+ if (!_.includes(container, '.')) {
+ shell.echo(`Container name: ${container}`);
childProcess.execSync(
- `react-generate gtconf ${_.drop(commandLineArgs)} ${component}`,
+ `react-generate gtconf ${_.drop(commandLineArgs)} ${container}`,
{
...stdioInherit,
},
diff --git a/bin/react-native-generate.js b/bin/react-native-generate.js
new file mode 100644
index 0000000..1ad407b
--- /dev/null
+++ b/bin/react-native-generate.js
@@ -0,0 +1,214 @@
+#! /usr/bin/env node
+const shell = require('shelljs');
+const fs = require('fs');
+const childProcess = require('child_process');
+const process = require('process');
+const _ = require('lodash');
+const path = require('path');
+
+const COMPONENT = 'component';
+const CONTAINER = 'container';
+const WEBPACK_BASE_BABEL = 'webpackBaseBabel';
+const TEST_UTIL = 'tUtil';
+const INJECT_SAGA = 'injectSaga';
+
+const generator = path.join(__dirname, '../generators/react-native/index.js');
+const plopGen = ['--plopfile', generator];
+
+const [, , ...args] = process.argv;
+let commandLineArgs = args.toString().split(',');
+const stdioInherit = { stdio: 'inherit' };
+
+function execShell(commandArray) {
+ childProcess.execFileSync(
+ path.join(__dirname, '../node_modules/.bin/plop'),
+ commandArray,
+ { ...stdioInherit },
+ );
+}
+
+// validate input
+if (!commandLineArgs[0]) {
+ shell.exec(
+ `echo Sorry! react-native-generate requires an argument to be passed. Run react-native-generate --help for more details`,
+ );
+ return;
+}
+
+// get the type of generator
+shell.env.GENERATOR_TYPE = _.includes(commandLineArgs[0], 't')
+ ? 'existing'
+ : 'new';
+let directoryName = 'react-native-template';
+switch (commandLineArgs[0]) {
+ case 'init':
+ shell.exec(
+ `git clone https://github.com/wednesday-solutions/react-native-template`,
+ );
+ shell.cd(process.cwd());
+ if (commandLineArgs[1]) {
+ shell.exec(`mkdir ${commandLineArgs[1]}`);
+ fs.rename(`react-native-template`, commandLineArgs[1], err => {
+ if (err) {
+ throw new Error('Error while renaming');
+ }
+ });
+ directoryName = commandLineArgs[1];
+ const json = path.join(__dirname, '../node_modules/.bin/json');
+
+ shell.exec(
+ `${json} -I -f ${directoryName}/package.json -e "this.name='${directoryName}'"`,
+ );
+ shell.exec(
+ `${json} -I -f ${directoryName}/package.json -e "this.homepage='""'"`,
+ );
+ shell.exec(
+ `${json} -I -f ${directoryName}/package.json -e "this.author='""'"`,
+ );
+ shell.exec(
+ `${json} -I -f ${directoryName}/package.json -e "this.repository='{}'"`,
+ );
+
+ commandLineArgs = _.drop(commandLineArgs);
+ }
+ shell.cd(directoryName);
+ shell.exec(`git remote remove origin`);
+ execShell([
+ '-f',
+ ...plopGen,
+ WEBPACK_BASE_BABEL,
+ ..._.drop(commandLineArgs),
+ ]);
+ shell.exec(`npm run initialize`);
+ break;
+ case 'gt':
+ execShell(plopGen);
+ break;
+ case 'gtcom':
+ execShell([...plopGen, COMPONENT, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gtcon':
+ execShell([...plopGen, CONTAINER, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gtf':
+ execShell(['-f', ...plopGen, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gtcomf':
+ execShell(['-f', ...plopGen, COMPONENT, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gtconf':
+ execShell(['-f', ...plopGen, CONTAINER, ..._.drop(commandLineArgs)]);
+ break;
+ case 'g':
+ execShell([...plopGen, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gcom':
+ execShell([...plopGen, COMPONENT, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gcon':
+ execShell([...plopGen, CONTAINER, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gf':
+ execShell(['-f', ...plopGen, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gcomf':
+ execShell(['-f', ...plopGen, COMPONENT, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gconf':
+ execShell(['-f', ...plopGen, CONTAINER, ..._.drop(commandLineArgs)]);
+ break;
+ case 'gtutil':
+ execShell(['-f', ...plopGen, TEST_UTIL, ..._.drop(commandLineArgs)]);
+ break;
+ case 'ginjectsaga':
+ execShell(['-f', ...plopGen, INJECT_SAGA, ..._.drop(commandLineArgs)]);
+ break;
+ case '--help':
+ shell.echo(
+ `Generate tests for existing and new react native components\n\n` +
+ `init: Create a new react native application\n` +
+ `gt: Creating a test for a container or component\n` +
+ `gtf: Forcefully creating a test for a container or component\n` +
+ `gtcom: Creating a test for an existing component\n` +
+ `gtcomf: Forcefully creating a test for an existing component\n` +
+ `gtcon: Creating a test for an existing container\n` +
+ `gtconf : Forcefully creating a test for an existing component\n` +
+ `g: Creating a container or component\n` +
+ `gf: Forcefully creating a container or component\n` +
+ `gcom: Creating a component\n` +
+ `gcomf: Forcefully creating a component\n` +
+ `gcon: Creating a container\n` +
+ `gconf: Forcefully creating a container\n` +
+ `--all: Adding tests for all existing containers or components.\n` +
+ `gtutil: Create a test util file with some test utility functions.\n` +
+ `ginjectsaga: Create an injector for sagas that work with hooks.\n\n` +
+ `-------\n\n` +
+ `Creating a test by specifying type, path and name: react-native-generate gt component src/app Button\n` +
+ `Creating a test for an existing component by specifying path and name: react-native-generate gtcon src/app Button\n` +
+ `Creating a test for an existing container by specifying path and name: react-native-generate gtcom src/app HomePage\n` +
+ `Creating a component/container by specifying type, path and name: react-native-generate g component src/app Button\n` +
+ `Creating a component by specifying path and name: react-native-generate gcon src/app Button\n` +
+ `Creating a container by specifying path and name: react-native-generate gcom src/app HomePage\n` +
+ `Generate test for all components in directory: react-native-generate --all component src/app/components\n` +
+ `Generate test for all containers in directory: react-native-generate --all containers src/app/containers`,
+ );
+ break;
+ case '--all':
+ {
+ commandLineArgs = _.drop(commandLineArgs);
+ if (!commandLineArgs[0] || !commandLineArgs[1] || commandLineArgs[2]) {
+ shell.exec(
+ `echo Sorry! react-native-generate --all requires 2 commandLineArgs to be passed. Run react-native-generate --help for more details`,
+ );
+ return;
+ }
+ let cwd;
+ let directories;
+ switch (commandLineArgs[0]) {
+ case COMPONENT:
+ cwd = shell.exec('pwd').stdout;
+ shell.cd(`./${commandLineArgs[1]}`);
+ directories = shell.ls();
+ shell.cd(cwd);
+ directories.forEach(component => {
+ if (!_.includes(component, '.')) {
+ shell.echo(`Component name: ${component}`);
+ childProcess.execSync(
+ `react-native-generate gtcomf ${_.drop(
+ commandLineArgs,
+ )} ${component}`,
+ {
+ ...stdioInherit,
+ },
+ );
+ }
+ });
+ break;
+ case CONTAINER:
+ cwd = shell.exec('pwd').stdout;
+ shell.cd(`./${commandLineArgs[1]}`);
+ directories = shell.ls();
+ shell.cd(cwd);
+ directories.forEach(container => {
+ if (!_.includes(container, '.')) {
+ shell.echo(`Container name: ${container}`);
+ childProcess.execSync(
+ `react-native-generate gtconf ${_.drop(
+ commandLineArgs,
+ )} ${container}`,
+ {
+ ...stdioInherit,
+ },
+ );
+ }
+ });
+ break;
+ default:
+ shell.exec(`echo ${commandLineArgs[0]} is not a valid argument`);
+ }
+ }
+ break;
+ default:
+ shell.exec(`echo Sorry ${commandLineArgs[0]} is not a valid command`);
+ break;
+}
diff --git a/generators/component/existing/index.js b/generators/react-native/component/existing/index.js
similarity index 100%
rename from generators/component/existing/index.js
rename to generators/react-native/component/existing/index.js
diff --git a/generators/react-native/component/index.js.hbs b/generators/react-native/component/index.js.hbs
new file mode 100644
index 0000000..a25d8cb
--- /dev/null
+++ b/generators/react-native/component/index.js.hbs
@@ -0,0 +1,32 @@
+/**
+ *
+ * {{ properCase name }}
+ *
+ */
+
+{{#if memo}}
+import React, { memo } from 'react'
+{{else}}
+import React from 'react'
+{{/if}}
+import { View, Text } from 'react-native';
+// import PropTypes from 'prop-types'
+// import styled from 'styled-components'
+
+import { FormattedMessage as T } from 'react-intl'
+
+function {{ properCase name }}() {
+ return (
+
+
+
+ )
+}
+
+{{ properCase name }}.propTypes = {}
+
+{{#if memo}}
+export default memo({{ properCase name }})
+{{else}}
+export default {{ properCase name }}
+{{/if}}
diff --git a/generators/component/new/index.js b/generators/react-native/component/new/index.js
similarity index 100%
rename from generators/component/new/index.js
rename to generators/react-native/component/new/index.js
diff --git a/generators/react-native/component/stories.js.hbs b/generators/react-native/component/stories.js.hbs
new file mode 100644
index 0000000..b2f0ab6
--- /dev/null
+++ b/generators/react-native/component/stories.js.hbs
@@ -0,0 +1,16 @@
+/**
+ *
+ * Stories for {{ properCase name }}
+ *
+ * @see https://github.com/storybookjs/storybook
+ *
+ */
+
+import React from 'react'
+import { View } from 'react-native';
+import { storiesOf } from '@storybook/react-native'
+import { text } from '@storybook/addon-knobs'
+import {{ properCase name }} from '../index'
+
+
+storiesOf('{{properCase name}}').add('simple', () =><{{properCase name}} id={text('id', '{{properCase name}}')}/>)
\ No newline at end of file
diff --git a/generators/react-native/component/test.js.hbs b/generators/react-native/component/test.js.hbs
new file mode 100644
index 0000000..8605fb9
--- /dev/null
+++ b/generators/react-native/component/test.js.hbs
@@ -0,0 +1,18 @@
+/**
+ *
+ * Tests for {{ properCase name }}
+ *
+ */
+
+import React from 'react'
+// import { fireEvent } from '@testing-library/dom'
+import { renderWithIntl } from '@utils/testUtils'
+import {{ properCase name }} from '../index'
+
+describe('<{{ properCase name }} />', () => {
+
+ it('should render and match the snapshot', () => {
+ const { baseElement } = renderWithIntl(<{{ properCase name }} />)
+ expect(baseElement).toMatchSnapshot()
+ })
+})
\ No newline at end of file
diff --git a/generators/container/existing/index.js b/generators/react-native/container/existing/index.js
similarity index 100%
rename from generators/container/existing/index.js
rename to generators/react-native/container/existing/index.js
diff --git a/generators/react-native/container/index.js.hbs b/generators/react-native/container/index.js.hbs
new file mode 100644
index 0000000..aec21a9
--- /dev/null
+++ b/generators/react-native/container/index.js.hbs
@@ -0,0 +1,62 @@
+/**
+ *
+ * {{properCase name }}
+ *
+ */
+
+{{#if memo}}
+import React, { memo } from 'react'
+{{else}}
+import React from 'react'
+{{/if}}
+import { View, Text } from 'react-native';
+// import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { injectIntl } from 'react-intl'
+import { FormattedMessage as T } from 'react-intl'
+{{#if wantActionsAndReducer}}
+import { createStructuredSelector } from 'reselect'
+{{/if}}
+import { compose } from 'redux'
+{{#if wantActionsAndReducer}}
+import makeSelect{{properCase name}} from './selectors'
+{{/if}}
+
+export function {{ properCase name }}() {
+
+ return (
+
+
+
+ )
+}
+
+{{ properCase name }}.propTypes = {
+}
+
+{{#if wantActionsAndReducer}}
+const mapStateToProps = createStructuredSelector({
+ {{ camelCase name }}: makeSelect{{properCase name}}(),
+})
+{{/if}}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ dispatch,
+ }
+}
+
+{{#if wantActionsAndReducer}}
+const withConnect = connect(mapStateToProps, mapDispatchToProps)
+{{else}}
+const withConnect = connect(null, mapDispatchToProps)
+{{/if}}
+
+export default compose(
+ withConnect,
+{{#if memo}}
+ memo,
+{{/if}}
+)({{ properCase name }})
+
+export const {{ properCase name }}Test = compose(injectIntl)({{ properCase name }})
\ No newline at end of file
diff --git a/generators/react-native/container/new/index.js b/generators/react-native/container/new/index.js
new file mode 100644
index 0000000..b4d1ca2
--- /dev/null
+++ b/generators/react-native/container/new/index.js
@@ -0,0 +1,88 @@
+/**
+ * Container Generator
+ */
+const existing = require('../existing');
+
+const cwd = process.cwd();
+module.exports = {
+ description: 'Add a container component',
+ prompts: [
+ {
+ type: 'input',
+ name: 'path',
+ message: 'What is the container directory? (app/containers)',
+ default: 'app/containers',
+ },
+ {
+ type: 'input',
+ name: 'name',
+ message: 'What should it be called?',
+ default: 'Form',
+ },
+ {
+ type: 'confirm',
+ name: 'memo',
+ default: false,
+ message: 'Do you want to wrap your container in React.memo?',
+ },
+ {
+ 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)',
+ },
+ ],
+ actions: data => {
+ // Generate index.js and index.test.js
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/index.js`,
+ templateFile: './container/index.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ // If they want actions and a reducer, generate reducer.js and the
+ // corresponding tests for actions and the reducer
+ if (data.wantActionsAndReducer) {
+ // Selectors
+ actions.push({
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/selectors.js`,
+ templateFile: './container/selectors.js.hbs',
+ abortOnFail: true,
+ });
+
+ // Reducer
+ actions.push({
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/reducer.js`,
+ templateFile: './container/reducer.js.hbs',
+ abortOnFail: true,
+ });
+ }
+
+ // Sagas
+ if (data.wantSaga) {
+ actions.push({
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/saga.js`,
+ templateFile: './container/saga.js.hbs',
+ abortOnFail: true,
+ });
+ }
+
+ actions.push(...existing.actions(data));
+ actions.push(existing.prettier());
+
+ return actions;
+ },
+};
diff --git a/generators/container/reducer.js.hbs b/generators/react-native/container/reducer.js.hbs
similarity index 100%
rename from generators/container/reducer.js.hbs
rename to generators/react-native/container/reducer.js.hbs
diff --git a/generators/container/reducer.test.js.hbs b/generators/react-native/container/reducer.test.js.hbs
similarity index 100%
rename from generators/container/reducer.test.js.hbs
rename to generators/react-native/container/reducer.test.js.hbs
diff --git a/generators/container/saga.js.hbs b/generators/react-native/container/saga.js.hbs
similarity index 100%
rename from generators/container/saga.js.hbs
rename to generators/react-native/container/saga.js.hbs
diff --git a/generators/container/saga.test.js.hbs b/generators/react-native/container/saga.test.js.hbs
similarity index 100%
rename from generators/container/saga.test.js.hbs
rename to generators/react-native/container/saga.test.js.hbs
diff --git a/generators/container/selectors.js.hbs b/generators/react-native/container/selectors.js.hbs
similarity index 100%
rename from generators/container/selectors.js.hbs
rename to generators/react-native/container/selectors.js.hbs
diff --git a/generators/container/selectors.test.js.hbs b/generators/react-native/container/selectors.test.js.hbs
similarity index 100%
rename from generators/container/selectors.test.js.hbs
rename to generators/react-native/container/selectors.test.js.hbs
diff --git a/generators/container/test.js.hbs b/generators/react-native/container/test.js.hbs
similarity index 100%
rename from generators/container/test.js.hbs
rename to generators/react-native/container/test.js.hbs
diff --git a/generators/index.js b/generators/react-native/index.js
similarity index 96%
rename from generators/index.js
rename to generators/react-native/index.js
index c208ed4..aad50c9 100644
--- a/generators/index.js
+++ b/generators/react-native/index.js
@@ -8,7 +8,7 @@ const fs = require('fs');
const path = require('path');
const shell = require('shelljs');
const { execSync } = require('child_process');
-const prettier = path.join(__dirname, '../node_modules/.bin/prettier');
+const prettier = path.join(__dirname, '../../node_modules/.bin/prettier');
const componentGenerator = require(`./component/${
shell.env.GENERATOR_TYPE
}/index.js`);
diff --git a/generators/injectSaga/index.js b/generators/react-native/injectSaga/index.js
similarity index 100%
rename from generators/injectSaga/index.js
rename to generators/react-native/injectSaga/index.js
diff --git a/generators/injectSaga/injectSaga.js.hbs b/generators/react-native/injectSaga/injectSaga.js.hbs
similarity index 100%
rename from generators/injectSaga/injectSaga.js.hbs
rename to generators/react-native/injectSaga/injectSaga.js.hbs
diff --git a/generators/injectSaga/sagaInjectors.js.hbs b/generators/react-native/injectSaga/sagaInjectors.js.hbs
similarity index 100%
rename from generators/injectSaga/sagaInjectors.js.hbs
rename to generators/react-native/injectSaga/sagaInjectors.js.hbs
diff --git a/generators/loadable/index.js b/generators/react-native/loadable/index.js
similarity index 100%
rename from generators/loadable/index.js
rename to generators/react-native/loadable/index.js
diff --git a/generators/loadable/loadable.js.hbs b/generators/react-native/loadable/loadable.js.hbs
similarity index 100%
rename from generators/loadable/loadable.js.hbs
rename to generators/react-native/loadable/loadable.js.hbs
diff --git a/generators/testUtil/index.js b/generators/react-native/testUtil/index.js
similarity index 100%
rename from generators/testUtil/index.js
rename to generators/react-native/testUtil/index.js
diff --git a/generators/testUtil/testUtils.js.hbs b/generators/react-native/testUtil/testUtils.js.hbs
similarity index 100%
rename from generators/testUtil/testUtils.js.hbs
rename to generators/react-native/testUtil/testUtils.js.hbs
diff --git a/generators/webpack/base/babel/babel.js.hbs b/generators/react-native/webpack/base/babel/babel.js.hbs
similarity index 100%
rename from generators/webpack/base/babel/babel.js.hbs
rename to generators/react-native/webpack/base/babel/babel.js.hbs
diff --git a/generators/webpack/base/babel/index.js b/generators/react-native/webpack/base/babel/index.js
similarity index 100%
rename from generators/webpack/base/babel/index.js
rename to generators/react-native/webpack/base/babel/index.js
diff --git a/generators/react/component/existing/index.js b/generators/react/component/existing/index.js
new file mode 100644
index 0000000..c337c78
--- /dev/null
+++ b/generators/react/component/existing/index.js
@@ -0,0 +1,66 @@
+/**
+ * Component Generator
+ */
+
+/* eslint strict: ["off"] */
+
+'use strict';
+
+const cwd = process.cwd();
+
+const storyPrompt = {
+ type: 'confirm',
+ name: 'wantStories',
+ default: true,
+ message: 'Do you want stories for your component?',
+};
+const pathPrompt = {
+ type: 'input',
+ name: 'path',
+ message: 'What is the component directory? (app/components)',
+ default: 'app/components',
+};
+
+const prompts = [
+ {
+ type: 'input',
+ name: 'name',
+ message: 'What is the name of the component you want to add tests for?',
+ default: 'Button',
+ },
+];
+prompts.push(storyPrompt);
+prompts.push(pathPrompt);
+
+module.exports = {
+ description: 'Add tests for an existing component',
+ storyPrompt,
+ pathPrompt,
+ prompts,
+ actions: data => {
+ // index.test.js
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/tests/index.test.js`,
+ templateFile: './component/test.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ if (data.wantStories) {
+ actions.push({
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/stories/{{properCase name}}.stories.js`,
+ templateFile: './component/stories.js.hbs',
+ abortOnFail: true,
+ });
+ }
+
+ return actions;
+ },
+ prettier: () => ({
+ type: 'prettify',
+ path: `{{path}}/`,
+ }),
+};
diff --git a/generators/component/index.js.hbs b/generators/react/component/index.js.hbs
similarity index 100%
rename from generators/component/index.js.hbs
rename to generators/react/component/index.js.hbs
diff --git a/generators/react/component/new/index.js b/generators/react/component/new/index.js
new file mode 100644
index 0000000..a6f1b1e
--- /dev/null
+++ b/generators/react/component/new/index.js
@@ -0,0 +1,48 @@
+/**
+ * Component Generator
+ */
+
+/* eslint strict: ["off"] */
+
+('use strict');
+
+const existing = require('../existing');
+const cwd = process.cwd();
+
+const prompts = [
+ {
+ type: 'input',
+ name: 'name',
+ message: 'What should it be called?',
+ default: 'Button',
+ },
+ {
+ type: 'confirm',
+ name: 'memo',
+ default: false,
+ message: 'Do you want to wrap your component in React.memo?',
+ },
+];
+prompts.unshift(existing.pathPrompt);
+prompts.push(existing.storyPrompt);
+
+module.exports = {
+ description: 'Add an unconnected component',
+ prompts,
+ actions: data => {
+ // Generate index.js and index.test.js
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/index.js`,
+ templateFile: './component/index.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ actions.push(...existing.actions(data));
+ actions.push(existing.prettier());
+
+ return actions;
+ },
+};
diff --git a/generators/component/stories.js.hbs b/generators/react/component/stories.js.hbs
similarity index 100%
rename from generators/component/stories.js.hbs
rename to generators/react/component/stories.js.hbs
diff --git a/generators/component/test.js.hbs b/generators/react/component/test.js.hbs
similarity index 100%
rename from generators/component/test.js.hbs
rename to generators/react/component/test.js.hbs
diff --git a/generators/container/Loadable.js.hbs b/generators/react/container/Loadable.js.hbs
similarity index 100%
rename from generators/container/Loadable.js.hbs
rename to generators/react/container/Loadable.js.hbs
diff --git a/generators/react/container/existing/index.js b/generators/react/container/existing/index.js
new file mode 100644
index 0000000..c823787
--- /dev/null
+++ b/generators/react/container/existing/index.js
@@ -0,0 +1,81 @@
+/**
+ * Container Generator
+ */
+
+const cwd = process.cwd();
+module.exports = {
+ description: 'Add test for an existing container component',
+ prompts: [
+ {
+ type: 'input',
+ name: 'path',
+ message: 'What is the container directory? (app/containers)',
+ default: 'app/containers',
+ },
+ {
+ type: 'input',
+ name: 'name',
+ message: 'Which container do you want to add tests for?',
+ default: 'Form',
+ },
+ {
+ type: 'confirm',
+ name: 'wantActionsAndReducer',
+ default: true,
+ message:
+ 'Do you want add tests for actions, selectors & reducer tuple for this container?',
+ },
+ {
+ type: 'confirm',
+ name: 'wantSaga',
+ default: true,
+ message: 'Do you want to add tests for sagas?',
+ },
+ ],
+ actions: data => {
+ // Generate index.js and index.test.js
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/tests/index.test.js`,
+ templateFile: './container/test.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ // If they want actions and a reducer, generate reducer.js,
+ // and the corresponding tests for actions and the reducer
+ if (data.wantActionsAndReducer) {
+ // Selectors
+ actions.push({
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/tests/selectors.test.js`,
+ templateFile: './container/selectors.test.js.hbs',
+ abortOnFail: true,
+ });
+
+ // Reducer
+ actions.push({
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/tests/reducer.test.js`,
+ templateFile: './container/reducer.test.js.hbs',
+ abortOnFail: true,
+ });
+ }
+
+ // Sagas
+ if (data.wantSaga) {
+ actions.push({
+ type: 'add',
+ path: `${cwd}/{{path}}/{{properCase name}}/tests/saga.test.js`,
+ templateFile: './container/saga.test.js.hbs',
+ abortOnFail: true,
+ });
+ }
+ return actions;
+ },
+ prettier: () => ({
+ type: 'prettify',
+ path: `{{path}}/`,
+ }),
+};
diff --git a/generators/container/index.js.hbs b/generators/react/container/index.js.hbs
similarity index 100%
rename from generators/container/index.js.hbs
rename to generators/react/container/index.js.hbs
diff --git a/generators/container/new/index.js b/generators/react/container/new/index.js
similarity index 100%
rename from generators/container/new/index.js
rename to generators/react/container/new/index.js
diff --git a/generators/react/container/reducer.js.hbs b/generators/react/container/reducer.js.hbs
new file mode 100644
index 0000000..e633aa6
--- /dev/null
+++ b/generators/react/container/reducer.js.hbs
@@ -0,0 +1,27 @@
+/*
+ *
+ * {{ properCase name }} reducer
+ *
+ */
+import produce from 'immer'
+import { fromJS } from 'immutable'
+import { createActions } from 'reduxsauce'
+
+export const initialState = fromJS({})
+
+export const { Types: {{ camelCase name }}Types, Creators: {{ camelCase name }}Creators } = createActions({
+ defaultAction: ['somePayload']
+})
+
+/* eslint-disable default-case, no-param-reassign */
+export const {{ camelCase name }}Reducer = (state = initialState, action) =>
+ produce(state, (/* draft */) => {
+ switch (action.type) {
+ case {{ camelCase name}}Types.DEFAULT_ACTION:
+ return state.set('somePayload', action.somePayload)
+ default:
+ return state
+ }
+ })
+
+export default {{ camelCase name }}Reducer
diff --git a/generators/react/container/reducer.test.js.hbs b/generators/react/container/reducer.test.js.hbs
new file mode 100644
index 0000000..fbdb10b
--- /dev/null
+++ b/generators/react/container/reducer.test.js.hbs
@@ -0,0 +1,25 @@
+// import produce from 'immer'
+import { fromJS } from 'immutable';
+import { {{ camelCase name }}Reducer, {{ camelCase name }}Types, initialState } from '../reducer'
+
+/* eslint-disable default-case, no-param-reassign */
+describe('{{ properCase name }} reducer tests', () => {
+ let state
+ beforeEach(() => {
+ state = initialState
+ })
+
+ it('should return the initial state', () => {
+ expect({{ camelCase name }}Reducer(undefined, {})).toEqual(state)
+ })
+
+ it('should return the update the state when an action of type DEFAULT is dispatched', () => {
+ const expectedResult = fromJS(state.toJS()).set('somePayload', 'Mohammed Ali Chherawalla')
+ expect(
+ {{ camelCase name }}Reducer(state, {
+ type: {{ camelCase name}}Types.DEFAULT_ACTION,
+ somePayload: 'Mohammed Ali Chherawalla'
+ })
+ ).toEqual(expectedResult)
+ })
+})
diff --git a/generators/react/container/saga.js.hbs b/generators/react/container/saga.js.hbs
new file mode 100644
index 0000000..3af9fee
--- /dev/null
+++ b/generators/react/container/saga.js.hbs
@@ -0,0 +1,13 @@
+import { takeLatest } from 'redux-saga/effects'
+import { {{ camelCase name}}Types } from './reducer'
+// Individual exports for testing
+const { DEFAULT_ACTION } = {{ camelCase name }}Types
+
+export function *defaultFunction (/* action */) {
+ // console.log('Do something here')
+
+}
+
+export default function* {{ camelCase name }}Saga() {
+ yield takeLatest(DEFAULT_ACTION, defaultFunction)
+}
\ No newline at end of file
diff --git a/generators/react/container/saga.test.js.hbs b/generators/react/container/saga.test.js.hbs
new file mode 100644
index 0000000..7c6acb1
--- /dev/null
+++ b/generators/react/container/saga.test.js.hbs
@@ -0,0 +1,18 @@
+/**
+ * Test {{ camelCase name }} sagas
+ */
+
+/* eslint-disable redux-saga/yield-effects */
+import { takeLatest } from 'redux-saga/effects'
+import {{ camelCase name}}Saga, { defaultFunction } from '../saga'
+import { {{ camelCase name}}Types } from '../reducer'
+
+describe('{{ properCase name }} saga tests', () => {
+ const generator = {{ camelCase name }}Saga()
+
+ it('should start task to watch for DEFAULT_ACTION action', () => {
+ expect(generator.next().value).toEqual(
+ takeLatest({{ camelCase name }}Types.DEFAULT_ACTION, defaultFunction)
+ )
+ })
+})
\ No newline at end of file
diff --git a/generators/react/container/selectors.js.hbs b/generators/react/container/selectors.js.hbs
new file mode 100644
index 0000000..69dcf73
--- /dev/null
+++ b/generators/react/container/selectors.js.hbs
@@ -0,0 +1,14 @@
+import { createSelector } from 'reselect'
+import { initialState } from './reducer'
+
+/**
+ * Direct selector to the {{ camelCase name }} state domain
+ */
+
+const select{{ properCase name }}Domain = state => (state.{{ camelCase name }} || initialState).toJS()
+
+const makeSelect{{ properCase name }} = () =>
+ createSelector(select{{ properCase name }}Domain, substate => substate)
+
+export default makeSelect{{ properCase name }}
+export { select{{ properCase name }}Domain }
diff --git a/generators/react/container/selectors.test.js.hbs b/generators/react/container/selectors.test.js.hbs
new file mode 100644
index 0000000..f690cd1
--- /dev/null
+++ b/generators/react/container/selectors.test.js.hbs
@@ -0,0 +1,16 @@
+import { fromJS } from 'immutable'
+import { select{{ properCase name }}Domain } from '../selectors'
+
+describe('{{ properCase name }} selector tests', () => {
+ let mockedState
+
+ beforeEach(() => {
+ mockedState = {
+ {{ camelCase name }}: fromJS({})
+ }
+ })
+
+ it('should select the user state', () => {
+ expect(select{{ properCase name }}Domain(mockedState)).toEqual(mockedState.{{ camelCase name }}.toJS())
+ })
+})
\ No newline at end of file
diff --git a/generators/react/container/test.js.hbs b/generators/react/container/test.js.hbs
new file mode 100644
index 0000000..0446d14
--- /dev/null
+++ b/generators/react/container/test.js.hbs
@@ -0,0 +1,26 @@
+/**
+ *
+ * Tests for {{ properCase name }}
+ *
+ *
+ */
+
+
+import React from 'react'
+import { renderProvider } from '@utils/testUtils'
+// import { fireEvent } from '@testing-library/dom'
+import { {{ properCase name }}Test as {{ properCase name }} } from '../index'
+
+describe('<{{ properCase name }} /> container tests', () => {
+ // let submitSpy
+
+ beforeEach(() => {
+ // submitSpy = jest.fn()
+ })
+ it('should render and match the snapshot', () => {
+ const { baseElement } = renderProvider(
+ <{{ properCase name }} />
+ )
+ expect(baseElement).toMatchSnapshot()
+ })
+})
\ No newline at end of file
diff --git a/generators/react/index.js b/generators/react/index.js
new file mode 100644
index 0000000..aad50c9
--- /dev/null
+++ b/generators/react/index.js
@@ -0,0 +1,65 @@
+/**
+ * generator/index.js
+ *
+ * Exports the generators so plop knows them
+ */
+
+const fs = require('fs');
+const path = require('path');
+const shell = require('shelljs');
+const { execSync } = require('child_process');
+const prettier = path.join(__dirname, '../../node_modules/.bin/prettier');
+const componentGenerator = require(`./component/${
+ shell.env.GENERATOR_TYPE
+}/index.js`);
+const containerGenerator = require(`./container/${
+ shell.env.GENERATOR_TYPE
+}/index.js`);
+const testUtilGenerator = require(`./testUtil/index.js`);
+const loadableUtilGenerator = require(`./loadable/index.js`);
+const injectSagaUtilGenerator = require(`./injectSaga/index.js`);
+const webpackBaseBabelGenerator = require(`./webpack/base/babel/index.js`);
+
+/**
+ * Every generated backup file gets this extension
+ * @type {string}
+ */
+const BACKUPFILE_EXTENSION = 'rbgen';
+
+module.exports = plop => {
+ plop.setGenerator('component', componentGenerator);
+ plop.setGenerator('container', containerGenerator);
+ plop.setGenerator('tUtil', testUtilGenerator);
+ plop.setGenerator('loadable', loadableUtilGenerator);
+ plop.setGenerator('injectSaga', injectSagaUtilGenerator);
+ plop.setGenerator('webpackBaseBabel', webpackBaseBabelGenerator);
+
+ 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 => {
+ const folderPath = `${path.join(
+ `${process.cwd()}/${answers.path}/`,
+ plop.getHelper('properCase')(answers.name),
+ '**/*.*.js',
+ )}`;
+
+ try {
+ execSync(`${prettier} --write -- "${folderPath}"`);
+ return folderPath;
+ } catch (err) {
+ throw new Error(`Prettier failed`);
+ }
+ });
+};
+
+module.exports.BACKUPFILE_EXTENSION = BACKUPFILE_EXTENSION;
diff --git a/generators/react/injectSaga/index.js b/generators/react/injectSaga/index.js
new file mode 100644
index 0000000..ceedaf3
--- /dev/null
+++ b/generators/react/injectSaga/index.js
@@ -0,0 +1,42 @@
+/**
+ * TestUtil Generator
+ */
+
+/* eslint strict: ["off"] */
+
+'use strict';
+
+const cwd = process.cwd();
+module.exports = {
+ description: 'Add support for injecting sagas',
+ prompts: [
+ {
+ type: 'input',
+ name: 'path',
+ message: 'What is the utils directory? (app/utils)',
+ default: 'app/utils',
+ },
+ ],
+ actions: () => {
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/injectSaga.js`,
+ templateFile: './injectSaga/injectSaga.js.hbs',
+ abortOnFail: true,
+ },
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/sagaInjectors.js`,
+ templateFile: './injectSaga/sagaInjectors.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ return actions;
+ },
+ prettier: () => ({
+ type: 'prettify',
+ path: `{{path}}/`,
+ }),
+};
diff --git a/generators/react/injectSaga/injectSaga.js.hbs b/generators/react/injectSaga/injectSaga.js.hbs
new file mode 100644
index 0000000..e734a04
--- /dev/null
+++ b/generators/react/injectSaga/injectSaga.js.hbs
@@ -0,0 +1,60 @@
+import React from 'react';
+import hoistNonReactStatics from 'hoist-non-react-statics';
+import { ReactReduxContext } from 'react-redux';
+
+import getInjectors from './sagaInjectors';
+
+/**
+ * Dynamically injects a saga, passes component's props as saga arguments
+ *
+ * @param {string} key A key of the saga
+ * @param {function} saga A root saga that will be injected
+ * @param {string} [mode] By default (constants.DAEMON) the saga will be started
+ * on component mount and never canceled or started again. Another two options:
+ * - constants.RESTART_ON_REMOUNT — the saga will be started on component mount and
+ * cancelled with `task.cancel()` on component unmount for improved performance,
+ * - constants.ONCE_TILL_UNMOUNT — behaves like 'RESTART_ON_REMOUNT' but never runs it again.
+ *
+ */
+export default ({ key, saga, mode }) => WrappedComponent => {
+ class InjectSaga extends React.Component {
+ static WrappedComponent = WrappedComponent;
+
+ static contextType = ReactReduxContext;
+
+ static displayName = `withSaga(${WrappedComponent.displayName ||
+ WrappedComponent.name ||
+ 'Component'})`;
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.injectors = getInjectors(context.store);
+
+ this.injectors.injectSaga(key, { saga, mode }, this.props);
+ }
+
+ componentWillUnmount() {
+ this.injectors.ejectSaga(key);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ return hoistNonReactStatics(InjectSaga, WrappedComponent);
+};
+
+const useInjectSaga = ({ key, saga, mode }) => {
+ const context = React.useContext(ReactReduxContext);
+ React.useEffect(() => {
+ const injectors = getInjectors(context.store);
+ injectors.injectSaga(key, { saga, mode });
+ return () => {
+ injectors.ejectSaga(key);
+ };
+ }, []);
+};
+
+export { useInjectSaga };
diff --git a/generators/react/injectSaga/sagaInjectors.js.hbs b/generators/react/injectSaga/sagaInjectors.js.hbs
new file mode 100644
index 0000000..a13bf2f
--- /dev/null
+++ b/generators/react/injectSaga/sagaInjectors.js.hbs
@@ -0,0 +1,111 @@
+import invariant from 'invariant';
+import { isEmpty, isFunction, isObject, isString, conformsTo } from 'lodash';
+
+
+const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount';
+const DAEMON = '@@saga-injector/daemon';
+const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount';
+
+const allowedModes = [RESTART_ON_REMOUNT, DAEMON, ONCE_TILL_UNMOUNT];
+
+const checkKey = key =>
+ invariant(
+ isString(key) && !isEmpty(key),
+ '(app/utils...) injectSaga: Expected `key` to be a non empty string'
+ );
+
+const checkDescriptor = descriptor => {
+ const shape = {
+ saga: isFunction,
+ mode: mode => isString(mode) && allowedModes.includes(mode)
+ };
+ invariant(
+ conformsTo(descriptor, shape),
+ '(app/utils...) injectSaga: Expected a valid saga descriptor'
+ );
+};
+
+export function injectSagaFactory(store, isValid) {
+ return function injectSaga(key, descriptor = {}, args) {
+ if (!isValid) checkStore(store);
+
+ const newDescriptor = {
+ ...descriptor,
+ mode: descriptor.mode || DAEMON
+ };
+ const { saga, mode } = newDescriptor;
+
+ checkKey(key);
+ checkDescriptor(newDescriptor);
+
+ let hasSaga = Reflect.has(store.injectedSagas, key);
+
+ if (process.env.NODE_ENV !== 'production') {
+ const oldDescriptor = store.injectedSagas[key];
+ // enable hot reloading of daemon and once-till-unmount sagas
+ if (hasSaga && oldDescriptor.saga !== saga) {
+ oldDescriptor.task.cancel();
+ hasSaga = false;
+ }
+ }
+
+ if (
+ !hasSaga ||
+ (hasSaga && mode !== DAEMON && mode !== ONCE_TILL_UNMOUNT)
+ ) {
+ /* eslint-disable no-param-reassign */
+ store.injectedSagas[key] = {
+ ...newDescriptor,
+ task: store.runSaga(saga, args)
+ };
+ /* eslint-enable no-param-reassign */
+ }
+ };
+}
+
+export function ejectSagaFactory(store, isValid) {
+ return function ejectSaga(key) {
+ if (!isValid) checkStore(store);
+
+ checkKey(key);
+
+ if (Reflect.has(store.injectedSagas, key)) {
+ const descriptor = store.injectedSagas[key];
+ if (descriptor.mode && descriptor.mode !== DAEMON) {
+ descriptor.task.cancel();
+ // Clean up in production; in development we need `descriptor.saga` for hot reloading
+ if (process.env.NODE_ENV === 'production') {
+ // Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga`
+ store.injectedSagas[key] = 'done'; // eslint-disable-line no-param-reassign
+ }
+ }
+ }
+ };
+}
+
+export default function getInjectors(store) {
+ checkStore(store);
+ return {
+ injectSaga: injectSagaFactory(store, true),
+ ejectSaga: ejectSagaFactory(store, true)
+ };
+}
+
+/**
+ * Validate the shape of redux store
+ */
+function checkStore(store) {
+ const shape = {
+ dispatch: isFunction,
+ subscribe: isFunction,
+ getState: isFunction,
+ replaceReducer: isFunction,
+ runSaga: isFunction,
+ injectedReducers: isObject,
+ injectedSagas: isObject
+ };
+ invariant(
+ conformsTo(store, shape),
+ '(app/utils...) injectors: Expected a valid redux store'
+ );
+}
\ No newline at end of file
diff --git a/generators/react/loadable/index.js b/generators/react/loadable/index.js
new file mode 100644
index 0000000..f03edf3
--- /dev/null
+++ b/generators/react/loadable/index.js
@@ -0,0 +1,36 @@
+/**
+ * Loadable Generator
+ */
+
+/* eslint strict: ["off"] */
+
+'use strict';
+
+const cwd = process.cwd();
+module.exports = {
+ description: 'Create a loadable util file ',
+ prompts: [
+ {
+ type: 'input',
+ name: 'path',
+ message: 'What is the utils directory? (app/utils)',
+ default: 'app/utils',
+ },
+ ],
+ actions: () => {
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/loadable.js`,
+ templateFile: './loadable/loadable.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ return actions;
+ },
+ prettier: () => ({
+ type: 'prettify',
+ path: `{{path}}/`,
+ }),
+};
diff --git a/generators/react/loadable/loadable.js.hbs b/generators/react/loadable/loadable.js.hbs
new file mode 100644
index 0000000..f8e956d
--- /dev/null
+++ b/generators/react/loadable/loadable.js.hbs
@@ -0,0 +1,13 @@
+import React, { lazy, Suspense } from 'react'
+
+const loadable = (importFunc, { fallback = null } = { fallback: null }) => {
+ const LazyComponent = lazy(importFunc)
+
+ return props => (
+
+
+
+ )
+}
+
+export default loadable
diff --git a/generators/react/testUtil/index.js b/generators/react/testUtil/index.js
new file mode 100644
index 0000000..c46723a
--- /dev/null
+++ b/generators/react/testUtil/index.js
@@ -0,0 +1,36 @@
+/**
+ * TestUtil Generator
+ */
+
+/* eslint strict: ["off"] */
+
+'use strict';
+
+const cwd = process.cwd();
+module.exports = {
+ description: 'Create a test util file',
+ prompts: [
+ {
+ type: 'input',
+ name: 'path',
+ message: 'What is the utils directory? (app/utils)',
+ default: 'app/utils',
+ },
+ ],
+ actions: () => {
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/{{path}}/testUtils.js`,
+ templateFile: './testUtil/testUtils.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ return actions;
+ },
+ prettier: () => ({
+ type: 'prettify',
+ path: `{{path}}/`,
+ }),
+};
diff --git a/generators/react/testUtil/testUtils.js.hbs b/generators/react/testUtil/testUtils.js.hbs
new file mode 100644
index 0000000..954bd8a
--- /dev/null
+++ b/generators/react/testUtil/testUtils.js.hbs
@@ -0,0 +1,51 @@
+import React from 'react'
+import { IntlProvider } from 'react-intl'
+import { render } from '@testing-library/react'
+import { Provider } from 'react-redux'
+import configureStore from '@app/configureStore'
+import { DEFAULT_LOCALE, translationMessages } from '@app/i18n'
+import ConnectedLanguageProvider from '@containers/LanguageProvider'
+import { BrowserRouter as Router, browserHistory } from 'react-router-dom'
+import { ThemeProvider } from 'styled-components'
+
+export const timeout = ms => new Promise(resolve => setTimeout(resolve, ms))
+
+export const apiResponseGenerator = (ok, data) => ({
+ ok,
+ data
+})
+
+export const renderProvider = children => {
+ const store = configureStore({}, browserHistory)
+ const theme = {main: 'violet'}
+ return render(
+
+
+
+ {children}
+
+
+
+ )
+}
+
+export const renderWithIntl = children =>
+ render(
+
+ {children}
+
+ )
+
+export const getComponentStyles = (Component, props = {}) => {
+ renderWithIntl(Component(props))
+ const { styledComponentId } = Component(props).type
+ const componentRoots = document.getElementsByClassName(styledComponentId)
+ // eslint-disable-next-line no-underscore-dangle
+ return window.getComputedStyle(componentRoots[0])._values
+}
+
+export const renderWithRouterAndIntl = children =>
+ renderWithIntl({children});
diff --git a/generators/react/webpack/base/babel/babel.js.hbs b/generators/react/webpack/base/babel/babel.js.hbs
new file mode 100644
index 0000000..9336399
--- /dev/null
+++ b/generators/react/webpack/base/babel/babel.js.hbs
@@ -0,0 +1,177 @@
+/**
+ * COMMON WEBPACK CONFIGURATION
+ */
+const path = require('path');
+const webpack = require('webpack');
+const dotenv = require('dotenv');
+const colors = require('../../app/themes/colors');
+
+const dotEnvFile =
+ process.env.NODE_ENV === 'production'
+ ? `.env`
+ : `.env.${process.env.NODE_ENV}`;
+const env = dotenv.config({ path: dotEnvFile }).parsed;
+const envKeys = {
+ ...Object.keys(process.env).reduce((prev, next) => {
+ prev[`process.env.${next}`] = JSON.stringify(process.env[next]);
+ return prev;
+ }, {}),
+ ...Object.keys(env).reduce((prev, next) => {
+ prev[`process.env.${next}`] = JSON.stringify(env[next]);
+ return prev;
+ }, {})
+};
+
+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
+ optimization: options.optimization,
+ module: {
+ rules: [
+ {
+ test: /\.jsx?$/, // Transform all .js and .jsx files required somewhere with Babel
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader',
+ 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']
+ },
+ {
+ test: /\.less$/,
+ use: [
+ {
+ loader: 'style-loader'
+ },
+ {
+ loader: 'css-loader'
+ },
+ {
+ loader: 'less-loader',
+ options: {
+ lessOptions: {
+ javascriptEnabled: true,
+ modifyVars: {
+ 'primary-color': colors.secondary
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ // 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: /\.svg$/,
+ use: [
+ {
+ loader: 'svg-url-loader',
+ options: {
+ // Inline files smaller than 10 kB
+ limit: 10 * 1024,
+ noquotes: true
+ }
+ }
+ ]
+ },
+ {
+ test: /\.(jpg|png|gif)$/,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ // Inline files smaller than 10 kB
+ limit: 10 * 1024
+ }
+ },
+ {
+ 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
+ }
+ }
+ }
+ ]
+ },
+ 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 webpack.EnvironmentPlugin({
+ NODE_ENV: 'development'
+ }),
+ new webpack.DefinePlugin(envKeys)
+ ]),
+ resolve: {
+ modules: ['node_modules', 'app'],
+ alias: {
+ '@app': path.resolve(__dirname, '../../app'),
+ '@components': path.resolve(__dirname, '../../app/components'),
+ '@containers': path.resolve(__dirname, '../../app/containers'),
+ '@utils': path.resolve(__dirname, '../../app/utils'),
+ '@services': path.resolve(__dirname, '../../app/services'),
+ '@themes': path.resolve(__dirname, '../../app/themes'),
+ '@images': path.resolve(__dirname, '../../app/images'),
+ '@hooks': path.resolve(__dirname, '../../app/hooks'),
+ moment$: path.resolve(__dirname, '../../node_modules/moment/moment.js')
+ },
+ extensions: ['.js', '.jsx', '.react.js'],
+ mainFields: ['browser', 'jsnext:main', 'main']
+ },
+ devtool: options.devtool,
+ target: 'web', // Make web variables accessible to webpack, e.g. window
+ performance: options.performance || {}
+});
\ No newline at end of file
diff --git a/generators/react/webpack/base/babel/index.js b/generators/react/webpack/base/babel/index.js
new file mode 100644
index 0000000..7a03ab1
--- /dev/null
+++ b/generators/react/webpack/base/babel/index.js
@@ -0,0 +1,28 @@
+/**
+ * TestUtil Generator
+ */
+
+/* eslint strict: ["off"] */
+
+'use strict';
+
+const cwd = process.cwd();
+module.exports = {
+ prompts: [],
+ actions: () => {
+ const actions = [
+ {
+ type: 'add',
+ path: `${cwd}/internals/webpack/webpack.base.babel.js`,
+ templateFile: './webpack/base/babel/babel.js.hbs',
+ abortOnFail: true,
+ },
+ ];
+
+ return actions;
+ },
+ prettier: () => ({
+ type: 'prettify',
+ path: `${cwd}/`,
+ }),
+};
diff --git a/package.json b/package.json
index c93d911..f05d1a7 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
},
"@babel/cli": "7.4.3",
"bin": {
- "react-generate": "bin/react-generate.js"
+ "react-generate": "bin/react-generate.js",
+ "react-native-generate": "bin/react-native-generate.js"
}
}