├── setupJest.js
├── src
├── components
│ ├── Hello.css
│ ├── shared
│ │ ├── Loading.css
│ │ ├── EditableDiv.css
│ │ ├── Loading.js
│ │ └── EditableDiv.js
│ ├── Hello.js
│ └── JsonAPI.js
├── config
│ └── actionType.js
├── styles
│ └── global.css
├── reducers
│ ├── index.js
│ ├── jsonAPI.js
│ └── hello.js
├── actions
│ ├── api.js
│ ├── jsonAPI.js
│ └── hello.js
├── matchConfig.js
├── containers
│ ├── JsonAPI.js
│ ├── HelloWorld.js
│ └── PreloadHelloWorld.js
├── server.js
├── clientRender.js
└── serverRender.js
├── .gitignore
├── postcss.config.js
├── .storybook
├── config.js
└── webpack.config.js
├── .travis.yml
├── __functional__
└── actions
│ └── api.js
├── __tests__
├── actions
│ ├── hello.js
│ └── jsonAPI.js
├── reducers
│ ├── jsonAPI.js
│ └── hello.js
├── components
│ └── Hello.js
└── containers
│ └── HelloWorld.js
├── .babelrc
├── __e2e__
├── specs
│ └── hello_spec.js
└── config.js
├── stories
└── index.js
├── LICENSE
├── webpack.config.js
├── webpack.prod.config.js
├── package.json
├── README.md
└── .eslintrc.js
/setupJest.js:
--------------------------------------------------------------------------------
1 | global.fetch = require('jest-fetch-mock');
--------------------------------------------------------------------------------
/src/components/Hello.css:
--------------------------------------------------------------------------------
1 |
2 | .title {
3 | color: red;
4 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | build
4 | public
5 | .DS_Store
6 | dist
7 | public
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')({browsers: ['Android >= 4.1', 'ie >= 9']})
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/config/actionType.js:
--------------------------------------------------------------------------------
1 | export const HELLO_WORLD = 'HELLO_WORLD'
2 | export const SET_MESSAGE = 'SET_MESSAGE'
3 |
4 | export const SET_API_DATA = 'SET_API_DATA'
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | function loadStories() {
4 | require('../stories');
5 | }
6 |
7 | configure(loadStories, module);
8 |
--------------------------------------------------------------------------------
/src/styles/global.css:
--------------------------------------------------------------------------------
1 | :global(body) {
2 | font-family: PingFangTC, Helvetica Neue, Helvetica, Arial, STHeiti, Microsoft JhengHei, 微軟正黑體, sans-serif;
3 | background: green;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/shared/Loading.css:
--------------------------------------------------------------------------------
1 | .loaderBox {
2 | position: absolute;
3 | z-index: 1;
4 | }
5 |
6 | .loaderBox img {
7 | position: fixed;
8 | left: 50%;
9 | top: 45%;
10 | margin: 0 auto;
11 | width: 50px;
12 | }
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import hello from './hello'
3 | import jsonAPI from './jsonAPI'
4 |
5 | const reducer = combineReducers({
6 | jsonAPI,
7 | hello
8 | })
9 |
10 | export default reducer
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "6"
5 |
6 | cache:
7 | directories:
8 | - node_modules
9 |
10 | script:
11 | - npm run test
12 |
13 | notifications:
14 | email:
15 | - wahengchang@gmail.com
16 |
--------------------------------------------------------------------------------
/src/actions/api.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch'
2 |
3 | const actionCreator = {
4 | getJsonAPI: (_username) => {
5 | return (
6 | fetch(`https://jsonplaceholder.typicode.com/posts`)
7 | .then(response => response.json())
8 | )
9 | }
10 | }
11 |
12 | export default actionCreator
13 |
--------------------------------------------------------------------------------
/__functional__/actions/api.js:
--------------------------------------------------------------------------------
1 | import apis from '../../src/actions/api.js'
2 |
3 | describe('api action creator', () => {
4 | it('get github user', (done) => {
5 | apis.getJsonAPI().then(
6 | (res) => {
7 | expect(res[0].title).toMatch('');
8 | done();
9 | },
10 | (err) => done(err)
11 | )
12 | })
13 | })
--------------------------------------------------------------------------------
/__tests__/actions/hello.js:
--------------------------------------------------------------------------------
1 | import helloActionCreator from '../../src/actions/hello.js'
2 |
3 | describe('helloActionCreator', () => {
4 | it('should create an action for HelloWorld', () => {
5 | const dispatch = jest.fn();
6 | helloActionCreator.helloWorld()(dispatch, {});
7 | expect(dispatch).toBeCalledWith({ type: 'HELLO_WORLD' });
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/src/reducers/jsonAPI.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_API_DATA
3 | } from '../config/actionType'
4 |
5 | const apiReducer = (state = {products: []}, action) => {
6 | switch (action.type) {
7 | case SET_API_DATA:
8 | return Object.assign({}, state, { products: action.products })
9 | default:
10 | return state
11 | }
12 | }
13 |
14 | export default apiReducer
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets":[
3 | "es2015", "react", "stage-2"
4 | ],
5 | "env": {
6 | "development": {
7 | "presets": ["es2015", "react", "stage-0"],
8 | "plugins": ["transform-runtime", "transform-object-rest-spread"],
9 | "presets": ["react-hmre"]
10 | }
11 | },
12 | "env": {
13 | "build": {
14 | "presets": ["es2015", "react", "stage-0"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/actions/jsonAPI.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_API_DATA
3 | } from '../config/actionType'
4 |
5 | import apis from './api.js'
6 |
7 | const jsonAPIActionCreator = {
8 | getJsonAPI: () => {
9 | return function (dispatch, getState) {
10 | return apis.getJsonAPI().then((res) => {
11 | dispatch({
12 | type: SET_API_DATA,
13 | products: res
14 | })
15 | })
16 | }
17 | }
18 | }
19 | export default jsonAPIActionCreator
--------------------------------------------------------------------------------
/src/reducers/hello.js:
--------------------------------------------------------------------------------
1 | import { HELLO_WORLD, SET_MESSAGE } from '../config/actionType'
2 |
3 | const helloWorld = (state = { message: 'Hello' }, action) => {
4 | switch (action.type) {
5 | case HELLO_WORLD:
6 | return Object.assign({}, state, { message: 'Hello, World!' })
7 | case SET_MESSAGE:
8 | return Object.assign({}, state, { message: action.message })
9 | default:
10 | return state
11 | }
12 | }
13 |
14 | export default helloWorld
15 |
--------------------------------------------------------------------------------
/__e2e__/specs/hello_spec.js:
--------------------------------------------------------------------------------
1 | // spec.js
2 |
3 | var url = 'http://localhost:3000/topic'
4 |
5 | describe('Protractor Demo App1', function() {
6 | browser.driver.get(url);
7 |
8 | beforeEach(function() {
9 | return browser.ignoreSynchronization = true;
10 | });
11 |
12 | it('should greet the named user', function() {
13 | expect($('h1').getText()).toEqual('Hello');
14 | $('button').click();
15 | expect($('h1').getText()).toEqual('Hello, World!');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/__tests__/reducers/jsonAPI.js:
--------------------------------------------------------------------------------
1 | import apiReducer from '../../src/reducers/jsonAPI.js'
2 |
3 | import { createStore } from 'redux'
4 | let store = createStore(apiReducer, {})
5 |
6 | describe('github reducer', () => {
7 | it('should return the initial state', (done) => {
8 | const _products = [{a: 'a'}];
9 |
10 | store.dispatch({
11 | type: 'SET_API_DATA',
12 | products: _products
13 | });
14 |
15 | expect(store.getState().products).toBe(_products)
16 | done()
17 | })
18 | })
--------------------------------------------------------------------------------
/src/actions/hello.js:
--------------------------------------------------------------------------------
1 | import { HELLO_WORLD, SET_MESSAGE } from '../config/actionType'
2 |
3 | const helloActionCreator = {
4 | helloWorld: () => {
5 | return (dispatch, getState) => {
6 | return dispatch({
7 | type: HELLO_WORLD,
8 | })
9 | }
10 | },
11 | setMessage: (_message) => {
12 | return (dispatch, getState) => {
13 | return dispatch({
14 | type: SET_MESSAGE,
15 | message: _message
16 | })
17 | }
18 | }
19 | }
20 | export default helloActionCreator
--------------------------------------------------------------------------------
/src/components/Hello.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classNames from 'classnames'
4 | import style from './Hello.css'
5 |
6 | const Hello = ({ onClick, message }) => {
7 | return (
8 |
9 |
{ message }
10 |
11 |
12 | )
13 | }
14 |
15 | Hello.propTypes = {
16 | onClick: PropTypes.func.isRequired,
17 | message: PropTypes.string.isRequired
18 | }
19 |
20 | export default Hello
21 |
--------------------------------------------------------------------------------
/src/matchConfig.js:
--------------------------------------------------------------------------------
1 |
2 | import HelloWorld from './containers/HelloWorld'
3 | import PreloadHelloWorld from './containers/PreloadHelloWorld'
4 | import JsonAPI from './containers/JsonAPI'
5 |
6 | const matchConfig = [
7 | {
8 | path: '/api',
9 | component: JsonAPI,
10 | initState: JsonAPI.initState
11 | },
12 | {
13 | path: '/preload',
14 | component: PreloadHelloWorld,
15 | initState: PreloadHelloWorld.initState
16 | },
17 | {
18 | path: '/',
19 | component: HelloWorld,
20 | initState: HelloWorld.initState,
21 | exact: false
22 | }
23 | ]
24 |
25 | export default matchConfig
26 |
--------------------------------------------------------------------------------
/src/components/shared/EditableDiv.css:
--------------------------------------------------------------------------------
1 | .single-line {
2 | white-space: nowrap;
3 | overflow: hidden;
4 | text-overflow: ellipsis;
5 | display: inline-block;
6 | vertical-align: top;
7 | border: 1px solid #f3f3f3;
8 | }
9 |
10 | [contenteditable="true"].single-line {
11 | white-space: nowrap;
12 | width:200px;
13 | overflow-y: hidden;
14 | padding: 5px 10px;
15 | }
16 | [contenteditable="true"].single-line br {
17 | display:none;
18 |
19 | }
20 | [contenteditable="true"].single-line * {
21 | display:inline;
22 | white-space:nowrap;
23 | }
24 |
25 | .single-line:focus {
26 | text-overflow: inherit;
27 | }
--------------------------------------------------------------------------------
/__tests__/actions/jsonAPI.js:
--------------------------------------------------------------------------------
1 | import jsonAPIActionCreator from '../../src/actions/jsonAPI'
2 | import configureStore from 'redux-mock-store';
3 | import thunk from 'redux-thunk'
4 | const mockStore = configureStore([thunk])
5 |
6 | describe('api action creator', () => {
7 | it('get apiJSON title', (done) => {
8 | var mockData = [{title:'_title'}]
9 | fetch.mockResponse(JSON.stringify(mockData))
10 |
11 | const store = mockStore({})
12 | const expectedActions = [
13 | { type: 'SET_API_DATA', products: mockData }
14 | ]
15 |
16 | store.dispatch(jsonAPIActionCreator.getJsonAPI())
17 | .then((res) => {
18 | // expect(store.getActions()).toEqual(expectedActions)
19 | done()
20 | })
21 | }, 5000)
22 | })
--------------------------------------------------------------------------------
/src/components/shared/Loading.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import classNames from 'classnames'
5 | import style from './Loading.css'
6 |
7 | const LOADING_ICON = 'https://digitalsynopsis.com/wp-content/uploads/2016/06/loading-animations-preloader-gifs-ui-ux-effects-3.gif'
8 |
9 | const renderLoading = () => (
10 |
11 |

12 |
13 | )
14 |
15 | class Loading extends Component {
16 | render () {
17 | const { isLoading } = this.props
18 | return (isLoading) ? renderLoading() : null
19 | }
20 | }
21 |
22 | Loading.propTypes = {
23 | isLoading: PropTypes.bool
24 | }
25 |
26 | export default Loading
--------------------------------------------------------------------------------
/src/containers/JsonAPI.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import jsonAPIActionCreator from '../actions/jsonAPI'
3 | import JsonAPI from '../components/JsonAPI'
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | products: state.jsonAPI.products
8 | }
9 | }
10 |
11 | const mapDispatchToProps = (dispatch, ownProps) => {
12 | return {
13 | fetchData: () => {
14 | dispatch(jsonAPIActionCreator.getJsonAPI());
15 | }
16 | }
17 | }
18 |
19 | const jsonAPIContainer = connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(JsonAPI)
23 |
24 |
25 | jsonAPIContainer.initState = (store, req, res) => {
26 | return (dispatch, getState) => {
27 | return new Promise((resolve, reject) => {
28 | resolve()
29 | })
30 | }
31 | }
32 |
33 | export default jsonAPIContainer
--------------------------------------------------------------------------------
/src/containers/HelloWorld.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Hello from './../components/Hello'
3 | import helloActionCreator from '../actions/hello'
4 | import globalStyle from '../styles/global.css'
5 |
6 | const mapStateToProps = (state, ownProps) => {
7 | return {
8 | message: state.hello.message
9 | }
10 | }
11 |
12 | const mapDispatchToProps = (dispatch, ownProps) => {
13 | return {
14 | onClick: () => {
15 | dispatch(helloActionCreator.helloWorld());
16 | }
17 | }
18 | }
19 |
20 | const hello = connect(
21 | mapStateToProps,
22 | mapDispatchToProps
23 | )(Hello)
24 |
25 | hello.initState = (store, req, res) => {
26 | return (dispatch, getState) => {
27 | return new Promise((resolve, reject) => {
28 | resolve()
29 | })
30 | }
31 | }
32 |
33 |
34 | export default hello
--------------------------------------------------------------------------------
/stories/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import { action } from '@storybook/addon-actions';
5 | import { linkTo } from '@storybook/addon-links';
6 |
7 | import EditableDiv from '../src/components/shared/EditableDiv';
8 | import Loading from '../src/components/shared/Loading';
9 |
10 | storiesOf('Shared Component', module)
11 | .add('EditableDiv', () => {
12 | const onChange = _v => console.log('updated : ', _v)
13 | const onUpdate = _v => console.log('UPDATE value to ', _v) //trigged when press ENTER
14 | return
18 | })
19 | .add('Loading', () => {
20 | return
22 | })
23 |
--------------------------------------------------------------------------------
/__tests__/reducers/hello.js:
--------------------------------------------------------------------------------
1 | import helloWorld from '../../src/reducers/hello.js'
2 |
3 | describe('helloWorld reducer', () => {
4 | it('should return the initial state', (done) => {
5 | expect(
6 | helloWorld([], {})
7 | ).toEqual([])
8 | done()
9 | })
10 |
11 | it('should return the HELLO_WORLD state', (done) => {
12 | expect(
13 | helloWorld({hello:""}, {
14 | type: 'HELLO_WORLD'
15 | })
16 | ).toEqual({ hello:'', message: 'Hello, World!' })
17 | done()
18 | })
19 |
20 | it('should return the SET_MESSAGE state', (done) => {
21 |
22 | var _message = 'I am message'
23 |
24 | expect(
25 | helloWorld({hello:""}, {
26 | type: 'SET_MESSAGE',
27 | message: _message
28 | })
29 | ).toEqual({ hello:'', message: _message })
30 | done()
31 | })
32 | })
--------------------------------------------------------------------------------
/__tests__/components/Hello.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {shallow} from 'enzyme'
3 | import Hello from '../../src/components/Hello'
4 |
5 | jest.mock('../../src/components/Hello.css', () => {
6 | return jest.fn()
7 | })
8 |
9 | var _mockMessage = 'mock message'
10 | var _mockFun = jest.fn()
11 | function setup() {
12 | const props = {
13 | onClick: _mockFun,
14 | message: _mockMessage
15 | }
16 |
17 | const enzymeWrapper = shallow()
18 |
19 | return {props, enzymeWrapper}
20 | }
21 |
22 | describe('components', () => {
23 | it('should render self and subcomponents', (done) => {
24 | const {enzymeWrapper} = setup()
25 | const newMessage = 'Hello, World!'
26 |
27 | expect(enzymeWrapper.find('h1').text()).toBe(_mockMessage)
28 | enzymeWrapper.find('button').simulate('click')
29 | expect(_mockFun).toBeCalled()
30 |
31 | done()
32 | })
33 | })
--------------------------------------------------------------------------------
/src/containers/PreloadHelloWorld.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Hello from './../components/Hello'
3 | import helloActionCreator from '../actions/hello'
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | message: state.hello.message
8 | }
9 | }
10 |
11 | const mapDispatchToProps = (dispatch, ownProps) => {
12 | return {
13 | onClick: () => {
14 | dispatch(helloActionCreator.helloWorld());
15 | }
16 | }
17 | }
18 |
19 | const preloadHello = connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(Hello)
23 |
24 | preloadHello.initState = (store, req, res) => {
25 | return (dispatch, getState) => {
26 | return new Promise((resolve, reject) => {
27 | console.log('preload ......................')
28 | dispatch(helloActionCreator.helloWorld())
29 | resolve(1)
30 | })
31 | }
32 | }
33 |
34 | export default preloadHello
--------------------------------------------------------------------------------
/src/components/shared/EditableDiv.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import classNames from 'classnames'
3 | import style from './EditableDiv.css'
4 |
5 | class EditableDiv extends React.Component {
6 | constructor (props) {
7 | super(props)
8 | }
9 |
10 | render () {
11 | const { value, onChange, onUpdate } = this.props
12 | const _onChange = (e) => onChange(e.target.textContent)
13 | const css = classNames(style['single-line'])
14 | const handleKeyPress = (target) => {
15 | (target.charCode === 13)?onUpdate():null
16 | }
17 | return (
23 | {value}
24 |
)
25 | }
26 | }
27 |
28 | EditableDiv.propTypes = {
29 | }
30 |
31 | export default EditableDiv
32 |
--------------------------------------------------------------------------------
/src/components/JsonAPI.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class JsonAPI extends React.Component {
5 | componentDidMount() {
6 | this.props.fetchData()
7 | }
8 |
9 | render() {
10 | const { products } = this.props
11 | const _renderli = products.map((item, index) => {
12 | return (
13 |
14 |
{item.id}
15 | {item.title}
16 |
17 |
{item.body}
18 |
19 | )
20 | })
21 |
22 | return (
23 |
30 | )
31 | }
32 | }
33 |
34 | JsonAPI.propTypes = {
35 | fetchData: PropTypes.func.isRequired,
36 | products: PropTypes.array.isRequired
37 | }
38 |
39 | export default JsonAPI
40 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
2 | // This is just the basic way to add additional webpack configurations.
3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config
4 |
5 | // IMPORTANT
6 | // When you add this file, we won't add the default configurations which is similar
7 | // to "React Create App". This only has babel loader to load JavaScript.
8 |
9 | const path = require('path');
10 |
11 | module.exports = {
12 | plugins: [
13 | // your custom plugins
14 | ],
15 | module: {
16 | rules: [
17 | {
18 | test: /\.css$/,
19 | use: [
20 | 'style-loader',
21 | {
22 | loader: 'css-loader',
23 | options: {
24 | modules: true,
25 | importLoaders: 1,
26 | localIdentName: '[name]__[local]___[hash:base64:5]'
27 | }
28 | },
29 | {
30 | loader: 'postcss-loader'
31 | }
32 | ]
33 | }
34 | ]
35 | },
36 | };
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Vaibhav Mule
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/__tests__/containers/HelloWorld.js:
--------------------------------------------------------------------------------
1 |
2 | import { Provider } from 'react-redux'
3 | import { createStore, compose, applyMiddleware } from 'redux'
4 | import { shallow, mount } from 'enzyme'
5 | import React from 'react'
6 | import thunk from 'redux-thunk'
7 |
8 | import HelloWorld from '../../src/containers/HelloWorld.js'
9 |
10 | import reducer from '../../src/reducers'
11 |
12 | jest.mock('../../src/components/Hello.css', () => {
13 | return jest.fn()
14 | })
15 | jest.mock('../../src/styles/global.css', () => {
16 | return jest.fn()
17 | })
18 |
19 |
20 | let store = createStore(
21 | reducer,
22 | compose(applyMiddleware(thunk))
23 | )
24 |
25 | const enzymeWrapper = mount(
26 |
27 |
28 | )
29 |
30 | describe('containers', () => {
31 | it('should render self and subcomponents', (done) => {
32 | var _message1 = 'Hello'
33 | var _message2 = 'Hello, World!'
34 |
35 | expect(enzymeWrapper.find('h1').text()).toBe(_message1)
36 | enzymeWrapper.find('button').simulate('click');
37 | expect(enzymeWrapper.find('h1').text()).toBe(_message2)
38 | done()
39 | })
40 | })
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 |
2 | require('babel-register');
3 |
4 | let app = new (require('express'))()
5 | const port = 3000
6 |
7 | require('css-modules-require-hook')({
8 | generateScopedName: '[name]__[local]___[hash:base64:5]'
9 | })
10 |
11 |
12 | // initalize webpack dev middleware if in development context
13 | if (process.env.NODE_ENV === 'development') {
14 | let webpack = require('webpack')
15 | let config = require('../webpack.config')
16 |
17 | let devMiddleware = require('webpack-dev-middleware')
18 | let hotDevMiddleware = require('webpack-hot-middleware')
19 | let compiler = webpack(config)
20 | let devMiddlewareConfig = {
21 | noInfo: true,
22 | stats: {colors: true},
23 | publicPath: config.output.publicPath
24 | }
25 |
26 | app.use(devMiddleware(compiler, devMiddlewareConfig))
27 | app.use(hotDevMiddleware(compiler))
28 | }
29 |
30 | app.use(require('express').static('public'))
31 |
32 | let serverRender = require('./serverRender')
33 |
34 | app.get('*', serverRender)
35 |
36 | app.listen(port, function(error) {
37 | if (error) {
38 | console.error(error)
39 | } else {
40 | console.info('==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.', port, port)
41 | }
42 | })
--------------------------------------------------------------------------------
/src/clientRender.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 |
3 | import React from 'react'
4 | import { render } from 'react-dom'
5 | import { Provider } from 'react-redux'
6 | import {
7 | createStore,
8 | compose,
9 | applyMiddleware } from 'redux'
10 | import thunk from 'redux-thunk'
11 | import _reducers from './reducers'
12 | import matchConfig from './matchConfig'
13 | import {
14 | BrowserRouter,
15 | Route,
16 | Switch
17 | } from 'react-router-dom'
18 |
19 | const composeEnhancers = process.env.NODE_ENV !== 'production' &&
20 | typeof window !==
21 | 'undefined' &&
22 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
23 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ :
24 | compose
25 |
26 | const initState = window.__PRELOADED_STATE__
27 | delete window.__PRELOADED_STATE__
28 |
29 | const store = createStore(
30 | _reducers,
31 | initState,
32 | composeEnhancers(applyMiddleware(thunk)),
33 | )
34 |
35 | render(
36 |
37 |
38 |
39 | {
40 | matchConfig.map((route, index) => )
41 | }
42 |
43 |
44 | ,
45 | document.getElementById('root')
46 | )
--------------------------------------------------------------------------------
/__e2e__/config.js:
--------------------------------------------------------------------------------
1 | // conf.js
2 |
3 | var _sauceUser= process.env.SAUCE_USERNAME,
4 | _sauceKey= process.env.SAUCE_KEY;
5 |
6 | function genSeleniumAddress(){
7 | return (!process.env.IS_REMOTE)? 'http://127.0.0.1:4444/wd/hub' : "http://" + _sauceUser + ":" + _sauceKey +
8 | "@ondemand.saucelabs.com:80/wd/hub"
9 | }
10 |
11 | exports.config = {
12 | sauceUser: _sauceUser,
13 | sauceKey: _sauceKey,
14 |
15 | seleniumAddress: genSeleniumAddress(),
16 |
17 | specs: ['specs/*spec.js'],
18 |
19 | // restartBrowserBetweenTests: true,
20 |
21 | onPrepare: function(){
22 | var caps = browser.getCapabilities()
23 | },
24 |
25 | multiCapabilities: [{
26 | browserName: 'chrome',
27 | version: '41',
28 | platform: 'Windows 7',
29 | name: "chrome-tests",
30 | shardTestFiles: true,
31 | maxInstances: 25
32 | }],
33 |
34 | jasmineNodeOpts: {
35 | defaultTimeoutInterval: 10000
36 | },
37 | onComplete: function() {
38 |
39 | var printSessionId = function(jobName){
40 | browser.getSession().then(function(session) {
41 | console.log('SauceOnDemandSessionID=' + session.getId() + ' job-name=' + jobName);
42 | });
43 | }
44 | printSessionId("Insert Job Name Here");
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve, join } = require('path')
2 | const webpack = require('webpack')
3 |
4 | const config = {
5 | devtool: 'cheap-eval-source-map',
6 | entry: {
7 | home: [
8 | 'webpack-hot-middleware/client',
9 | './src/clientRender.js'
10 | ]
11 | },
12 | output: {
13 | path: resolve(__dirname,'public/'),
14 | filename: 'bundle.js',
15 | publicPath: '/static/'
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.js$/,
21 | use: [
22 | 'babel-loader'
23 | ],
24 | exclude: '/node_modules/'
25 | },
26 | {
27 | test: /node_modules\/.+\.css$/,
28 | use: [
29 | 'style-loader',
30 | {
31 | loader: 'css-loader',
32 | options: {
33 | modules: false,
34 | importLoaders: 1
35 | }
36 | }
37 | ]
38 | },
39 | {
40 | test: /\.css$/,
41 | use: [
42 | 'style-loader',
43 | {
44 | loader: 'css-loader',
45 | options: {
46 | modules: true,
47 | importLoaders: 1,
48 | localIdentName: '[name]__[local]___[hash:base64:5]'
49 | }
50 | },
51 | {
52 | loader: 'postcss-loader'
53 | }
54 | ]
55 | }
56 | ]
57 | },
58 | plugins: [
59 | new webpack.HotModuleReplacementPlugin(),
60 | new webpack.NoEmitOnErrorsPlugin(),
61 | new webpack.NamedModulesPlugin()
62 | ]
63 | }
64 | module.exports = config
65 |
--------------------------------------------------------------------------------
/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 |
2 | const { resolve, join } = require('path')
3 | const webpack = require('webpack')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 |
6 | const config = {
7 | entry: {
8 | bundle: [
9 | './src/clientRender.js'
10 | ],
11 | 'vendor/js': [
12 | 'react',
13 | 'react-dom',
14 | 'redux',
15 | 'react-redux'
16 | ]
17 | },
18 | output: {
19 | path: resolve(__dirname, 'public/static'),
20 | filename: '[name].js',
21 | publicPath: '/static/'
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test: /\.js[x]?$/,
27 | enforce: 'pre',
28 | use: [{
29 | loader: 'eslint-loader',
30 | options: { fix: true }
31 | }],
32 | exclude: '/node_modules/'
33 | },
34 | {
35 | test: /\.js$/,
36 | use: [
37 | 'babel-loader'
38 | ],
39 | exclude: '/node_modules/'
40 | },
41 | {
42 | test: /\.css$/,
43 | use: ExtractTextPlugin.extract({
44 | use: [
45 | {
46 | loader: 'css-loader',
47 | options: {
48 | modules: true,
49 | importLoaders: 1,
50 | localIdentName: '[name]_[local]___[hash:base64:5]'
51 | }
52 | },
53 | {
54 | loader: 'postcss-loader'
55 | }
56 | ]
57 | })
58 | }
59 | ]
60 | },
61 | plugins: [
62 | new webpack.optimize.CommonsChunkPlugin({
63 | name: 'vendor',
64 | minChunks: function (module) {
65 | // this assumes your vendor imports exist in the node_modules directory
66 | return module.context && module.context.indexOf('node_modules') !== -1
67 | }
68 | }),
69 | new ExtractTextPlugin({
70 | filename: '[name].css',
71 | disable: false,
72 | allChunks: true
73 | })
74 | ]
75 | }
76 |
77 | module.exports = config
78 |
--------------------------------------------------------------------------------
/src/serverRender.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 |
3 | import React from 'react'
4 | import ReactDOMServer from 'react-dom/server'
5 | import { createStore, compose, applyMiddleware } from 'redux'
6 | import thunk from 'redux-thunk'
7 | import _reducers from './reducers'
8 | import { Provider } from 'react-redux';
9 | import matchConfig from './matchConfig'
10 | import {
11 | StaticRouter,
12 | Route,
13 | Switch,
14 | matchPath
15 | } from 'react-router-dom'
16 |
17 |
18 | function serverRender(req, res) {
19 | const composeEnhancers = process.env.NODE_ENV !== 'production' &&
20 | typeof window !== 'undefined' &&
21 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
22 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ :
23 | compose
24 |
25 | const store = createStore(
26 | _reducers,
27 | composeEnhancers(applyMiddleware(thunk))
28 | )
29 |
30 | let initState;
31 | matchConfig.some(route => {
32 | const match = matchPath(req.url, route)
33 | if (match) {
34 | initState = route.initState
35 | }
36 | return match
37 | })
38 |
39 | store.dispatch(initState(store, req, res))
40 | .then(() => {
41 | renderStoreRouter(store, req, res)
42 | })
43 | }
44 |
45 | function renderStoreRouter(store, req, res) {
46 | const context = {}
47 | const componentStr = ReactDOMServer.renderToString(
48 |
49 |
50 |
51 | {
52 | matchConfig.map((route, index) => )
53 | }
54 |
55 |
56 |
57 | )
58 | res.send(renderFullPage(componentStr, store.getState()))
59 | }
60 |
61 |
62 | function renderFullPage(html, preloadedState) {
63 | let vendorJS = ''
64 | let bundleCSS = ''
65 | if (process.env.NODE_ENV === 'development') {
66 | // do something
67 | } else {
68 | bundleCSS = '/static/bundle.css'
69 | vendorJS = '/static/vendor.js'
70 | }
71 | return `
72 |
73 |
74 |
75 | Redux Hello World
76 |
77 |
78 |
79 |
80 |
85 |
86 |
87 |
88 |
89 | `
90 | }
91 |
92 | module.exports = serverRender
93 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-boilerplate",
3 | "version": "1.0.0",
4 | "description": "Hello, World! app in React and Redux",
5 | "main": "./src/server.js",
6 | "dependencies": {
7 | "babel-cli": "^6.24.1",
8 | "babel-polyfill": "^6.23.0",
9 | "babel-runtime": "^6.23.0",
10 | "classnames": "^2.2.5",
11 | "express": "^4.15.3",
12 | "isomorphic-fetch": "^2.2.1",
13 | "prop-types": "^15.5.10",
14 | "react": "^15.6.1",
15 | "react-dom": "^15.6.1",
16 | "react-redux": "^5.0.5",
17 | "react-router-dom": "^4.1.2",
18 | "redux": "^3.7.2",
19 | "redux-logger": "^3.0.6",
20 | "redux-mock-store": "^1.2.3",
21 | "redux-thunk": "^2.2.0"
22 | },
23 | "devDependencies": {
24 | "@storybook/react": "^3.1.8",
25 | "babel-core": "^6.7.6",
26 | "babel-eslint": "^7.2.3",
27 | "babel-jest": "*",
28 | "babel-loader": "^6.2.4",
29 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
30 | "babel-plugin-transform-runtime": "^6.7.5",
31 | "babel-preset-es2015": "^6.6.0",
32 | "babel-preset-react": "^6.5.0",
33 | "babel-preset-react-hmre": "^1.1.1",
34 | "babel-preset-stage-0": "^6.5.0",
35 | "babel-register": "^6.7.2",
36 | "css-loader": "^0.28.4",
37 | "css-modules-require-hook": "^4.0.6",
38 | "enzyme": "^2.8.0",
39 | "eslint": "^4.2.0",
40 | "eslint-loader": "^1.9.0",
41 | "eslint-plugin-import": "^2.7.0",
42 | "eslint-plugin-react": "^7.1.0",
43 | "extract-text-webpack-plugin": "^2.1.2",
44 | "install": "^0.10.1",
45 | "jest": "^19.0.2",
46 | "jest-fetch-mock": "^1.1.1",
47 | "postcss-loader": "^2.0.5",
48 | "react-addons-test-utils": "^15.4.2",
49 | "regenerator-runtime": "*",
50 | "style-loader": "^0.18.2",
51 | "webpack": "^3.0.0",
52 | "webpack-dev-middleware": "^1.10.2",
53 | "webpack-hot-middleware": "^2.18.0"
54 | },
55 | "scripts": {
56 | "build": "NODE_ENV=production babel src --out-dir dist --copy-files && webpack -p --progress --config webpack.prod.config.js",
57 | "start": "NODE_ENV=production node ./dist/server.js",
58 | "dev": "NODE_ENV=development babel-node ./src/server.js --presets es2015,stage-2",
59 | "test": "./node_modules/jest/bin/jest.js --coverage",
60 | "test:watch": "./node_modules/jest/bin/jest.js --watchAll #runs all tests --cache ",
61 | "storybook": "start-storybook -p 6006",
62 | "build-storybook": "build-storybook",
63 | "lint:fix": "eslint --fix .js src",
64 | "lint": "eslint --ext .js src"
65 | },
66 | "eslintConfig": {
67 | "env": {
68 | "browser": true,
69 | "node": true
70 | }
71 | },
72 | "repository": {
73 | "type": "git",
74 | "url": "git+https://github.com/wahengchang/react-redux-boilerplate.git"
75 | },
76 | "keywords": [
77 | "react",
78 | "redux",
79 | "boilerplate",
80 | "tutorial"
81 | ],
82 | "author": "waheng",
83 | "license": "ISC",
84 | "bugs": {
85 | "url": "https://github.com/wahengchang/react-redux-boilerplate/issues"
86 | },
87 | "homepage": "https://github.com/wahengchang/react-redux-boilerplate#readme",
88 | "jest": {
89 | "testPathIgnorePatterns": [
90 | "./__tests__/notRun.js"
91 | ],
92 | "automock": false,
93 | "setupFiles": [
94 | "./setupJest.js"
95 | ]
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-redux-boilerplate
2 | - It is React-Redux as infrastructure boilerplate, with this developers just focus on developing component, action creator and reducer, without spending time in router, dev/pro build enviroement, wiring up file and express as HTTP server.
3 |
4 | ## Read more
5 | - [Example: Adding new page](https://github.com/wahengchang/react-redux-boilerplate/wiki/Add-new-page)
6 | - [Example: Thread upvote/downvote project](https://github.com/wahengchang/react-redux-boilerplate-example)
7 | - [Shared components Storybook](https://github.com/wahengchang/react-redux-boilerplate/tree/master/src/components) `$ npm run storybook`
8 | - EditableDiv
9 | - Loading icon
10 |
11 | ## Install
12 |
13 | ```
14 | $ git clone https://github.com/wahengchang/react-redux-boilerplate
15 | $ npm install
16 | ```
17 |
18 | ## Run (Develop Mode)
19 | It is runnign in Development mode, enable HMR
20 | - http://localhost:3000/
21 | - http://localhost:3000/api , example of fetching data by action, and dispatch to update UI component
22 | - http://localhost:3000/preload , example of fetch data from [here](https://jsonplaceholder.typicode.com/posts
23 | ), before server rendering components
24 |
25 | ```
26 | $ npm run dev
27 | open http://localhost:3000/
28 |
29 | ```
30 |
31 |
32 | ## Run (Production Mode)
33 | - Compiling react/redux script to browser use lib, with webpack default optimized config.
34 | - Disable unnecessary funtionality which helps to debug in development mode
35 | ```
36 | $ npm run build
37 | $ node dist/server.js
38 |
39 | ```
40 |
41 | ## lint
42 |
43 | #### lint fix
44 | ```
45 | $ npm run lint:fix
46 | ```
47 |
48 | #### lint check
49 | ```
50 | $ npm run lint
51 | ```
52 |
53 |
54 | ## Storybook
55 | ```
56 | $ npm run storybook
57 | ```
58 |
59 |
60 | ## Test
61 | [More detail](https://github.com/wahengchang/react-redux-boilerplate/wiki/Test ): about test of action creater, component, container and reducer
62 |
63 | ```
64 | $ npm run test
65 |
66 |
67 | Test Suites: 4 passed, 4 total
68 | Tests: 6 passed, 6 total
69 | Snapshots: 0 total
70 | Time: 1.824s, estimated 2s
71 |
72 | ```
73 | or watch mode
74 | ```
75 | $ npm run test:watch
76 | ```
77 |
78 |
79 | # Server Rendering Structure
80 | 
81 | Above is the structure of how the whole app works, the app bases on Express web framework, which serves only one route, with res.sendFile function to put index.html into the browser. Inside the scoop of the structure, what we are interested is the blue box, the interaction between react component, redux, root component, store and reducer.
82 |
83 | # Shared Components
84 | 
85 |
86 |
87 | # React-Redux Structure
88 | 
89 | _**index.js**_ , as the entry file and a high level root component, which gathers all the sub-component as the subtree of the Virtual DOM, also it is the only file entangled with many independent modules. Apart from it, different file requires independent modules, which makes clean code and work independently.
90 |
91 |
92 | ## Credit
93 | - [https://github.com/vaibhavmule/react-redux-helloworld](https://github.com/vaibhavmule/react-redux-helloworld)
94 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true,
6 | "commonjs": true
7 | },
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:react/recommended"
11 | ],
12 | "parserOptions": {
13 | "ecmaVersion": 2017,
14 | "sourceType": "module",
15 | "ecmaFeatures": {
16 | "experimentalObjectRestSpread": true,
17 | "jsx": true,
18 | "arrowFunctions": true,
19 | "classes": true,
20 | "modules": true,
21 | "defaultParams": true
22 | }
23 | },
24 | "parser": "babel-eslint",
25 | "plugins": [
26 | "react"
27 | ],
28 | "rules": {
29 | "no-console": "off",
30 | "for-direction": "error",
31 | "getter-return": [
32 | "error",
33 | {
34 | allowImplicit: false
35 | }
36 | ],
37 | "no-await-in-loop": "error",
38 | "no-compare-neg-zero": "error",
39 | "no-cond-assign": [
40 | "error",
41 | "except-parens"
42 | ],
43 | "no-constant-condition": [
44 | "error",
45 | {
46 | checkLoops: false
47 | }
48 | ],
49 | "no-control-regex": "error",
50 | "no-debugger": "error",
51 | "no-dupe-args": "error",
52 | "no-dupe-keys": "error",
53 | "no-duplicate-case": "error",
54 | "no-empty": [
55 | "error",
56 | {
57 | allowEmptyCatch: false
58 | }
59 | ],
60 | "no-empty-character-class": "error",
61 | "no-ex-assign": "error",
62 | "no-extra-boolean-cast": "error",
63 | "no-extra-parens": [
64 | "error",
65 | "all",
66 | {
67 | conditionalAssign: false,
68 | returnAssign: false,
69 | nestedBinaryExpressions: false,
70 | ignoreJSX: "multi-line",
71 | enforceForArrowConditionals: false
72 | }
73 | ],
74 | "no-extra-semi": "error",
75 | "no-func-assign": "error",
76 | "no-inner-declarations": [
77 | "error",
78 | "both"
79 | ],
80 | "no-invalid-regexp": "error",
81 | "no-irregular-whitespace": [
82 | "error",
83 | {
84 | skipStrings: true,
85 | skipComments: false,
86 | skipRegExps: true,
87 | skipTemplates: true
88 | }
89 | ],
90 | "no-obj-calls": "error",
91 | "no-regex-spaces": "error",
92 | "no-sparse-arrays": "error",
93 | "no-template-curly-in-string": "error",
94 | "no-unexpected-multiline": "error",
95 | "no-unreachable": "error",
96 | "no-unsafe-finally": "error",
97 | "no-unsafe-negation": "error",
98 | "use-isnan": "error",
99 | "valid-typeof": "error",
100 | "accessor-pairs": [
101 | "error",
102 | {
103 | setWithoutGet: true,
104 | getWithoutSet: false
105 | }
106 | ],
107 | "array-callback-return": "error",
108 | "block-scoped-var": "error",
109 | "complexity": [
110 | "error",
111 | {
112 | max: 5
113 | }
114 | ],
115 | "curly": [
116 | "error",
117 | "all"
118 | ],
119 | "dot-location": [
120 | "error",
121 | "property"
122 | ],
123 | "eqeqeq": [
124 | "error",
125 | "always"
126 | ],
127 | "guard-for-in": "error",
128 | "no-caller": "error",
129 | "no-case-declarations": "error",
130 | "no-empty-function": [
131 | "error",
132 | {
133 | allow: [
134 | "functions",
135 | "arrowFunctions"
136 | ]
137 | }
138 | ],
139 | "no-empty-pattern": "error",
140 | "no-eq-null": "error",
141 | "no-eval": "error",
142 | "no-extend-native": "error",
143 | "no-extra-bind": "error",
144 | "no-extra-label": "error",
145 | "no-fallthrough": "error",
146 | "no-floating-decimal": "error",
147 | "no-global-assign": "error",
148 | "no-implicit-coercion": "error",
149 | "no-implicit-globals": "error",
150 | "no-implied-eval": "error",
151 | "no-invalid-this": "error",
152 | "no-iterator": "error",
153 | "no-labels": "error",
154 | "no-lone-blocks": "error",
155 | "no-loop-func": "error",
156 | "no-magic-numbers": [
157 | "error",
158 | {
159 | ignore: [
160 | -1,
161 | 0,
162 | 1,
163 | 2,
164 | 3,
165 | 100
166 | ],
167 | ignoreArrayIndexes: true,
168 | enforceConst: true,
169 | detectObjects: false
170 | }
171 | ],
172 | "no-multi-spaces": [
173 | "error",
174 | {
175 | ignoreEOLComments: true,
176 | exceptions: {
177 | Property: true,
178 | BinaryExpression: false,
179 | VariableDeclarator: true,
180 | ImportDeclaration: true
181 | }
182 | }
183 | ],
184 | "no-multi-str": "error",
185 | "no-new": "error",
186 | "no-new-func": "error",
187 | "no-new-wrappers": "error",
188 | "no-octal": "error",
189 | "no-octal-escape": "error",
190 | "no-param-reassign": "error",
191 | "no-proto": "error",
192 | "no-redeclare": "error",
193 | "no-restricted-properties": "error",
194 | "no-return-assign": [
195 | "error",
196 | "always"
197 | ],
198 | "no-return-await": "error",
199 | "no-self-assign": "error",
200 | "no-self-compare": "error",
201 | "no-sequences": "error",
202 | "no-throw-literal": "error",
203 | "no-unmodified-loop-condition": "error",
204 | "no-unused-expressions": [
205 | "error",
206 | {
207 | allowShortCircuit: true,
208 | allowTernary: true,
209 | allowTaggedTemplates: true
210 | }
211 | ],
212 | "no-unused-labels": "error",
213 | "no-useless-call": "error",
214 | "no-useless-concat": "error",
215 | "no-useless-escape": "error",
216 | "no-void": "error",
217 | "no-with": "error",
218 | "prefer-promise-reject-errors": "error",
219 | "radix": "error",
220 | "require-await": "error",
221 | "wrap-iife": [
222 | "error",
223 | "outside",
224 | {
225 | functionPrototypeMethods: true
226 | }
227 | ],
228 | "yoda": [
229 | "error",
230 | "never",
231 | {
232 | exceptRange: false,
233 | onlyEquality: false
234 | }
235 | ],
236 | "strict": [
237 | "error",
238 | "never"
239 | ],
240 | "no-delete-var": "error",
241 | "no-label-var": "error",
242 | "no-shadow": [
243 | "error",
244 | {
245 | builtinGlobals: false,
246 | hoist: "functions"
247 | }
248 | ],
249 | "no-shadow-restricted-names": "error",
250 | "no-undef": [
251 | "error",
252 | {
253 | typeof: false
254 | }
255 | ],
256 | "no-undef-init": "error",
257 | "no-undefined": "error",
258 | "no-unused-vars": [
259 | "error",
260 | {
261 | varsIgnorePattern: "[S|s]tyle",
262 | vars: "all",
263 | args: "none",
264 | caughtErrors: "none"
265 | }
266 | ],
267 | "no-use-before-define": [
268 | "error",
269 | {
270 | functions: false,
271 | classes: true,
272 | variables: true
273 | }
274 | ],
275 | // "global-require": "error",
276 | "handle-callback-err": "error",
277 | "no-buffer-constructor": "error",
278 | "no-new-require": "error",
279 | "no-path-concat": "error",
280 | "array-bracket-newline": [
281 | "error",
282 | {
283 | multiline: true,
284 | minItems: null
285 | }
286 | ],
287 | "array-bracket-spacing": [
288 | "error",
289 | "never"
290 | ],
291 | "block-spacing": [
292 | "error",
293 | "always"
294 | ],
295 | "brace-style": [
296 | "error",
297 | "1tbs",
298 | {
299 | allowSingleLine: false
300 | }
301 | ],
302 | "comma-spacing": [
303 | "error",
304 | {
305 | "before": false,
306 | "after": true
307 | }
308 | ],
309 | "comma-style": [
310 | "error",
311 | "last"
312 | ],
313 | "computed-property-spacing": [
314 | "error",
315 | "never"
316 | ],
317 | "func-call-spacing": [
318 | "error",
319 | "never"
320 | ],
321 | "func-name-matching": [
322 | "error",
323 | "always",
324 | {
325 | includeCommonJSModuleExports: false
326 | }
327 | ],
328 | "indent": [
329 | "error",
330 | 2,
331 | {
332 | SwitchCase: 1,
333 | VariableDeclarator: 1,
334 | outerIIFEBody: 1,
335 | MemberExpression: 1,
336 | FunctionDeclaration: {
337 | body: 1,
338 | parameters: 1
339 | },
340 | CallExpression: {
341 | arguments: 1
342 | },
343 | ArrayExpression: 1,
344 | ObjectExpression: 1,
345 | flatTernaryExpressions: true
346 | }
347 | ],
348 | "jsx-quotes": [
349 | "error",
350 | "prefer-double"
351 | ],
352 | "key-spacing": [
353 | "error",
354 | {
355 | beforeColon: false,
356 | afterColon: true,
357 | mode: "strict",
358 | }
359 | ],
360 | "keyword-spacing": [
361 | "error",
362 | {
363 | before: true,
364 | after: true
365 | }
366 | ],
367 | "max-depth": [
368 | "error",
369 | 5
370 | ],
371 | "max-lines": [
372 | "error",
373 | {
374 | max: 300,
375 | skipBlankLines: true,
376 | skipComments: true
377 | }
378 | ],
379 | "max-nested-callbacks": [
380 | "error",
381 | 3
382 | ],
383 | "max-params": [
384 | "error",
385 | 7
386 | ],
387 | "new-cap": [
388 | "error",
389 | {
390 | newIsCap: true,
391 | capIsNew: false,
392 | properties: true
393 | }
394 | ],
395 | "new-parens": "error",
396 | "no-array-constructor": "error",
397 | "no-mixed-spaces-and-tabs": "error",
398 | "no-multiple-empty-lines": [
399 | "error",
400 | {
401 | max: 3,
402 | maxEOF: 1,
403 | maxBOF: 1
404 | }
405 | ],
406 | "no-new-object": "error",
407 | "no-tabs": "error",
408 | "no-trailing-spaces": "error",
409 | "no-unneeded-ternary": "error",
410 | "no-whitespace-before-property": "error",
411 | "nonblock-statement-body-position": [
412 | "error",
413 | "beside",
414 | {
415 | overrides: {
416 | while: "below"
417 | }
418 | }
419 | ],
420 | "object-curly-newline": [
421 | "error",
422 | {
423 | multiline: true,
424 | consistent: true
425 | }
426 | ],
427 | // "object-curly-spacing": [
428 | // "error",
429 | // "never",
430 | // {
431 | // arraysInObjects: false,
432 | // objectsInObjects: false
433 | // }
434 | // ],
435 | "one-var": [
436 | "error",
437 | "never"
438 | ],
439 | "one-var-declaration-per-line": [
440 | "error",
441 | "always"
442 | ],
443 | "operator-linebreak": [
444 | "error",
445 | "after"
446 | ],
447 | "padded-blocks": [
448 | "error",
449 | "never"
450 | ],
451 | "quotes": [
452 | "error",
453 | "single",
454 | {
455 | avoidEscape: true,
456 | allowTemplateLiterals: true
457 | }
458 | ],
459 | // "semi": [
460 | // "error",
461 | // "always",
462 | // {
463 | // omitLastInOneLineBlock: true
464 | // }
465 | // ],
466 | "semi-spacing": [
467 | "error",
468 | {
469 | before: false,
470 | after: true
471 | }
472 | ],
473 | "semi-style": [
474 | "error",
475 | "last"
476 | ],
477 | "space-before-blocks": [
478 | "error",
479 | "always"
480 | ],
481 | // "space-before-function-paren": [
482 | // "error",
483 | // {
484 | // anonymous: "never",
485 | // named: "never",
486 | // asyncArrow: "always"
487 | // }
488 | // ],
489 | "space-in-parens": [
490 | "error",
491 | "never"
492 | ],
493 | "space-infix-ops": "error",
494 | "space-unary-ops": [
495 | "error",
496 | {
497 | words: true,
498 | nonwords: false
499 | }
500 | ],
501 | "spaced-comment": [
502 | "error",
503 | "always",
504 | {
505 | exceptions: [
506 | "*"
507 | ]
508 | }
509 | ],
510 | "switch-colon-spacing": [
511 | "error",
512 | {
513 | after: true,
514 | before: false
515 | }
516 | ],
517 | "template-tag-spacing": [
518 | "error",
519 | "never"
520 | ],
521 | "unicode-bom": [
522 | "error",
523 | "never"
524 | ],
525 | "arrow-spacing": [
526 | "error",
527 | {
528 | before: true,
529 | after: true
530 | }
531 | ],
532 | "constructor-super": "error",
533 | "generator-star-spacing": [
534 | "error",
535 | {
536 | before: false,
537 | after: true
538 | }
539 | ],
540 | "no-class-assign": "error",
541 | "no-confusing-arrow": "error",
542 | "no-const-assign": "error",
543 | "no-dupe-class-members": "error",
544 | "no-duplicate-imports": "error",
545 | "no-new-symbol": "error",
546 | "no-this-before-super": "error",
547 | "no-useless-computed-key": "error",
548 | "no-useless-constructor": "error",
549 | "no-useless-rename": "error",
550 | "no-var": "error",
551 | // "object-shorthand": [
552 | // "error",
553 | // "always",
554 | // {
555 | // avoidQuotes: true,
556 | // ignoreConstructors: false,
557 | // avoidExplicitReturnArrows: true
558 | // }
559 | // ],
560 | "prefer-numeric-literals": "error",
561 | "prefer-rest-params": "error",
562 | "prefer-spread": "error",
563 | "require-yield": "error",
564 | "rest-spread-spacing": [
565 | "error",
566 | "never"
567 | ],
568 | "symbol-description": "error",
569 | "template-curly-spacing": [
570 | "error",
571 | "never"
572 | ],
573 | "yield-star-spacing": [
574 | "error",
575 | "after"
576 | ]
577 | }
578 | }
--------------------------------------------------------------------------------