From bc3a6c2439160d2acd031922c6de2e66439e19d1 Mon Sep 17 00:00:00 2001 From: Lakshya Ranganath Date: Sat, 24 Feb 2018 16:38:48 +0530 Subject: [PATCH 1/5] move to react@16.2.0, react-router@4.2.2, modules structure --- .babelrc | 3 +- client/components/LoaderHOC/LoaderHOC.js | 40 --------- client/components/LoaderHOC/loaderHOC.css | 63 --------------- client/{components => core}/Flex/Col.js | 0 client/{components => core}/Flex/Grid.js | 0 client/{components => core}/Flex/Row.js | 0 client/{components => core}/Flex/flex.css | 0 .../Flex/Flex.js => core/Flex/index.js} | 0 client/{components => core}/NotFound/404.png | Bin .../{components => core}/NotFound/NotFound.js | 0 client/core/NotFound/index.js | 1 + client/{views => core}/Wrapper/Wrapper.js | 9 ++- client/core/Wrapper/index.js | 1 + client/{views => core}/Wrapper/wrapper.css | 0 client/home/HomePage/HomePage.js | 48 +++++++++++ .../HomePage/homePage.css} | 2 +- client/home/HomePage/index.js | 10 +++ client/index.js | 19 ++--- client/routes.js | 40 ++++----- client/services/user/userModel.js | 19 ----- client/services/user/userReducer.js | 37 --------- .../{configureStore.js => createStore.js} | 17 ++-- client/store/rootReducer.js | 6 +- .../LandingPage => user}/UserList/UserList.js | 21 +---- .../UserList/index.js} | 5 +- .../UserList/userList.css | 2 +- .../{services => }/user/userActionCreators.js | 6 +- .../userTypes.js => user/userActionTypes.js} | 0 client/user/userHelpers.js | 17 ++++ client/user/userReducer.js | 29 +++++++ .../{services => }/user/userReducer.test.js | 40 ++++----- client/utils/importCss.js | 2 +- client/vendor/modules/modules.js | 4 +- client/views/LandingPage/LandingPage.js | 45 ----------- package.json | 76 +++++++++--------- server/index.js | 7 +- .../execComponentWillServerRender.js | 24 ++++++ server/middlewares/renderMiddleware/index.js | 1 + .../renderMiddleware/renderMiddleware.js | 58 ++++++------- webpack.client.js | 1 + webpack.server.js | 1 + 41 files changed, 278 insertions(+), 376 deletions(-) delete mode 100644 client/components/LoaderHOC/LoaderHOC.js delete mode 100644 client/components/LoaderHOC/loaderHOC.css rename client/{components => core}/Flex/Col.js (100%) rename client/{components => core}/Flex/Grid.js (100%) rename client/{components => core}/Flex/Row.js (100%) rename client/{components => core}/Flex/flex.css (100%) rename client/{components/Flex/Flex.js => core/Flex/index.js} (100%) rename client/{components => core}/NotFound/404.png (100%) rename client/{components => core}/NotFound/NotFound.js (100%) create mode 100644 client/core/NotFound/index.js rename client/{views => core}/Wrapper/Wrapper.js (67%) create mode 100644 client/core/Wrapper/index.js rename client/{views => core}/Wrapper/wrapper.css (100%) create mode 100644 client/home/HomePage/HomePage.js rename client/{views/LandingPage/landingPage.css => home/HomePage/homePage.css} (84%) create mode 100644 client/home/HomePage/index.js delete mode 100644 client/services/user/userModel.js delete mode 100644 client/services/user/userReducer.js rename client/store/{configureStore.js => createStore.js} (60%) rename client/{views/LandingPage => user}/UserList/UserList.js (74%) rename client/{views/LandingPage/UserList/UserListAsync.js => user/UserList/index.js} (53%) rename client/{views/LandingPage => user}/UserList/userList.css (91%) rename client/{services => }/user/userActionCreators.js (73%) rename client/{services/user/userTypes.js => user/userActionTypes.js} (100%) create mode 100644 client/user/userHelpers.js create mode 100644 client/user/userReducer.js rename client/{services => }/user/userReducer.test.js (62%) delete mode 100644 client/views/LandingPage/LandingPage.js create mode 100644 server/middlewares/renderMiddleware/execComponentWillServerRender.js create mode 100644 server/middlewares/renderMiddleware/index.js diff --git a/.babelrc b/.babelrc index d6b15d8..2fc4791 100644 --- a/.babelrc +++ b/.babelrc @@ -4,8 +4,9 @@ "targets": { "uglify": true }, + "loose": true, "modules": false, - "useBuiltIns": true + "useBuiltIns": "entry" }], "react", "stage-1" diff --git a/client/components/LoaderHOC/LoaderHOC.js b/client/components/LoaderHOC/LoaderHOC.js deleted file mode 100644 index 4981478..0000000 --- a/client/components/LoaderHOC/LoaderHOC.js +++ /dev/null @@ -1,40 +0,0 @@ -import React, { Component } from 'react'; -import isEmpty from 'lodash/isEmpty'; -import moment from 'moment'; -import './loaderHOC.css'; - -const LoaderHOC = (property) => - (WrappedComponent) => - class extends Component { - componentDidMount() { - this.startTime = moment(); - } - - componentWillUpdate() { - this.endTime = moment(); - } - - render() { - const additionalProps = { - loadTime: this.endTime ? `${this.endTime.diff(this.startTime, 'ms')}ms` : null, - }; - - return isEmpty(this.props[property]) ? ( -
-
-
-
-
-
-
-
-
-
-
- ) : ( - - ); - } - }; - -export default LoaderHOC; diff --git a/client/components/LoaderHOC/loaderHOC.css b/client/components/LoaderHOC/loaderHOC.css deleted file mode 100644 index 7e1bf42..0000000 --- a/client/components/LoaderHOC/loaderHOC.css +++ /dev/null @@ -1,63 +0,0 @@ -@import '../../vendor/styles/variables.css'; - -.sk-cube-grid { - width: 30px; - height: 30px; - margin: 100px auto; -} - -.sk-cube-grid .sk-cube { - width: 33%; - height: 33%; - background-color: var(--color-primary); - float: left; - animation: sk-cubeGridScaleDelay 1.5s infinite ease-in-out; -} - -.sk-cube-grid .sk-cube1 { - animation-delay: 0.2s; -} - -.sk-cube-grid .sk-cube2 { - animation-delay: 0.3s; -} - -.sk-cube-grid .sk-cube3 { - animation-delay: 0.4s; -} - -.sk-cube-grid .sk-cube4 { - animation-delay: 0.1s; -} - -.sk-cube-grid .sk-cube5 { - animation-delay: 0.2s; -} - -.sk-cube-grid .sk-cube6 { - animation-delay: 0.3s; -} - -.sk-cube-grid .sk-cube7 { - animation-delay: 0s; -} - -.sk-cube-grid .sk-cube8 { - animation-delay: 0.1s; -} - -.sk-cube-grid .sk-cube9 { - animation-delay: 0.2s; -} - -@keyframes sk-cubeGridScaleDelay { - 0%, - 70%, - 100% { - transform: scale3d(1, 1, 1); - } - - 35% { - transform: scale3d(0, 0, 1); - } -} diff --git a/client/components/Flex/Col.js b/client/core/Flex/Col.js similarity index 100% rename from client/components/Flex/Col.js rename to client/core/Flex/Col.js diff --git a/client/components/Flex/Grid.js b/client/core/Flex/Grid.js similarity index 100% rename from client/components/Flex/Grid.js rename to client/core/Flex/Grid.js diff --git a/client/components/Flex/Row.js b/client/core/Flex/Row.js similarity index 100% rename from client/components/Flex/Row.js rename to client/core/Flex/Row.js diff --git a/client/components/Flex/flex.css b/client/core/Flex/flex.css similarity index 100% rename from client/components/Flex/flex.css rename to client/core/Flex/flex.css diff --git a/client/components/Flex/Flex.js b/client/core/Flex/index.js similarity index 100% rename from client/components/Flex/Flex.js rename to client/core/Flex/index.js diff --git a/client/components/NotFound/404.png b/client/core/NotFound/404.png similarity index 100% rename from client/components/NotFound/404.png rename to client/core/NotFound/404.png diff --git a/client/components/NotFound/NotFound.js b/client/core/NotFound/NotFound.js similarity index 100% rename from client/components/NotFound/NotFound.js rename to client/core/NotFound/NotFound.js diff --git a/client/core/NotFound/index.js b/client/core/NotFound/index.js new file mode 100644 index 0000000..50068c5 --- /dev/null +++ b/client/core/NotFound/index.js @@ -0,0 +1 @@ +export default from './NotFound'; diff --git a/client/views/Wrapper/Wrapper.js b/client/core/Wrapper/Wrapper.js similarity index 67% rename from client/views/Wrapper/Wrapper.js rename to client/core/Wrapper/Wrapper.js index 6a114bc..26941d2 100644 --- a/client/views/Wrapper/Wrapper.js +++ b/client/core/Wrapper/Wrapper.js @@ -1,28 +1,29 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Helmet from 'react-helmet'; +import { renderRoutes } from 'react-router-config'; import performanceMark from '../../utils/performanceMark'; import './wrapper.css'; class Wrapper extends Component { componentDidMount() { - performanceMark('firstInteraction'); + performanceMark('first-interaction'); } render() { - const { children } = this.props; + const { route } = this.props; return (
- {children} + {renderRoutes(route.routes)}
); } } Wrapper.propTypes = { - children: PropTypes.element.isRequired, + route: PropTypes.object.isRequired, }; export default Wrapper; diff --git a/client/core/Wrapper/index.js b/client/core/Wrapper/index.js new file mode 100644 index 0000000..5b9663f --- /dev/null +++ b/client/core/Wrapper/index.js @@ -0,0 +1 @@ +export default from './Wrapper'; diff --git a/client/views/Wrapper/wrapper.css b/client/core/Wrapper/wrapper.css similarity index 100% rename from client/views/Wrapper/wrapper.css rename to client/core/Wrapper/wrapper.css diff --git a/client/home/HomePage/HomePage.js b/client/home/HomePage/HomePage.js new file mode 100644 index 0000000..7081e35 --- /dev/null +++ b/client/home/HomePage/HomePage.js @@ -0,0 +1,48 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import isEmpty from 'lodash/isEmpty'; +import { connect } from 'react-redux'; +import { bindActionCreators, compose } from 'redux'; +import * as userActionCreators from '../../user/userActionCreators'; +import UsersList from '../../user/UserList'; +import './homePage.css'; + +class HomePage extends React.Component { + static componentWillServerRender = ({ store }) => + isEmpty(store.getState().$user.ids) + ? store.dispatch(userActionCreators.getAll()) : null + + componentDidMount() { + const { $user, userActions } = this.props; + if (isEmpty($user.ids)) { + userActions.getAll(); + } + } + + render() { + return ( +
+

PWA

+

An opinionated progressive web app boilerplate

+ +
+ ); + } +} + +HomePage.propTypes = { + $user: PropTypes.object.isRequired, + userActions: PropTypes.object.isRequired, +}; + +const mapStateToProps = (state) => ({ + $user: state.$user, +}); + +const mapDispatchToProps = (dispatch) => ({ + userActions: bindActionCreators(userActionCreators, dispatch), +}); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(HomePage); diff --git a/client/views/LandingPage/landingPage.css b/client/home/HomePage/homePage.css similarity index 84% rename from client/views/LandingPage/landingPage.css rename to client/home/HomePage/homePage.css index ccefb72..e5d09a8 100644 --- a/client/views/LandingPage/landingPage.css +++ b/client/home/HomePage/homePage.css @@ -1,6 +1,6 @@ @import '../../vendor/styles/variables.css'; -.landing-page { +.home-page { margin-top: 12px; text-align: center; } diff --git a/client/home/HomePage/index.js b/client/home/HomePage/index.js new file mode 100644 index 0000000..bb30a69 --- /dev/null +++ b/client/home/HomePage/index.js @@ -0,0 +1,10 @@ +import Loadable from 'react-loadable'; +import importCss from '../../utils/importCss'; + +export default Loadable({ + loader: () => { + importCss('HomePage'); + return import('./HomePage' /* webpackChunkName: 'HomePage' */); + }, + loading: () => null, +}); diff --git a/client/index.js b/client/index.js index 2828feb..c98a462 100644 --- a/client/index.js +++ b/client/index.js @@ -1,21 +1,18 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; -import { Router, browserHistory } from 'react-router'; -import { ReduxAsyncConnect } from 'redux-connect'; -import configureStore from './store/configureStore'; +import { BrowserRouter } from 'react-router-dom'; +import { renderRoutes } from 'react-router-config'; +import createStore from './store/createStore'; import routes from './routes'; -const store = configureStore(window.__INITIAL_STATE__); +const store = createStore(window.__INITIAL_STATE__); ReactDOM.render( - - } - onUpdate={() => window.scrollTo(0, 0)} - /> + + + {renderRoutes(routes)} + , document.getElementById('root'), ); diff --git a/client/routes.js b/client/routes.js index b6a405b..d03f81b 100644 --- a/client/routes.js +++ b/client/routes.js @@ -1,26 +1,16 @@ -import React from 'react'; -import { Route, IndexRoute } from 'react-router'; -import NotFoundPage from './components/NotFound/NotFound'; -import Wrapper from './views/Wrapper/Wrapper'; -import importCss from './utils/importCss'; +import Wrapper from './core/Wrapper'; +import NotFound from './core/NotFound'; +import HomePage from './home/HomePage'; -export const loadRoute = { - LandingPage: () => Promise.all([ - import('./views/LandingPage/LandingPage' /* webpackChunkName: 'LandingPage' */), - importCss('LandingPage'), - ]), -}; - -export default ( - - - loadRoute.LandingPage() - .then(([module]) => cb(null, module.default))} - /> - - - - -); +export default [{ + component: Wrapper, + routes: [{ + path: '/', + exact: true, + name: 'HomePage', + component: HomePage, + }, { + name: 'NotFound', + component: NotFound, + }], +}]; diff --git a/client/services/user/userModel.js b/client/services/user/userModel.js deleted file mode 100644 index 6790857..0000000 --- a/client/services/user/userModel.js +++ /dev/null @@ -1,19 +0,0 @@ -const userProto = { - get introduce() { - return `Hey, my name is ${this.name}`; - }, -}; - -export const makeUser = (name) => { - const user = Object.create(userProto); - user.name = name; - return user; -}; - -export const normalize = (users) => ({ - byId: users.reduce((obj, user, index) => ({ ...obj, [index]: user }), {}), - ids: users.map((user, index) => index), -}); - -export const getAll = (ids, byId) => - ids.map((userId) => byId[userId]); diff --git a/client/services/user/userReducer.js b/client/services/user/userReducer.js deleted file mode 100644 index c60e99a..0000000 --- a/client/services/user/userReducer.js +++ /dev/null @@ -1,37 +0,0 @@ -import { handle } from 'redux-pack'; -import * as userTypes from './userTypes'; -import * as userModel from './userModel'; - -export const initialState = { - byId: {}, - ids: [], - isLoading: false, -}; - -export default (state = initialState, action) => { - const { type, payload } = action; - - switch (type) { - case userTypes.GET_ALL: return handle(state, action, { - start: (s) => ({ - ...s, - isLoading: true, - }), - success: (s) => ({ - ...s, - ...userModel.normalize(payload.results), - }), - failure: (s) => ({ - ...s, - error: payload.error, - }), - finish: (s) => ({ - ...s, - isLoading: false, - }), - }); - - default: - return state; - } -}; diff --git a/client/store/configureStore.js b/client/store/createStore.js similarity index 60% rename from client/store/configureStore.js rename to client/store/createStore.js index 80f4c16..c6d4f07 100644 --- a/client/store/configureStore.js +++ b/client/store/createStore.js @@ -7,15 +7,16 @@ import rootReducer from './rootReducer'; const middlewares = [ reduxThunk.withExtraArgument({ api }), reduxPack, -]; +].filter(Boolean); const storeEnhancers = [ applyMiddleware(...middlewares), - __BROWSER__ && __LOCAL__ && window.devToolsExtension ? window.devToolsExtension() : (f) => f, -]; + __BROWSER__ && __LOCAL__ && window.devToolsExtension && window.devToolsExtension(), +].filter(Boolean); -export default (initialState) => createStore( - rootReducer, - initialState, - compose(...storeEnhancers), -); +export default (initialState) => + createStore( + rootReducer, + initialState, + compose(...storeEnhancers), + ); diff --git a/client/store/rootReducer.js b/client/store/rootReducer.js index 708f7a1..ad25a23 100644 --- a/client/store/rootReducer.js +++ b/client/store/rootReducer.js @@ -1,8 +1,6 @@ import { combineReducers } from 'redux'; -import { reducer as reduxAsyncConnectReducer } from 'redux-connect'; -import userReducer from '../services/user/userReducer'; +import * as userReducer from '../user/userReducer'; export default combineReducers({ - reduxAsyncConnect: reduxAsyncConnectReducer, - user: userReducer, + $user: userReducer.reducer, }); diff --git a/client/views/LandingPage/UserList/UserList.js b/client/user/UserList/UserList.js similarity index 74% rename from client/views/LandingPage/UserList/UserList.js rename to client/user/UserList/UserList.js index 5e3efda..248d5a5 100644 --- a/client/views/LandingPage/UserList/UserList.js +++ b/client/user/UserList/UserList.js @@ -1,11 +1,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import cn from 'classnames'; import { compose } from 'redux'; import { connect } from 'react-redux'; -import cn from 'classnames'; -import Flex from '../../../components/Flex/Flex'; -import LoaderHOC from '../../../components/LoaderHOC/LoaderHOC'; -import * as userModel from '../../../services/user/userModel'; +import Flex from '../../core/Flex'; import './userList.css'; class UsersList extends Component { @@ -18,16 +16,11 @@ class UsersList extends Component { } render() { - const { users, loadTime } = this.props; + const { users } = this.props; const { active } = this.state; return users.length ? (
- { - loadTime ? ( - Took: {loadTime} - ) : null - } { users.map(({ name, picture }, i) => ( @@ -59,18 +52,12 @@ class UsersList extends Component { UsersList.propTypes = { users: PropTypes.array.isRequired, - loadTime: PropTypes.string, -}; - -UsersList.defaultProps = { - loadTime: '', }; const mapStateToProps = (state) => ({ - users: userModel.getAll(state.user.ids, state.user.byId), + users: Object.values(state.$user.byId), }); export default compose( connect(mapStateToProps), - LoaderHOC('users'), )(UsersList); diff --git a/client/views/LandingPage/UserList/UserListAsync.js b/client/user/UserList/index.js similarity index 53% rename from client/views/LandingPage/UserList/UserListAsync.js rename to client/user/UserList/index.js index 828c55e..b152365 100644 --- a/client/views/LandingPage/UserList/UserListAsync.js +++ b/client/user/UserList/index.js @@ -1,12 +1,11 @@ import React from 'react'; import Loadable from 'react-loadable'; -import importCss from '../../../utils/importCss'; +import importCss from '../../utils/importCss'; export default Loadable({ loader: () => { importCss('UserList'); return import('./UserList' /* webpackChunkName: 'UserList' */); }, - // provide better UX by using a skeleton screen here instead of just text - loading: () =>

Loading the UserList component...

, + loading: () =>

Loading users...

, }); diff --git a/client/views/LandingPage/UserList/userList.css b/client/user/UserList/userList.css similarity index 91% rename from client/views/LandingPage/UserList/userList.css rename to client/user/UserList/userList.css index 77cc0dd..ea04d13 100644 --- a/client/views/LandingPage/UserList/userList.css +++ b/client/user/UserList/userList.css @@ -1,4 +1,4 @@ -@import '../../../vendor/styles/variables.css'; +@import '../../vendor/styles/variables.css'; .user { margin-top: 50px; diff --git a/client/services/user/userActionCreators.js b/client/user/userActionCreators.js similarity index 73% rename from client/services/user/userActionCreators.js rename to client/user/userActionCreators.js index 435c26d..c08d00c 100644 --- a/client/services/user/userActionCreators.js +++ b/client/user/userActionCreators.js @@ -1,8 +1,8 @@ -import * as userTypes from './userTypes'; +import * as userActionTypes from './userActionTypes'; export const getAll = () => (dispatch, getState, { api }) => dispatch({ - type: userTypes.GET_ALL, + type: userActionTypes.GET_ALL, promise: api.get('/api', { results: 3, inc: 'name,location,picture', @@ -11,7 +11,7 @@ export const getAll = () => (dispatch, getState, { api }) => export const getOne = () => (dispatch, getState, { api }) => dispatch({ - type: userTypes.GET_ONE, + type: userActionTypes.GET_ONE, promise: api.get('/api', { results: 1, inc: 'name,location,picture', diff --git a/client/services/user/userTypes.js b/client/user/userActionTypes.js similarity index 100% rename from client/services/user/userTypes.js rename to client/user/userActionTypes.js diff --git a/client/user/userHelpers.js b/client/user/userHelpers.js new file mode 100644 index 0000000..76e37c9 --- /dev/null +++ b/client/user/userHelpers.js @@ -0,0 +1,17 @@ +export const makeUser = (props) => { + const user = {}; + + user.name = props.name; + user.picture = props.picture; + user.location = props.location; + + return user; +}; + +export const normalize = (users) => ({ + byId: users.reduce((obj, user, index) => ({ + ...obj, + [index]: makeUser(user), + }), {}), + ids: users.map((user, index) => index), +}); diff --git a/client/user/userReducer.js b/client/user/userReducer.js new file mode 100644 index 0000000..0c794a6 --- /dev/null +++ b/client/user/userReducer.js @@ -0,0 +1,29 @@ +import { handle } from 'redux-pack'; +import * as userActionTypes from './userActionTypes'; +import * as userHelpers from './userHelpers'; + +export const initialState = { + byId: {}, + ids: [], +}; + +export const reducer = (state = initialState, action) => { + const { type, payload } = action; + + switch (type) { + case userActionTypes.GET_ALL: + return handle(state, action, { + success: (s) => ({ + ...s, + ...userHelpers.normalize(payload.results), + }), + failure: (s) => ({ + ...s, + error: payload.error, + }), + }); + + default: + return state; + } +}; diff --git a/client/services/user/userReducer.test.js b/client/user/userReducer.test.js similarity index 62% rename from client/services/user/userReducer.test.js rename to client/user/userReducer.test.js index 85de038..7964926 100644 --- a/client/services/user/userReducer.test.js +++ b/client/user/userReducer.test.js @@ -1,10 +1,10 @@ import nock from 'nock'; import { LIFECYCLE } from 'redux-pack'; -import * as testHelpers from '../../utils/testHelpers'; -import * as userTypes from './userTypes'; +import * as testHelpers from '../utils/testHelpers'; +import * as userActionTypes from './userActionTypes'; import * as userActionCreators from './userActionCreators'; -import * as userModel from './userModel'; -import reducer, { initialState } from './userReducer'; +import * as userHelpers from './userHelpers'; +import * as userReducer from './userReducer'; describe('user/userActionCreators', () => { const store = testHelpers.mockStore(); @@ -14,7 +14,7 @@ describe('user/userActionCreators', () => { store.clearActions(); }); - it(`dispatches ${userTypes.GET_ALL}`, async () => { + it(`dispatches ${userActionTypes.GET_ALL}`, async () => { const apiResult = { results: [{}, {}, {}] }; nock('https://randomuser.me') @@ -24,10 +24,10 @@ describe('user/userActionCreators', () => { const expectedActions = [ testHelpers.makeReduxPackAction(LIFECYCLE.START, { - type: userTypes.GET_ALL, + type: userActionTypes.GET_ALL, }), testHelpers.makeReduxPackAction(LIFECYCLE.SUCCESS, { - type: userTypes.GET_ALL, + type: userActionTypes.GET_ALL, payload: apiResult, meta: { startPayload: undefined }, }), @@ -42,35 +42,35 @@ describe('user/userActionCreators', () => { describe('user/userReducer', () => { it('returns intialState', () => { - const finalState = reducer(undefined, {}); - const expectedState = initialState; + const finalState = userReducer.reducer(undefined, {}); + const expectedState = userReducer.initialState; expect(finalState).toEqual(expectedState); }); - it(`sets user on ${userTypes.GET_ALL}:success`, () => { + it(`sets user on ${userActionTypes.GET_ALL}:success`, () => { const apiResult = [{}, {}, {}]; - const finalState = reducer( - initialState, + const finalState = userReducer.reducer( + userReducer.initialState, testHelpers.makeReduxPackAction(LIFECYCLE.SUCCESS, { - type: userTypes.GET_ALL, + type: userActionTypes.GET_ALL, payload: { results: apiResult }, }), ); const expectedState = { - ...initialState, - ...userModel.normalize(apiResult), + ...userReducer.initialState, + ...userHelpers.normalize(apiResult), }; expect(finalState).toEqual(expectedState); }); - it(`sets error on ${userTypes.GET_ALL}:failure`, () => { - const finalState = reducer( - initialState, + it(`sets error on ${userActionTypes.GET_ALL}:failure`, () => { + const finalState = userReducer.reducer( + userReducer.initialState, testHelpers.makeReduxPackAction(LIFECYCLE.FAILURE, { - type: userTypes.GET_ALL, + type: userActionTypes.GET_ALL, payload: { error: {}, }, @@ -78,7 +78,7 @@ describe('user/userReducer', () => { ); const expectedState = { - ...initialState, + ...userReducer.initialState, error: {}, }; diff --git a/client/utils/importCss.js b/client/utils/importCss.js index ccc53fd..b47e1ea 100644 --- a/client/utils/importCss.js +++ b/client/utils/importCss.js @@ -4,7 +4,7 @@ export default (chunkName) => { } else if (!(chunkName in window.__ASSETS_MANIFEST__)) { return Promise.reject(`chunk not found: ${chunkName}`); } else if (!window.__ASSETS_MANIFEST__[chunkName].css) { - return Promise.resolve(`chunk css does not exist: ${chunkName}`); + return Promise.resolve(`css chunk does not exist: ${chunkName}`); } else if (document.getElementById(`${chunkName}.css`)) { return Promise.resolve(`css chunk already loaded: ${chunkName}`); } diff --git a/client/vendor/modules/modules.js b/client/vendor/modules/modules.js index 0e29832..5f8e7f3 100644 --- a/client/vendor/modules/modules.js +++ b/client/vendor/modules/modules.js @@ -8,8 +8,8 @@ import 'preact-compat'; import 'query-string'; import 'react-helmet'; import 'react-redux'; -import 'react-router'; +import 'react-router-config'; +import 'react-router-dom'; import 'redux'; -import 'redux-connect'; import 'redux-pack'; import 'redux-thunk'; diff --git a/client/views/LandingPage/LandingPage.js b/client/views/LandingPage/LandingPage.js deleted file mode 100644 index 5bb8bd4..0000000 --- a/client/views/LandingPage/LandingPage.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import isEmpty from 'lodash/isEmpty'; -import { asyncConnect } from 'redux-connect'; -import { bindActionCreators, compose } from 'redux'; -import * as userActionCreators from '../../services/user/userActionCreators'; -import UsersListAsync from './UserList/UserListAsync'; -import './landingPage.css'; - -class LandingPage extends React.Component { - componentDidMount() { - const { userActions } = this.props; - userActions.getAll(); - } - - render() { - return ( -
-

PWA

-

An opinionated progressive web app boilerplate

- -
- ); - } -} - -LandingPage.propTypes = { - userActions: PropTypes.object.isRequired, -}; - -const beforeRouteEnter = [{ - promise: ({ store: { dispatch, getState } }) => { - const promise = isEmpty(getState().user.ids) - ? dispatch(userActionCreators.getAll()) : null; - return __BROWSER__ ? null : promise; - }, -}]; - -const mapDispatchToProps = (dispatch) => ({ - userActions: bindActionCreators(userActionCreators, dispatch), -}); - -export default compose( - asyncConnect(beforeRouteEnter, null, mapDispatchToProps), -)(LandingPage); diff --git a/package.json b/package.json index d3e4d05..cc5fe0f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "start": "npm run local", "stop": "pm2 delete pm2.json", "prelocal": "mkdir -p ./build/server/ && touch ./build/server/index.js", - "local": "PWA_ENV=local PWA_PUBLIC_PATH=http://localhost:8080/build/client/ PWA_SSR=false NODE_ENV=development PORT=8000 webpack-dashboard -- run-p local:*", + "local": "PWA_ENV=local PWA_PUBLIC_PATH=http://localhost:8080/build/client/ PWA_SSR=true NODE_ENV=development PORT=8000 webpack-dashboard -- run-p local:*", "local:client": "webpack-dev-server --config ./webpack.client.js --hot", "local:server": "webpack --config ./webpack.server.js --watch", "local:serve": "pm2 start pm2.json --only pwa-local", @@ -42,78 +42,82 @@ "pm2": "pm2" }, "lint-staged": { - "*.js": ["eslint"], - "*.css": ["stylelint"] + "*.js": [ + "eslint" + ], + "*.css": [ + "stylelint" + ] }, "license": "MIT", "devDependencies": { "babel-core": "6.26.0", - "babel-eslint": "8.0.3", - "babel-jest": "21.2.0", + "babel-eslint": "8.2.2", + "babel-jest": "22.4.1", "babel-loader": "7.1.2", - "babel-plugin-transform-react-remove-prop-types": "0.4.10", + "babel-plugin-transform-react-remove-prop-types": "0.4.13", "babel-preset-env": "1.6.1", "babel-preset-react": "6.24.1", "babel-preset-stage-1": "6.24.1", - "css-loader": "0.28.7", - "eslint": "4.13.1", + "css-loader": "0.28.9", + "eslint": "4.18.1", "eslint-config-airbnb": "16.1.0", - "eslint-plugin-import": "2.8.0", - "eslint-plugin-jsx-a11y": "6.0.2", - "eslint-plugin-react": "7.5.1", - "file-loader": "1.1.5", + "eslint-plugin-import": "2.9.0", + "eslint-plugin-jsx-a11y": "6.0.3", + "eslint-plugin-react": "7.7.0", + "file-loader": "1.1.9", "husky": "0.14.3", - "jest": "21.2.1", - "lint-staged": "6.0.0", + "jest": "22.4.2", + "lint-staged": "7.0.0", "npm-run-all": "4.1.2", - "pm2": "2.8.0", + "pm2": "2.10.1", "postcss-cssnext": "2.11.0", "postcss-import": "10.0.0", "postcss-loader": "1.3.3", "postcss-url": "7.0.0", "rimraf": "2.6.2", - "style-loader": "0.19.0", - "stylelint": "8.3.1", - "stylelint-config-standard": "18.0.0", + "style-loader": "0.20.2", + "stylelint": "9.1.1", + "stylelint-config-standard": "18.1.0", "url-loader": "0.6.2", - "webpack-dev-server": "2.9.7" + "webpack-dev-server": "2.11.1" }, "dependencies": { "assets-webpack-plugin": "3.5.1", "babel-polyfill": "6.26.0", "classnames": "2.2.5", - "clean-webpack-plugin": "0.1.17", - "compression": "1.7.1", + "clean-webpack-plugin": "0.1.18", + "compression": "1.7.2", "connect-slashes": "1.3.1", - "copy-webpack-plugin": "4.2.3", + "copy-webpack-plugin": "4.4.2", "express": "4.16.2", "extract-css-chunks-webpack-plugin": "2.0.18", - "helmet": "3.9.0", + "helmet": "3.11.0", "isomorphic-fetch": "2.2.1", - "lodash": "4.17.4", - "moment": "2.19.4", + "lodash": "4.17.5", + "moment": "2.20.1", "morgan": "1.9.0", - "nock": "9.1.4", + "nock": "9.1.10", "normalize.css": "7.0.0", "preact": "8.2.7", - "preact-compat": "3.17.0", + "preact-compat": "3.18.0", "prop-types": "15.6.0", - "query-string": "5.0.1", - "react": "15.6.1", - "react-dom": "15.6.1", + "query-string": "5.1.0", + "react": "16.2.0", + "react-dom": "16.2.0", "react-helmet": "5.2.0", "react-loadable": "5.3.1", - "react-redux": "5.0.6", - "react-router": "3.0.2", + "react-redux": "5.0.7", + "react-router-config": "1.0.0-beta.4", + "react-router-dom": "4.2.2", "redux": "3.7.2", - "redux-connect": "5.1.0", - "redux-mock-store": "1.3.0", + "redux-mock-store": "1.5.1", "redux-pack": "0.1.5", "redux-thunk": "2.2.0", "sw-precache-webpack-plugin": "0.11.4", "webpack": "3.6.0", - "webpack-bundle-analyzer": "2.9.1", - "webpack-dashboard": "1.0.2", + "webpack-bundle-analyzer": "2.10.1", + "webpack-dashboard": "1.1.1", "webpack-node-externals": "1.6.0" }, "jest": { diff --git a/server/index.js b/server/index.js index 02bd0cd..5871cd5 100644 --- a/server/index.js +++ b/server/index.js @@ -5,19 +5,20 @@ import compression from 'compression'; import morgan from 'morgan'; import slashes from 'connect-slashes'; import Loadable from 'react-loadable'; -import renderMiddleware from './middlewares/renderMiddleware/renderMiddleware'; +import renderMiddleware from './middlewares/renderMiddleware'; + +const { PORT } = process.env; const app = express(); app.use(helmet({ dnsPrefetchControl: false })); app.use(compression()); app.use(morgan(__LOCAL__ ? 'dev' : 'combined')); -app.use('/build/client', express.static('build/client')); app.use('/serviceWorker.js', express.static('build/client/serviceWorker.js')); app.use('/manifest.json', express.static('build/client/manifest.json')); +app.use('/build/client', express.static('build/client')); app.use(slashes(true)); app.use(renderMiddleware); -const PORT = process.env.PORT || 8000; Loadable.preloadAll().then(() => { app.listen(PORT, () => { // eslint-disable-next-line diff --git a/server/middlewares/renderMiddleware/execComponentWillServerRender.js b/server/middlewares/renderMiddleware/execComponentWillServerRender.js new file mode 100644 index 0000000..23383b6 --- /dev/null +++ b/server/middlewares/renderMiddleware/execComponentWillServerRender.js @@ -0,0 +1,24 @@ +export default (branches, ctx) => { + const promises = branches.map(async (branch) => { + let { component } = branch.route; + const context = { + match: branch.match, + ...ctx, + }; + + if (component.preload) { + const loadedComponent = await component.preload(); + component = loadedComponent.default; + } + + if (component.componentWillServerRender) { + return Promise + .resolve(component.componentWillServerRender(context)) + .catch(() => {}); + } + + return Promise.resolve(); + }); + + return Promise.all(promises); +}; diff --git a/server/middlewares/renderMiddleware/index.js b/server/middlewares/renderMiddleware/index.js new file mode 100644 index 0000000..287319f --- /dev/null +++ b/server/middlewares/renderMiddleware/index.js @@ -0,0 +1 @@ +export default from './renderMiddleware'; diff --git a/server/middlewares/renderMiddleware/renderMiddleware.js b/server/middlewares/renderMiddleware/renderMiddleware.js index 4c3619d..ff88c43 100644 --- a/server/middlewares/renderMiddleware/renderMiddleware.js +++ b/server/middlewares/renderMiddleware/renderMiddleware.js @@ -1,58 +1,52 @@ import React from 'react'; import Helmet from 'react-helmet'; import Loadable from 'react-loadable'; +import { matchRoutes, renderRoutes } from 'react-router-config'; import { renderToString } from 'react-dom/server'; -import { match } from 'react-router'; +import { StaticRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; -import { ReduxAsyncConnect, loadOnServer } from 'redux-connect'; -import configureStore from '../../../client/store/configureStore'; +import createStore from '../../../client/store/createStore'; import routes from '../../../client/routes'; +import execComponentWillServerRender from './execComponentWillServerRender'; import html from './html'; const PWA_SSR = process.env.PWA_SSR === 'true'; -const serverRenderedChunks = async (req, res, renderProps) => { - const route = renderProps.routes[renderProps.routes.length - 1]; - const store = configureStore(); +export default async (req, res) => { + const location = req.originalUrl || req.url; + const branches = matchRoutes(routes, location); + const branch = branches[branches.length - 1]; + const store = createStore(); + const context = {}; const chunks = []; res.set('Content-Type', 'text/html'); - const earlyChunk = html.earlyChunk(route); + const earlyChunk = html.earlyChunk(branch.route); res.write(earlyChunk); res.flush(); - if (PWA_SSR) await loadOnServer({ ...renderProps, store }); + if (PWA_SSR) { + await execComponentWillServerRender(branches, { req, res, store }); + } + + const app = PWA_SSR ? renderToString( + chunks.push(name.replace(/.*\//, ''))}> + + + {renderRoutes(routes)} + + + , + ) : ''; const lateChunk = html.lateChunk( - PWA_SSR ? renderToString( - chunks.push(name.replace(/.*\//, ''))}> - - - - , - ) : '', + app, Helmet.renderStatic(), store.getState(), - route, + branch.route, chunks, ); res.end(lateChunk); }; - -export default (req, res) => { - match({ - routes, - location: req.originalUrl, - }, (error, redirectLocation, renderProps) => { - if (error) { - return res.status(500).send(error.message); - } else if (redirectLocation) { - return res.redirect(302, redirectLocation.pathname + redirectLocation.search); - } else if (renderProps) { - return serverRenderedChunks(req, res, renderProps); - } - return res.status(404).send('404: Not Found'); - }); -}; diff --git a/webpack.client.js b/webpack.client.js index a8c851e..8a3b84d 100644 --- a/webpack.client.js +++ b/webpack.client.js @@ -32,6 +32,7 @@ module.exports = { alias: { react: 'preact-compat', 'react-dom': 'preact-compat', + 'react-dom/server': 'preact-compat/server', }, }, diff --git a/webpack.server.js b/webpack.server.js index 4f36c23..4865784 100644 --- a/webpack.server.js +++ b/webpack.server.js @@ -30,6 +30,7 @@ module.exports = { alias: { react: 'preact-compat', 'react-dom': 'preact-compat', + 'react-dom/server': 'preact-compat/server', }, }, From fe44c50bfea7d5826dc24c06906180150b6fe9b2 Mon Sep 17 00:00:00 2001 From: Lakshya Ranganath Date: Sat, 24 Feb 2018 19:40:37 +0530 Subject: [PATCH 2/5] move to styled-components --- .babelrc | 11 +- .eslintignore | 3 + .gitignore | 3 + .stylelintignore | 3 + .stylelintrc | 11 +- client/core/Flex/Col.js | 37 ---- client/core/Flex/Flex.js | 58 +++++ client/core/Flex/Grid.js | 27 --- client/core/Flex/Row.js | 49 ----- client/core/Flex/flex.css | 200 ------------------ client/core/Flex/index.js | 11 +- client/core/Spacer/Spacer.js | 38 ++++ client/core/Spacer/index.js | 1 + client/core/Text/Text.js | 34 +++ client/core/Text/index.js | 1 + client/core/Wrapper/Wrapper.js | 14 +- client/core/Wrapper/styles.js | 18 ++ client/core/Wrapper/wrapper.css | 3 - client/core/theme/index.js | 2 + client/core/theme/injectBaseStyles.js | 46 ++++ client/core/theme/theme.js | 90 ++++++++ client/home/HomePage/HomePage.js | 14 +- client/home/HomePage/homePage.css | 6 - client/home/HomePage/index.js | 6 +- client/user/UserList/UserList.js | 61 +++--- client/user/UserList/index.js | 6 +- client/user/UserList/userList.css | 33 --- client/utils/importCss.js | 38 ---- client/utils/pluralize.js | 2 - .../{vendor/modules/modules.js => vendor.js} | 2 +- client/vendor/styles/styles.css | 51 ----- client/vendor/styles/utility.css | 19 -- client/vendor/styles/variables.css | 13 -- package.json | 20 +- postcss.config.js | 7 - .../middlewares/renderMiddleware/fragments.js | 12 +- server/middlewares/renderMiddleware/html.js | 7 +- .../renderMiddleware/renderMiddleware.js | 7 +- webpack.client.js | 8 +- webpack.server.js | 6 +- 40 files changed, 392 insertions(+), 586 deletions(-) delete mode 100644 client/core/Flex/Col.js create mode 100644 client/core/Flex/Flex.js delete mode 100644 client/core/Flex/Grid.js delete mode 100644 client/core/Flex/Row.js delete mode 100644 client/core/Flex/flex.css create mode 100644 client/core/Spacer/Spacer.js create mode 100644 client/core/Spacer/index.js create mode 100644 client/core/Text/Text.js create mode 100644 client/core/Text/index.js create mode 100644 client/core/Wrapper/styles.js delete mode 100644 client/core/Wrapper/wrapper.css create mode 100644 client/core/theme/index.js create mode 100644 client/core/theme/injectBaseStyles.js create mode 100644 client/core/theme/theme.js delete mode 100644 client/home/HomePage/homePage.css delete mode 100644 client/user/UserList/userList.css delete mode 100644 client/utils/importCss.js delete mode 100644 client/utils/pluralize.js rename client/{vendor/modules/modules.js => vendor.js} (91%) delete mode 100644 client/vendor/styles/styles.css delete mode 100644 client/vendor/styles/utility.css delete mode 100644 client/vendor/styles/variables.css delete mode 100644 postcss.config.js diff --git a/.babelrc b/.babelrc index 2fc4791..97bb45c 100644 --- a/.babelrc +++ b/.babelrc @@ -12,7 +12,10 @@ "stage-1" ], "plugins": [ - "react-loadable/babel" + "react-loadable/babel", + ["styled-components", { + "ssr": true + }] ], "env": { "test": { @@ -24,7 +27,11 @@ }, "production": { "plugins": [ - "transform-react-remove-prop-types" + "transform-react-remove-prop-types", + ["styled-components", { + "displayName": false, + "ssr": true + }] ] } } diff --git a/.eslintignore b/.eslintignore index 4a5d465..e6cdaa6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,9 @@ .idea .vscode +es +lib build coverage node_modules npm-debug.log* +lerna-debug.log* diff --git a/.gitignore b/.gitignore index 4a5d465..e6cdaa6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .idea .vscode +es +lib build coverage node_modules npm-debug.log* +lerna-debug.log* diff --git a/.stylelintignore b/.stylelintignore index 4a5d465..e6cdaa6 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,6 +1,9 @@ .idea .vscode +es +lib build coverage node_modules npm-debug.log* +lerna-debug.log* diff --git a/.stylelintrc b/.stylelintrc index d2ea289..33a4989 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,3 +1,12 @@ { - "extends": "stylelint-config-standard", + "processors": ["stylelint-processor-styled-components"], + "extends": [ + "stylelint-config-standard", + "stylelint-config-styled-components" + ], + "rules": { + "indentation": null, + "comment-empty-line-before": null + }, + "syntax": "scss" } diff --git a/client/core/Flex/Col.js b/client/core/Flex/Col.js deleted file mode 100644 index 5b98a94..0000000 --- a/client/core/Flex/Col.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cn from 'classnames'; - -const Col = ({ className, tag, children, ...restProps }) => - React.createElement(tag, { - className: cn('col', { - [`col--${restProps.size}`]: restProps.size, - [`col--offset-${restProps.offset}`]: restProps.offset, - 'col--first': restProps.first, - 'col--last': restProps.last, - 'col--reverse': restProps.reverse, - }, className), - }, children); - -Col.propTypes = { - className: PropTypes.string, - tag: PropTypes.string, - size: PropTypes.number, - offset: PropTypes.number, - first: PropTypes.bool, - last: PropTypes.bool, - reverse: PropTypes.bool, - children: PropTypes.node.isRequired, -}; - -Col.defaultProps = { - className: '', - tag: 'div', - size: 0, - offset: 0, - first: false, - last: false, - reverse: false, -}; - -export default Col; diff --git a/client/core/Flex/Flex.js b/client/core/Flex/Flex.js new file mode 100644 index 0000000..7dafb59 --- /dev/null +++ b/client/core/Flex/Flex.js @@ -0,0 +1,58 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; + +const Flex = styled(({ + alignContent, + alignItems, + alignSelf, + children, + component, + display, + flex, + flexBasis, + flexDirection, + flexGrow, + flexShrink, + flexWrap, + justifyContent, + order, + ...props +}) => React.createElement(component, props, children))` + ${(p) => p.alignContent ? `align-content: ${p.alignContent};` : ''} + ${(p) => p.alignSelf ? `align-self: ${p.alignSelf};` : ''} + ${(p) => p.alignItems ? `align-items: ${p.alignItems};` : ''} + ${(p) => p.display ? `display: ${p.display};` : ''} + ${(p) => p.flex ? `flex: ${p.flex};` : ''} + ${(p) => p.flexBasis ? `flex-basis: ${p.flexBasis};` : ''} + ${(p) => p.flexDirection ? `flex-direction: ${p.flexDirection};` : ''} + ${(p) => p.flexGrow ? `flex-grow: ${p.flexGrow};` : ''} + ${(p) => p.flexShrink ? `flex-shrink: ${p.flexShrink};` : ''} + ${(p) => p.flexWrap ? `flex-wrap: ${p.flexWrap};` : ''} + ${(p) => p.justifyContent ? `justify-content: ${p.justifyContent};` : ''} + ${(p) => p.order ? `order: ${p.order};` : ''} +`; + +Flex.propTypes = { + alignContent: PropTypes.oneOf(['center', 'flex-end', 'flex-start', 'space-around', 'space-between', 'stretch']), + alignItems: PropTypes.oneOf(['baseline', 'center', 'flex-end', 'flex-start', 'stretch']), + alignSelf: PropTypes.oneOf(['baseline', 'center', 'flex-end', 'flex-start', 'stretch']), + children: PropTypes.node, + display: PropTypes.oneOf(['flex', 'inline-flex']), + component: PropTypes.oneOf(['article', 'aside', 'div', 'figure', 'footer', 'header', 'main', 'nav', 'section']), + flex: PropTypes.string, + flexBasis: PropTypes.string, + flexDirection: PropTypes.oneOf(['column-reverse', 'column', 'row-reverse', 'row']), + flexGrow: PropTypes.number, + flexShrink: PropTypes.number, + flexWrap: PropTypes.oneOf(['nowrap', 'wrap-reverse', 'wrap']), + justifyContent: PropTypes.oneOf(['center', 'flex-end', 'flex-start', 'space-around', 'space-between']), + order: PropTypes.number, +}; + +Flex.defaultProps = { + component: 'div', + display: 'flex', +}; + +export default Flex; diff --git a/client/core/Flex/Grid.js b/client/core/Flex/Grid.js deleted file mode 100644 index be59f9d..0000000 --- a/client/core/Flex/Grid.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cn from 'classnames'; - -const Grid = ({ className, tag, fluid, children, ...restProps }) => - React.createElement(tag, { - className: cn({ - container: !fluid, - 'container-fluid': fluid, - }, className), - ...restProps, - }, children); - -Grid.propTypes = { - className: PropTypes.string, - tag: PropTypes.string, - fluid: PropTypes.bool, - children: PropTypes.node.isRequired, -}; - -Grid.defaultProps = { - className: '', - tag: 'div', - fluid: false, -}; - -export default Grid; diff --git a/client/core/Flex/Row.js b/client/core/Flex/Row.js deleted file mode 100644 index abf9963..0000000 --- a/client/core/Flex/Row.js +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cn from 'classnames'; - -const Row = ({ className, tag, children, ...restProps }) => - React.createElement(tag, { - className: cn('row', { - 'row--start': restProps.start, - 'row--center': restProps.center, - 'row--end': restProps.end, - 'row--top': restProps.top, - 'row--middle': restProps.middle, - 'row--bottom': restProps.bottom, - 'row--around': restProps.around, - 'row--between': restProps.between, - 'row--reverse': restProps.reverse, - }, className), - }, children); - -Row.propTypes = { - className: PropTypes.string, - tag: PropTypes.string, - start: PropTypes.bool, - center: PropTypes.bool, - end: PropTypes.bool, - top: PropTypes.bool, - middle: PropTypes.bool, - bottom: PropTypes.bool, - around: PropTypes.bool, - between: PropTypes.bool, - reverse: PropTypes.bool, - children: PropTypes.node.isRequired, -}; - -Row.defaultProps = { - className: '', - tag: 'div', - start: false, - center: false, - end: false, - top: false, - middle: false, - bottom: false, - around: false, - between: false, - reverse: false, -}; - -export default Row; diff --git a/client/core/Flex/flex.css b/client/core/Flex/flex.css deleted file mode 100644 index 7e10b24..0000000 --- a/client/core/Flex/flex.css +++ /dev/null @@ -1,200 +0,0 @@ -.container { - margin-right: auto; - margin-left: auto; -} - -.container-fluid { - margin-right: auto; - margin-left: auto; - padding-left: 16px; - padding-right: 16px; -} - -.row { - box-sizing: border-box; - display: flex; - flex: 0 1 auto; - flex-flow: row wrap; - margin-right: -8px; - margin-left: -8px; -} - -.row--start { - justify-content: flex-start; - text-align: start; -} - -.row--center { - justify-content: center; - text-align: center; -} - -.row--end { - justify-content: flex-end; - text-align: end; -} - -.row--top { - align-items: flex-start; -} - -.row--middle { - align-items: center; -} - -.row--bottom { - align-items: flex-end; -} - -.row--around { - justify-content: space-around; -} - -.row--between { - justify-content: space-between; -} - -.row--reverse { - flex-direction: row-reverse; -} - -.col, -.col--1, -.col--2, -.col--3, -.col--4, -.col--5, -.col--6, -.col--7, -.col--8, -.col--9, -.col--10, -.col--11, -.col--12 { - box-sizing: border-box; - flex: 0 0 auto; - padding-right: 8px; - padding-left: 8px; -} - -.col { - flex-grow: 1; - flex-basis: 0; - max-width: 100%; -} - -.col--1 { - flex-basis: 8.333%; - max-width: 8.333%; -} - -.col--2 { - flex-basis: 16.667%; - max-width: 16.667%; -} - -.col--3 { - flex-basis: 25%; - max-width: 25%; -} - -.col--4 { - flex-basis: 33.333%; - max-width: 33.333%; -} - -.col--5 { - flex-basis: 41.667%; - max-width: 41.667%; -} - -.col--6 { - flex-basis: 50%; - max-width: 50%; -} - -.col--7 { - flex-basis: 58.333%; - max-width: 58.333%; -} - -.col--8 { - flex-basis: 66.667%; - max-width: 66.667%; -} - -.col--9 { - flex-basis: 75%; - max-width: 75%; -} - -.col--10 { - flex-basis: 83.333%; - max-width: 83.333%; -} - -.col--11 { - flex-basis: 91.667%; - max-width: 91.667%; -} - -.col--12 { - flex-basis: 100%; - max-width: 100%; -} - -.col--offset-1 { - margin-left: 8.333%; -} - -.col--offset-2 { - margin-left: 16.667%; -} - -.col--offset-3 { - margin-left: 25%; -} - -.col--offset-4 { - margin-left: 33.333%; -} - -.col--offset-5 { - margin-left: 41.667%; -} - -.col--offset-6 { - margin-left: 50%; -} - -.col--offset-7 { - margin-left: 58.333%; -} - -.col--offset-8 { - margin-left: 66.667%; -} - -.col--offset-9 { - margin-left: 75%; -} - -.col--offset-10 { - margin-left: 83.333%; -} - -.col--offset-11 { - margin-left: 91.667%; -} - -.col--first { - order: -1; -} - -.col--last { - order: 1; -} - -.col--reverse { - flex-direction: column-reverse; -} diff --git a/client/core/Flex/index.js b/client/core/Flex/index.js index 3e3e86a..5ee4d4e 100644 --- a/client/core/Flex/index.js +++ b/client/core/Flex/index.js @@ -1,10 +1 @@ -import Grid from './Grid'; -import Row from './Row'; -import Col from './Col'; -import './flex.css'; - -export default { - Grid, - Row, - Col, -}; +export default from './Flex'; diff --git a/client/core/Spacer/Spacer.js b/client/core/Spacer/Spacer.js new file mode 100644 index 0000000..bb81ab2 --- /dev/null +++ b/client/core/Spacer/Spacer.js @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; + +const Spacer = styled(({ + children, + ...props +}) => React.cloneElement(children, props))` + ${(p) => p.margin || p.margin === 0 ? `margin: ${p.theme.px(p.margin)} !important;` : ''} + ${(p) => p.padding || p.padding === 0 ? `padding: ${p.theme.px(p.padding)} !important;` : ''} + ${(p) => p.maxWidth ? `max-width: ${p.maxWidth} !important;` : ''} + ${(p) => p.width ? `width: ${p.width} !important;` : ''} + ${(p) => p.minWidth ? `min-width: ${p.minWidth} !important;` : ''} + ${(p) => p.minHeight ? `min-height: ${p.minHeight} !important;` : ''} + ${(p) => p.height ? `height: ${p.height} !important;` : ''} + ${(p) => p.maxHeight ? `max-height: ${p.maxHeight} !important;` : ''} +`; + +Spacer.propTypes = { + margin: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), + ]), + padding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), + ]), + maxWidth: PropTypes.string, + width: PropTypes.string, + minWidth: PropTypes.string, + minHeight: PropTypes.string, + height: PropTypes.string, + maxHeight: PropTypes.string, +}; + +export default Spacer; diff --git a/client/core/Spacer/index.js b/client/core/Spacer/index.js new file mode 100644 index 0000000..3f66c97 --- /dev/null +++ b/client/core/Spacer/index.js @@ -0,0 +1 @@ +export default from './Spacer'; diff --git a/client/core/Text/Text.js b/client/core/Text/Text.js new file mode 100644 index 0000000..52f5730 --- /dev/null +++ b/client/core/Text/Text.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; + +const Text = styled(({ + component, + children, + ...props +}) => React.createElement(component, props, children))` + ${(p) => p.color ? `color: ${p.theme.color[p.color]};` : ''} + ${(p) => p.size ? `font-size: ${p.theme.fontSize[p.size]};` : ''} + ${(p) => p.weight ? `font-weight: ${p.theme.fontWeight[p.weight]};` : ''} + ${(p) => p.family ? `font-family: ${p.theme.fontFamily[p.family]};` : ''} + ${(p) => p.truncate ? ` + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + ` : ''} +`; + +Text.propTypes = { + component: PropTypes.node, + color: PropTypes.string, + size: PropTypes.string, + weight: PropTypes.string, + family: PropTypes.string, + truncate: PropTypes.bool, +}; + +Text.defaultProps = { + component: 'p', +}; + +export default Text; diff --git a/client/core/Text/index.js b/client/core/Text/index.js new file mode 100644 index 0000000..b960764 --- /dev/null +++ b/client/core/Text/index.js @@ -0,0 +1 @@ +export default from './Text'; diff --git a/client/core/Wrapper/Wrapper.js b/client/core/Wrapper/Wrapper.js index 26941d2..71b02ee 100644 --- a/client/core/Wrapper/Wrapper.js +++ b/client/core/Wrapper/Wrapper.js @@ -1,9 +1,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { ThemeProvider } from 'styled-components'; import Helmet from 'react-helmet'; import { renderRoutes } from 'react-router-config'; +import theme from '../theme'; import performanceMark from '../../utils/performanceMark'; -import './wrapper.css'; +import './styles'; class Wrapper extends Component { componentDidMount() { @@ -14,10 +16,12 @@ class Wrapper extends Component { const { route } = this.props; return ( -
- - {renderRoutes(route.routes)} -
+ +
+ + {renderRoutes(route.routes)} +
+
); } } diff --git a/client/core/Wrapper/styles.js b/client/core/Wrapper/styles.js new file mode 100644 index 0000000..f99d700 --- /dev/null +++ b/client/core/Wrapper/styles.js @@ -0,0 +1,18 @@ +/* eslint-disable no-unused-expressions */ +import styledNormalize from 'styled-normalize'; +import { injectGlobal } from 'styled-components'; +import theme, { injectBaseStyles } from '../../core/theme'; + +injectGlobal`${styledNormalize}`; +injectBaseStyles(theme); +injectGlobal` + html { + min-width: 320px; + } + + *, + *::after, + *::before { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + } +`; diff --git a/client/core/Wrapper/wrapper.css b/client/core/Wrapper/wrapper.css deleted file mode 100644 index e8ff585..0000000 --- a/client/core/Wrapper/wrapper.css +++ /dev/null @@ -1,3 +0,0 @@ -.wrapper { - min-width: 320px; -} diff --git a/client/core/theme/index.js b/client/core/theme/index.js new file mode 100644 index 0000000..7aa23cd --- /dev/null +++ b/client/core/theme/index.js @@ -0,0 +1,2 @@ +export default from './theme'; +export injectBaseStyles from './injectBaseStyles'; diff --git a/client/core/theme/injectBaseStyles.js b/client/core/theme/injectBaseStyles.js new file mode 100644 index 0000000..e7dc015 --- /dev/null +++ b/client/core/theme/injectBaseStyles.js @@ -0,0 +1,46 @@ +import { injectGlobal } from 'styled-components'; + +export default (theme) => injectGlobal` + html { + box-sizing: border-box; + } + + body { + background: ${theme.color.greyLighter}; + color: ${theme.color.greyDarker}; + font-size: ${theme.fontSize.s}; + font-weight: ${theme.fontWeight.normal}; + font-family: ${theme.fontFamily.roboto}, system-ui, sans-serif; + } + + *, + *::after, + *::before { + box-sizing: inherit; + } + + p, + h1, + h2, + h3, + h4, + h5, + h6, + ul, + ol { + padding: 0; + margin: 0; + } + + p { + line-height: 1.5; + } + + ul { + list-style: none; + } + + ol { + list-style: decimal inside; + } +`; diff --git a/client/core/theme/theme.js b/client/core/theme/theme.js new file mode 100644 index 0000000..b27706a --- /dev/null +++ b/client/core/theme/theme.js @@ -0,0 +1,90 @@ +const theme = {}; + +theme.borderRadius = '2px'; + +theme.boxShadow = []; +theme.boxShadow[0] = 'none'; +theme.boxShadow[1] = '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(114, 113, 113, 0.08)'; +theme.boxShadow[2] = '0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 3px 6px 0 rgba(174, 174, 174, 0.16)'; +theme.boxShadow[3] = '0 10px 20px 0 rgba(0, 0, 0, 0.12), 0 6px 6px 0 rgba(0, 0, 0, 0.16)'; +theme.boxShadow[4] = '0 14px 28px 0 rgba(0, 0, 0, 0.12), 0 10px 10px 0 rgba(0, 0, 0, 0.16)'; +theme.boxShadow[5] = '0 19px 38px 0 rgba(0, 0, 0, 0.16), 0 15px 12px 0 rgba(0, 0, 0, 0.16)'; + +theme.color = {}; +theme.color.greenLighter = '#e6f7ed'; +theme.color.greenLight = '#6ed396'; +theme.color.green = '#0eb550'; +theme.color.greenDark = '#00893d'; +theme.color.blueLighter = '#eeeffc'; +theme.color.blueLight = '#9aa4f2'; +theme.color.blue = '#5768e9'; +theme.color.blueDark = '#4451b6'; +theme.color.yellowLighter = '#fff0d6'; +theme.color.yellowLight = '#ffc866'; +theme.color.yellow = '#ffa400'; +theme.color.yellowDark = '#cc8300'; +theme.color.redLighter = '#ffeff1'; +theme.color.redLight = '#ffa3ab'; +theme.color.red = '#ff6673'; +theme.color.redDark = '#cc525c'; +theme.color.lagoonLighter = '#e5f1f3'; +theme.color.lagoonLight = '#66afb8'; +theme.color.lagoon = '#007989'; +theme.color.lagoonDark = '#004c56'; +theme.color.tealLighter = '#eaf3f1'; +theme.color.tealLight = '#a1d5ca'; +theme.color.teal = '#44ac95'; +theme.color.tealDark = '#2d907a'; +theme.color.chillLighter = '#f2f8f7'; +theme.color.chillLight = '#d4e8e4'; +theme.color.chill = '#bcdcd6'; +theme.color.chillDark = '#9ab5b0'; +theme.color.white = '#ffffff'; +theme.color.greyLighter = '#f1f1f1'; +theme.color.greyLight = '#dedede'; +theme.color.grey = '#aeaeae'; +theme.color.greyDark = '#727171'; +theme.color.greyDarker = '#4a4a4a'; +theme.color.black = '#000000'; +theme.color.translucent = 'rgba(0, 0, 0, 0.1)'; +theme.color.transparent = 'rgba(0, 0, 0, 0)'; +theme.color.primaryLighter = theme.color.greenLighter; +theme.color.primaryLight = theme.color.greenLight; +theme.color.primary = theme.color.green; +theme.color.primaryDark = theme.color.greenDark; +theme.color.accentLighter = theme.color.white; +theme.color.accentLight = theme.color.white; +theme.color.accent = theme.color.white; +theme.color.accentDark = theme.color.white; + +theme.fontFamily = {}; +theme.fontFamily.roboto = 'Roboto'; +theme.fontFamily.averta = 'Averta'; + +theme.fontSize = {}; +theme.fontSize.xxxxl = '32px'; +theme.fontSize.xxxl = '28px'; +theme.fontSize.xxl = '24px'; +theme.fontSize.xl = '20px'; +theme.fontSize.l = '18px'; +theme.fontSize.m = '16px'; +theme.fontSize.s = '14px'; +theme.fontSize.xs = '12px'; +theme.fontSize.xxs = '10px'; + +theme.fontWeight = {}; +theme.fontWeight.regular = 400; +theme.fontWeight.medium = 500; +theme.fontWeight.semibold = 600; +theme.fontWeight.bold = 700; + +theme.pxScale = 8; + +theme.px = (value) => { + const values = [].concat(value); + return values + .map((v) => typeof v === 'string' ? v : `${v * theme.pxScale}px`) + .join(' '); +}; + +export default theme; diff --git a/client/home/HomePage/HomePage.js b/client/home/HomePage/HomePage.js index 7081e35..e73b0a9 100644 --- a/client/home/HomePage/HomePage.js +++ b/client/home/HomePage/HomePage.js @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import { bindActionCreators, compose } from 'redux'; import * as userActionCreators from '../../user/userActionCreators'; import UsersList from '../../user/UserList'; -import './homePage.css'; +import Spacer from '../../core/Spacer'; class HomePage extends React.Component { static componentWillServerRender = ({ store }) => @@ -21,11 +21,13 @@ class HomePage extends React.Component { render() { return ( -
-

PWA

-

An opinionated progressive web app boilerplate

- -
+ +
+

PWA

+

An opinionated progressive web app boilerplate

+ +
+
); } } diff --git a/client/home/HomePage/homePage.css b/client/home/HomePage/homePage.css deleted file mode 100644 index e5d09a8..0000000 --- a/client/home/HomePage/homePage.css +++ /dev/null @@ -1,6 +0,0 @@ -@import '../../vendor/styles/variables.css'; - -.home-page { - margin-top: 12px; - text-align: center; -} diff --git a/client/home/HomePage/index.js b/client/home/HomePage/index.js index bb30a69..5732525 100644 --- a/client/home/HomePage/index.js +++ b/client/home/HomePage/index.js @@ -1,10 +1,6 @@ import Loadable from 'react-loadable'; -import importCss from '../../utils/importCss'; export default Loadable({ - loader: () => { - importCss('HomePage'); - return import('./HomePage' /* webpackChunkName: 'HomePage' */); - }, + loader: () => import('./HomePage' /* webpackChunkName: 'HomePage' */), loading: () => null, }); diff --git a/client/user/UserList/UserList.js b/client/user/UserList/UserList.js index 248d5a5..955d15e 100644 --- a/client/user/UserList/UserList.js +++ b/client/user/UserList/UserList.js @@ -1,10 +1,19 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import cn from 'classnames'; +import styled from 'styled-components'; import { compose } from 'redux'; import { connect } from 'react-redux'; +import Text from '../../core/Text'; import Flex from '../../core/Flex'; -import './userList.css'; +import Spacer from '../../core/Spacer'; + +const Image = styled.img` + cursor: pointer; + transition: all 0.2s ease-in-out; + border-radius: 50%; + border: 3px solid transparent; + border-color: ${(p) => p.isActive ? p.theme.color.primary : ''}; +`; class UsersList extends Component { state = { @@ -20,32 +29,28 @@ class UsersList extends Component { const { active } = this.state; return users.length ? ( -
- - { - users.map(({ name, picture }, i) => ( - - {name.first} -
- {name.first} -
-
- )) - } -
-

{users[active].location.street}

-
+ +
+ + { + users.map(({ name, picture }, i) => ( + + + {name.first} + + {name.first} + + )) + } + + {users[active].location.street} +
+
) : null; } } diff --git a/client/user/UserList/index.js b/client/user/UserList/index.js index b152365..34210e5 100644 --- a/client/user/UserList/index.js +++ b/client/user/UserList/index.js @@ -1,11 +1,7 @@ import React from 'react'; import Loadable from 'react-loadable'; -import importCss from '../../utils/importCss'; export default Loadable({ - loader: () => { - importCss('UserList'); - return import('./UserList' /* webpackChunkName: 'UserList' */); - }, + loader: () => import('./UserList' /* webpackChunkName: 'UserList' */), loading: () =>

Loading users...

, }); diff --git a/client/user/UserList/userList.css b/client/user/UserList/userList.css deleted file mode 100644 index ea04d13..0000000 --- a/client/user/UserList/userList.css +++ /dev/null @@ -1,33 +0,0 @@ -@import '../../vendor/styles/variables.css'; - -.user { - margin-top: 50px; - - &__list { - margin: 16px 0; - } - - &__img { - border-radius: 50%; - border: 3px solid transparent; - padding: 4px; - cursor: pointer; - transition: all 0.2s ease-in-out; - - &--active { - border-color: var(--color-primary); - } - } - - &__name { - visibility: hidden; - color: var(--color-secondary); - margin-top: 8px; - text-transform: uppercase; - transition: all 0.2s ease-in-out; - - &--visible { - visibility: visible; - } - } -} diff --git a/client/utils/importCss.js b/client/utils/importCss.js deleted file mode 100644 index b47e1ea..0000000 --- a/client/utils/importCss.js +++ /dev/null @@ -1,38 +0,0 @@ -export default (chunkName) => { - if (!__BROWSER__) { - return Promise.resolve(); - } else if (!(chunkName in window.__ASSETS_MANIFEST__)) { - return Promise.reject(`chunk not found: ${chunkName}`); - } else if (!window.__ASSETS_MANIFEST__[chunkName].css) { - return Promise.resolve(`css chunk does not exist: ${chunkName}`); - } else if (document.getElementById(`${chunkName}.css`)) { - return Promise.resolve(`css chunk already loaded: ${chunkName}`); - } - - const head = document.getElementsByTagName('head')[0]; - const link = document.createElement('link'); - link.href = window.__ASSETS_MANIFEST__[chunkName].css; - link.id = `${chunkName}.css`; - link.rel = 'stylesheet'; - - return new Promise((resolve, reject) => { - let timeout; - - link.onload = () => { - link.onload = null; - link.onerror = null; - clearTimeout(timeout); - resolve(`css chunk loaded: ${chunkName}`); - }; - - link.onerror = () => { - link.onload = null; - link.onerror = null; - clearTimeout(timeout); - reject(new Error(`could not load css chunk: ${chunkName}`)); - }; - - timeout = setTimeout(link.onerror, 30000); - head.appendChild(link); - }); -}; diff --git a/client/utils/pluralize.js b/client/utils/pluralize.js deleted file mode 100644 index b0ce1f8..0000000 --- a/client/utils/pluralize.js +++ /dev/null @@ -1,2 +0,0 @@ -export default (amount, text, suffix = 's') => - +amount > 1 ? `${text}${suffix}` : text; diff --git a/client/vendor/modules/modules.js b/client/vendor.js similarity index 91% rename from client/vendor/modules/modules.js rename to client/vendor.js index 5f8e7f3..e2b46fe 100644 --- a/client/vendor/modules/modules.js +++ b/client/vendor.js @@ -1,5 +1,4 @@ import 'babel-polyfill'; -import 'classnames'; import 'isomorphic-fetch'; import 'lodash/isEmpty'; import 'moment'; @@ -13,3 +12,4 @@ import 'react-router-dom'; import 'redux'; import 'redux-pack'; import 'redux-thunk'; +import 'styled-components'; diff --git a/client/vendor/styles/styles.css b/client/vendor/styles/styles.css deleted file mode 100644 index f7718e0..0000000 --- a/client/vendor/styles/styles.css +++ /dev/null @@ -1,51 +0,0 @@ -@import 'normalize.css'; -@import './utility.css'; -@import './variables.css'; - -html { - box-sizing: border-box; - font-size: 62.5%; -} - -body { - background: #fff; - color: var(--color-tertiary); - font-size: 1.4em; - font-weight: 400; - font-family: system-ui, sans-serif; -} - -*, -*::after, -*::before { - box-sizing: inherit; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -a { - color: inherit; - text-decoration: none; -} - -ul { - list-style: none; - padding: 0; - margin: 0; -} - -ol { - list-style: decimal inside; - padding: 0; - margin: 0; -} - -p, -h1, -h2, -h3, -h4, -h5, -h6 { - padding: 0; - margin: 0; -} diff --git a/client/vendor/styles/utility.css b/client/vendor/styles/utility.css deleted file mode 100644 index ef3035e..0000000 --- a/client/vendor/styles/utility.css +++ /dev/null @@ -1,19 +0,0 @@ -@import './variables.css'; - -.hide { - display: none; -} - -.text { - &-center { - text-align: center; - } - - &-left { - text-align: left; - } - - &-right { - text-align: right; - } -} diff --git a/client/vendor/styles/variables.css b/client/vendor/styles/variables.css deleted file mode 100644 index e16448c..0000000 --- a/client/vendor/styles/variables.css +++ /dev/null @@ -1,13 +0,0 @@ -:root { - /* colors */ - --color-primary: #5500eb; - --color-secondary: #4a4a4a; - --color-tertiary: #9b9b9b; - --color-quaternary: #e0e0e0; - --color-quinary: #f1f1f1; - --color-error: #d00; - --color-link: #0d0; - - /* shadows */ - --shadow-primary: 0 2px 4px 0 rgba(0, 0, 0, 0.1); -} diff --git a/package.json b/package.json index cc5fe0f..640645a 100644 --- a/package.json +++ b/package.json @@ -36,16 +36,14 @@ "build:server": "webpack --config ./webpack.server.js --progress", "lint": "run-p lint:*", "lint:eslint": "eslint .", - "lint:stylelint": "stylelint '**/*.css'", + "lint:stylelint": "stylelint '**/*.js'", "test": "NODE_ENV=test jest", "precommit": "lint-staged && npm run test", "pm2": "pm2" }, "lint-staged": { "*.js": [ - "eslint" - ], - "*.css": [ + "eslint", "stylelint" ] }, @@ -55,11 +53,11 @@ "babel-eslint": "8.2.2", "babel-jest": "22.4.1", "babel-loader": "7.1.2", + "babel-plugin-styled-components": "1.5.0", "babel-plugin-transform-react-remove-prop-types": "0.4.13", "babel-preset-env": "1.6.1", "babel-preset-react": "6.24.1", "babel-preset-stage-1": "6.24.1", - "css-loader": "0.28.9", "eslint": "4.18.1", "eslint-config-airbnb": "16.1.0", "eslint-plugin-import": "2.9.0", @@ -71,38 +69,34 @@ "lint-staged": "7.0.0", "npm-run-all": "4.1.2", "pm2": "2.10.1", - "postcss-cssnext": "2.11.0", - "postcss-import": "10.0.0", - "postcss-loader": "1.3.3", - "postcss-url": "7.0.0", "rimraf": "2.6.2", "style-loader": "0.20.2", "stylelint": "9.1.1", "stylelint-config-standard": "18.1.0", + "stylelint-config-styled-components": "0.1.1", + "stylelint-processor-styled-components": "1.2.2", "url-loader": "0.6.2", "webpack-dev-server": "2.11.1" }, "dependencies": { "assets-webpack-plugin": "3.5.1", "babel-polyfill": "6.26.0", - "classnames": "2.2.5", "clean-webpack-plugin": "0.1.18", "compression": "1.7.2", "connect-slashes": "1.3.1", "copy-webpack-plugin": "4.4.2", "express": "4.16.2", - "extract-css-chunks-webpack-plugin": "2.0.18", "helmet": "3.11.0", "isomorphic-fetch": "2.2.1", "lodash": "4.17.5", "moment": "2.20.1", "morgan": "1.9.0", "nock": "9.1.10", - "normalize.css": "7.0.0", "preact": "8.2.7", "preact-compat": "3.18.0", "prop-types": "15.6.0", "query-string": "5.1.0", + "raw-loader": "0.5.1", "react": "16.2.0", "react-dom": "16.2.0", "react-helmet": "5.2.0", @@ -114,6 +108,8 @@ "redux-mock-store": "1.5.1", "redux-pack": "0.1.5", "redux-thunk": "2.2.0", + "styled-components": "3.1.6", + "styled-normalize": "4.0.0", "sw-precache-webpack-plugin": "0.11.4", "webpack": "3.6.0", "webpack-bundle-analyzer": "2.10.1", diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 3b0281a..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - plugins: { - 'postcss-import': {}, - 'postcss-url': {}, - 'postcss-cssnext': {}, - }, -}; diff --git a/server/middlewares/renderMiddleware/fragments.js b/server/middlewares/renderMiddleware/fragments.js index 3b571a0..a1a208b 100644 --- a/server/middlewares/renderMiddleware/fragments.js +++ b/server/middlewares/renderMiddleware/fragments.js @@ -1,17 +1,7 @@ /* eslint-disable max-len, import/no-unresolved */ -import fs from 'fs'; import assetsManifest from '../../build/client/assetsManifest.json'; -export const assets = Object.keys(assetsManifest) - .reduce((obj, entry) => ({ - ...obj, - [entry]: { - ...assetsManifest[entry], - styles: assetsManifest[entry].css - ? fs.readFileSync(`build/client/css/${assetsManifest[entry].css.split('/').pop()}`, 'utf8') - : undefined, - }, - }), {}); +export const assets = assetsManifest; export const scripts = { serviceWorker: ` diff --git a/server/middlewares/renderMiddleware/html.js b/server/middlewares/renderMiddleware/html.js index c4f99c1..4f7c6aa 100644 --- a/server/middlewares/renderMiddleware/html.js +++ b/server/middlewares/renderMiddleware/html.js @@ -18,12 +18,9 @@ export default { ${!assets[route.name] ? '' : ``}`; }, - lateChunk(app, head, initialState, route, chunks) { + lateChunk(app, styles, head, initialState, route, chunks) { return ` - ${__LOCAL__ ? '' : ``} - ${__LOCAL__ ? '' : ``} - ${__LOCAL__ || !assets[route.name] ? '' : ``} - ${__LOCAL__ ? '' : chunks.reduce((s, name) => `${s}`, '')} + ${__LOCAL__ ? '' : styles} ${__LOCAL__ ? '' : ''} diff --git a/server/middlewares/renderMiddleware/renderMiddleware.js b/server/middlewares/renderMiddleware/renderMiddleware.js index ff88c43..46c63ff 100644 --- a/server/middlewares/renderMiddleware/renderMiddleware.js +++ b/server/middlewares/renderMiddleware/renderMiddleware.js @@ -5,6 +5,7 @@ import { matchRoutes, renderRoutes } from 'react-router-config'; import { renderToString } from 'react-dom/server'; import { StaticRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; +import { ServerStyleSheet } from 'styled-components'; import createStore from '../../../client/store/createStore'; import routes from '../../../client/routes'; import execComponentWillServerRender from './execComponentWillServerRender'; @@ -16,6 +17,7 @@ export default async (req, res) => { const location = req.originalUrl || req.url; const branches = matchRoutes(routes, location); const branch = branches[branches.length - 1]; + const sheet = new ServerStyleSheet(); const store = createStore(); const context = {}; const chunks = []; @@ -30,7 +32,7 @@ export default async (req, res) => { await execComponentWillServerRender(branches, { req, res, store }); } - const app = PWA_SSR ? renderToString( + const app = PWA_SSR ? renderToString(sheet.collectStyles( chunks.push(name.replace(/.*\//, ''))}> @@ -38,10 +40,11 @@ export default async (req, res) => { , - ) : ''; + )) : ''; const lateChunk = html.lateChunk( app, + sheet.getStyleTags(), Helmet.renderStatic(), store.getState(), branch.route, diff --git a/webpack.client.js b/webpack.client.js index 8a3b84d..54f2e4d 100644 --- a/webpack.client.js +++ b/webpack.client.js @@ -4,7 +4,6 @@ const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const AssetsPlugin = require('assets-webpack-plugin'); -const ExtractCssChunks = require('extract-css-chunks-webpack-plugin'); const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const DashboardPlugin = require('webpack-dashboard/plugin'); @@ -18,7 +17,7 @@ module.exports = { entry: { main: './client/index.js', - vendor: ['./client/vendor/modules/modules.js', './client/vendor/styles/styles.css'], + vendor: './client/vendor.js', }, output: { @@ -39,12 +38,12 @@ module.exports = { module: { rules: isProd ? [ { test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'] }, - { test: /\.css$/, loader: ExtractCssChunks.extract({ use: [{ loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'] }) }, + { test: /\.css$/, use: ['raw-loader'] }, { test: /\.(gif|png|jpe?g|svg|ico)$/i, use: [{ loader: 'file-loader', options: { name: 'images/[name].[hash:8].[ext]' } }] }, { test: /\.(woff(2)?|ttf|otf|eot)(\?[a-z0-9=&.]+)?$/, use: [{ loader: 'url-loader', options: { limit: 1000, name: 'fonts/[name].[hash:8].[ext]' } }] }, ] : [ { test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'] }, - { test: /\.css$/, use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'] }, + { test: /\.css$/, use: ['raw-loader'] }, { test: /\.(gif|png|jpe?g|svg|ico)$/i, use: [{ loader: 'file-loader', options: { name: 'images/[name].[ext]' } }] }, { test: /\.(woff(2)?|ttf|otf|eot)(\?[a-z0-9=&.]+)?$/, use: [{ loader: 'url-loader', options: { limit: 1000, name: 'fonts/[name].[ext]' } }] }, ], @@ -95,7 +94,6 @@ module.exports = { screw_ie8: true, }, }), - new ExtractCssChunks('css/[name].[contenthash:8].css'), new CopyWebpackPlugin([ { from: './client/manifest.json' }, { from: './client/offline', to: 'offline/[name].00000001.[ext]' }, diff --git a/webpack.server.js b/webpack.server.js index 4865784..d35933c 100644 --- a/webpack.server.js +++ b/webpack.server.js @@ -14,7 +14,7 @@ module.exports = { target: 'node', externals: [ - nodeExternals({ whitelist: [/\.css$/] }), + nodeExternals(), /assetsManifest.json/, ], @@ -37,12 +37,12 @@ module.exports = { module: { rules: isProd ? [ { test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'] }, - { test: /\.css$/, use: ['css-loader/locals', 'postcss-loader'] }, + { test: /\.css$/, use: ['raw-loader'] }, { test: /\.(gif|png|jpe?g|svg|ico)$/i, use: [{ loader: 'file-loader', options: { name: 'images/[name].[hash:8].[ext]' } }] }, { test: /\.(woff(2)?|ttf|otf|eot)(\?[a-z0-9=&.]+)?$/, use: [{ loader: 'url-loader', options: { limit: 1000, name: 'fonts/[name].[hash:8].[ext]' } }] }, ] : [ { test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'] }, - { test: /\.css$/, use: ['css-loader/locals', 'postcss-loader'] }, + { test: /\.css$/, use: ['raw-loader'] }, { test: /\.(gif|png|jpe?g|svg|ico)$/i, use: [{ loader: 'file-loader', options: { name: 'images/[name].[ext]' } }] }, { test: /\.(woff(2)?|ttf|otf|eot)(\?[a-z0-9=&.]+)?$/, use: [{ loader: 'url-loader', options: { limit: 1000, name: 'fonts/[name].[ext]' } }] }, ], From 49a4b51aa5a4ed87e4d10b83b6e6bcf26c77f693 Mon Sep 17 00:00:00 2001 From: Lakshya Ranganath Date: Sun, 4 Mar 2018 07:05:32 +0530 Subject: [PATCH 3/5] move to modules structure in server --- server/index.js | 6 +++--- .../execComponentWillServerRender.js | 0 .../{middlewares/renderMiddleware => render}/fragments.js | 0 server/{middlewares/renderMiddleware => render}/html.js | 0 server/{middlewares/renderMiddleware => render}/index.js | 0 .../renderMiddleware => render}/renderMiddleware.js | 4 ++-- 6 files changed, 5 insertions(+), 5 deletions(-) rename server/{middlewares/renderMiddleware => render}/execComponentWillServerRender.js (100%) rename server/{middlewares/renderMiddleware => render}/fragments.js (100%) rename server/{middlewares/renderMiddleware => render}/html.js (100%) rename server/{middlewares/renderMiddleware => render}/index.js (100%) rename server/{middlewares/renderMiddleware => render}/renderMiddleware.js (93%) diff --git a/server/index.js b/server/index.js index 5871cd5..b61bf7d 100644 --- a/server/index.js +++ b/server/index.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import 'babel-polyfill'; import express from 'express'; import helmet from 'helmet'; @@ -5,7 +6,7 @@ import compression from 'compression'; import morgan from 'morgan'; import slashes from 'connect-slashes'; import Loadable from 'react-loadable'; -import renderMiddleware from './middlewares/renderMiddleware'; +import renderMiddleware from './render/renderMiddleware'; const { PORT } = process.env; @@ -21,7 +22,6 @@ app.use(renderMiddleware); Loadable.preloadAll().then(() => { app.listen(PORT, () => { - // eslint-disable-next-line - console.info(`pwa is running as ${__PWA_ENV__} on port ${PORT}`); + console.log(`pwa is running as ${__PWA_ENV__} on port ${PORT}`); }); }); diff --git a/server/middlewares/renderMiddleware/execComponentWillServerRender.js b/server/render/execComponentWillServerRender.js similarity index 100% rename from server/middlewares/renderMiddleware/execComponentWillServerRender.js rename to server/render/execComponentWillServerRender.js diff --git a/server/middlewares/renderMiddleware/fragments.js b/server/render/fragments.js similarity index 100% rename from server/middlewares/renderMiddleware/fragments.js rename to server/render/fragments.js diff --git a/server/middlewares/renderMiddleware/html.js b/server/render/html.js similarity index 100% rename from server/middlewares/renderMiddleware/html.js rename to server/render/html.js diff --git a/server/middlewares/renderMiddleware/index.js b/server/render/index.js similarity index 100% rename from server/middlewares/renderMiddleware/index.js rename to server/render/index.js diff --git a/server/middlewares/renderMiddleware/renderMiddleware.js b/server/render/renderMiddleware.js similarity index 93% rename from server/middlewares/renderMiddleware/renderMiddleware.js rename to server/render/renderMiddleware.js index 46c63ff..0dc64a1 100644 --- a/server/middlewares/renderMiddleware/renderMiddleware.js +++ b/server/render/renderMiddleware.js @@ -6,8 +6,8 @@ import { renderToString } from 'react-dom/server'; import { StaticRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { ServerStyleSheet } from 'styled-components'; -import createStore from '../../../client/store/createStore'; -import routes from '../../../client/routes'; +import createStore from '../../client/store/createStore'; +import routes from '../../client/routes'; import execComponentWillServerRender from './execComponentWillServerRender'; import html from './html'; From b479b8254908c9cc797ef64f360aefb00cfbd627 Mon Sep 17 00:00:00 2001 From: Lakshya Ranganath Date: Sun, 4 Mar 2018 07:14:22 +0530 Subject: [PATCH 4/5] update dependencies --- package.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 640645a..6ef02dc 100644 --- a/package.json +++ b/package.json @@ -52,18 +52,18 @@ "babel-core": "6.26.0", "babel-eslint": "8.2.2", "babel-jest": "22.4.1", - "babel-loader": "7.1.2", - "babel-plugin-styled-components": "1.5.0", + "babel-loader": "7.1.3", + "babel-plugin-styled-components": "1.5.1", "babel-plugin-transform-react-remove-prop-types": "0.4.13", "babel-preset-env": "1.6.1", "babel-preset-react": "6.24.1", "babel-preset-stage-1": "6.24.1", - "eslint": "4.18.1", + "eslint": "4.18.2", "eslint-config-airbnb": "16.1.0", "eslint-plugin-import": "2.9.0", "eslint-plugin-jsx-a11y": "6.0.3", "eslint-plugin-react": "7.7.0", - "file-loader": "1.1.9", + "file-loader": "1.1.11", "husky": "0.14.3", "jest": "22.4.2", "lint-staged": "7.0.0", @@ -72,10 +72,10 @@ "rimraf": "2.6.2", "style-loader": "0.20.2", "stylelint": "9.1.1", - "stylelint-config-standard": "18.1.0", + "stylelint-config-standard": "18.2.0", "stylelint-config-styled-components": "0.1.1", - "stylelint-processor-styled-components": "1.2.2", - "url-loader": "0.6.2", + "stylelint-processor-styled-components": "1.3.0", + "url-loader": "1.0.1", "webpack-dev-server": "2.11.1" }, "dependencies": { @@ -84,17 +84,17 @@ "clean-webpack-plugin": "0.1.18", "compression": "1.7.2", "connect-slashes": "1.3.1", - "copy-webpack-plugin": "4.4.2", + "copy-webpack-plugin": "4.5.0", "express": "4.16.2", - "helmet": "3.11.0", + "helmet": "3.12.0", "isomorphic-fetch": "2.2.1", "lodash": "4.17.5", - "moment": "2.20.1", + "moment": "2.21.0", "morgan": "1.9.0", - "nock": "9.1.10", + "nock": "9.2.3", "preact": "8.2.7", "preact-compat": "3.18.0", - "prop-types": "15.6.0", + "prop-types": "15.6.1", "query-string": "5.1.0", "raw-loader": "0.5.1", "react": "16.2.0", @@ -112,7 +112,7 @@ "styled-normalize": "4.0.0", "sw-precache-webpack-plugin": "0.11.4", "webpack": "3.6.0", - "webpack-bundle-analyzer": "2.10.1", + "webpack-bundle-analyzer": "2.11.1", "webpack-dashboard": "1.1.1", "webpack-node-externals": "1.6.0" }, From 13a8e152eee2f955c2fde5717379a09b34faaa1b Mon Sep 17 00:00:00 2001 From: Lakshya Ranganath Date: Tue, 27 Mar 2018 10:19:19 +0530 Subject: [PATCH 5/5] merge client, server and config into src --- client/utils/api.js | 38 ------------- config/development.js | 1 - config/index.js | 26 --------- config/local.js | 1 - package.json | 1 - server/render/index.js | 1 - client/index.js => src/client.js | 0 .../default.js => src/config/development.js | 0 src/config/index.js | 13 +++++ src/config/local.js | 3 ++ {config => src/config}/production.js | 0 {config => src/config}/staging.js | 0 {client => src}/core/Flex/Flex.js | 0 {client => src}/core/Flex/index.js | 0 {client => src}/core/NotFound/404.png | Bin {client => src}/core/NotFound/NotFound.js | 0 {client => src}/core/NotFound/index.js | 0 {client => src}/core/Spacer/Spacer.js | 0 {client => src}/core/Spacer/index.js | 0 {client => src}/core/Text/Text.js | 0 {client => src}/core/Text/index.js | 0 {client => src}/core/Wrapper/Wrapper.js | 0 {client => src}/core/Wrapper/index.js | 0 {client => src}/core/Wrapper/styles.js | 0 {client => src}/core/theme/index.js | 0 .../core/theme/injectBaseStyles.js | 0 {client => src}/core/theme/theme.js | 0 {client => src}/home/HomePage/HomePage.js | 0 {client => src}/home/HomePage/index.js | 0 {client => src}/manifest.json | 0 {client => src}/offline/offline.html | 0 {client => src}/offline/offline.js | 0 .../render/execComponentWillServerRender.js | 0 {server => src}/render/fragments.js | 0 {server => src}/render/html.js | 0 .../render/renderExpressMiddleware.js | 4 +- {client => src}/routes.js | 0 server/index.js => src/server.js | 4 +- {client => src}/store/createStore.js | 5 +- {client => src}/store/rootReducer.js | 0 {client => src}/user/UserList/UserList.js | 0 {client => src}/user/UserList/index.js | 0 {client => src}/user/userActionCreators.js | 8 +-- {client => src}/user/userActionTypes.js | 0 {client => src}/user/userHelpers.js | 0 {client => src}/user/userReducer.js | 0 {client => src}/user/userReducer.test.js | 0 {client => src}/utils/performanceMark.js | 0 src/utils/request.js | 51 ++++++++++++++++++ {client => src}/utils/testHelpers.js | 5 +- {client => src}/vendor.js | 0 webpack.client.js | 8 +-- webpack.server.js | 2 +- 53 files changed, 86 insertions(+), 85 deletions(-) delete mode 100644 client/utils/api.js delete mode 100644 config/development.js delete mode 100644 config/index.js delete mode 100644 config/local.js delete mode 100644 server/render/index.js rename client/index.js => src/client.js (100%) rename config/default.js => src/config/development.js (100%) create mode 100644 src/config/index.js create mode 100644 src/config/local.js rename {config => src/config}/production.js (100%) rename {config => src/config}/staging.js (100%) rename {client => src}/core/Flex/Flex.js (100%) rename {client => src}/core/Flex/index.js (100%) rename {client => src}/core/NotFound/404.png (100%) rename {client => src}/core/NotFound/NotFound.js (100%) rename {client => src}/core/NotFound/index.js (100%) rename {client => src}/core/Spacer/Spacer.js (100%) rename {client => src}/core/Spacer/index.js (100%) rename {client => src}/core/Text/Text.js (100%) rename {client => src}/core/Text/index.js (100%) rename {client => src}/core/Wrapper/Wrapper.js (100%) rename {client => src}/core/Wrapper/index.js (100%) rename {client => src}/core/Wrapper/styles.js (100%) rename {client => src}/core/theme/index.js (100%) rename {client => src}/core/theme/injectBaseStyles.js (100%) rename {client => src}/core/theme/theme.js (100%) rename {client => src}/home/HomePage/HomePage.js (100%) rename {client => src}/home/HomePage/index.js (100%) rename {client => src}/manifest.json (100%) rename {client => src}/offline/offline.html (100%) rename {client => src}/offline/offline.js (100%) rename {server => src}/render/execComponentWillServerRender.js (100%) rename {server => src}/render/fragments.js (100%) rename {server => src}/render/html.js (100%) rename server/render/renderMiddleware.js => src/render/renderExpressMiddleware.js (93%) rename {client => src}/routes.js (100%) rename server/index.js => src/server.js (88%) rename {client => src}/store/createStore.js (77%) rename {client => src}/store/rootReducer.js (100%) rename {client => src}/user/UserList/UserList.js (100%) rename {client => src}/user/UserList/index.js (100%) rename {client => src}/user/userActionCreators.js (59%) rename {client => src}/user/userActionTypes.js (100%) rename {client => src}/user/userHelpers.js (100%) rename {client => src}/user/userReducer.js (100%) rename {client => src}/user/userReducer.test.js (100%) rename {client => src}/utils/performanceMark.js (100%) create mode 100644 src/utils/request.js rename {client => src}/utils/testHelpers.js (78%) rename {client => src}/vendor.js (100%) diff --git a/client/utils/api.js b/client/utils/api.js deleted file mode 100644 index 0e97431..0000000 --- a/client/utils/api.js +++ /dev/null @@ -1,38 +0,0 @@ -import fetch from 'isomorphic-fetch'; -import queryString from 'query-string'; -import config from '../../config'; - -const fireRequest = async (method, url, data) => { - const fullUrl = `${config.apiUrl}${url}`; - const options = { - method, - body: JSON.stringify(data), - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - }; - - const response = await fetch(fullUrl, options); - const json = await response.json(); - return response.ok ? json : Promise.reject(json); -}; - -export default { - get(url, query) { - const qs = queryString.stringify(query, { arrayFormat: 'index' }); - return fireRequest('GET', `${url}?${qs}`); - }, - - post(url, data) { - return fireRequest('POST', url, data); - }, - - put(url, data) { - return fireRequest('PUT', url, data); - }, - - delete(url) { - return fireRequest('DELETE', url); - }, -}; diff --git a/config/development.js b/config/development.js deleted file mode 100644 index ff8b4c5..0000000 --- a/config/development.js +++ /dev/null @@ -1 +0,0 @@ -export default {}; diff --git a/config/index.js b/config/index.js deleted file mode 100644 index 9deee9a..0000000 --- a/config/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import defaultConfig from './default'; -import localConfig from './local'; -import developmentConfig from './development'; -import stagingConfig from './staging'; -import productionConfig from './production'; - -const config = { - local: { - ...defaultConfig, - ...localConfig, - }, - development: { - ...defaultConfig, - ...developmentConfig, - }, - staging: { - ...defaultConfig, - ...stagingConfig, - }, - production: { - ...defaultConfig, - ...productionConfig, - }, -}; - -export default config[__PWA_ENV__]; diff --git a/config/local.js b/config/local.js deleted file mode 100644 index ff8b4c5..0000000 --- a/config/local.js +++ /dev/null @@ -1 +0,0 @@ -export default {}; diff --git a/package.json b/package.json index 6ef02dc..65ad0cb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "boilerplate", "jest" ], - "main": "server/index.js", "scripts": { "start": "npm run local", "stop": "pm2 delete pm2.json", diff --git a/server/render/index.js b/server/render/index.js deleted file mode 100644 index 287319f..0000000 --- a/server/render/index.js +++ /dev/null @@ -1 +0,0 @@ -export default from './renderMiddleware'; diff --git a/client/index.js b/src/client.js similarity index 100% rename from client/index.js rename to src/client.js diff --git a/config/default.js b/src/config/development.js similarity index 100% rename from config/default.js rename to src/config/development.js diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 0000000..7bce68f --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,13 @@ +import local from './local'; +import development from './development'; +import staging from './staging'; +import production from './production'; + +const config = { + local, + development, + staging, + production, +}; + +export default config[__PWA_ENV__]; diff --git a/src/config/local.js b/src/config/local.js new file mode 100644 index 0000000..f81cd65 --- /dev/null +++ b/src/config/local.js @@ -0,0 +1,3 @@ +export default { + apiUrl: 'https://randomuser.me', +}; diff --git a/config/production.js b/src/config/production.js similarity index 100% rename from config/production.js rename to src/config/production.js diff --git a/config/staging.js b/src/config/staging.js similarity index 100% rename from config/staging.js rename to src/config/staging.js diff --git a/client/core/Flex/Flex.js b/src/core/Flex/Flex.js similarity index 100% rename from client/core/Flex/Flex.js rename to src/core/Flex/Flex.js diff --git a/client/core/Flex/index.js b/src/core/Flex/index.js similarity index 100% rename from client/core/Flex/index.js rename to src/core/Flex/index.js diff --git a/client/core/NotFound/404.png b/src/core/NotFound/404.png similarity index 100% rename from client/core/NotFound/404.png rename to src/core/NotFound/404.png diff --git a/client/core/NotFound/NotFound.js b/src/core/NotFound/NotFound.js similarity index 100% rename from client/core/NotFound/NotFound.js rename to src/core/NotFound/NotFound.js diff --git a/client/core/NotFound/index.js b/src/core/NotFound/index.js similarity index 100% rename from client/core/NotFound/index.js rename to src/core/NotFound/index.js diff --git a/client/core/Spacer/Spacer.js b/src/core/Spacer/Spacer.js similarity index 100% rename from client/core/Spacer/Spacer.js rename to src/core/Spacer/Spacer.js diff --git a/client/core/Spacer/index.js b/src/core/Spacer/index.js similarity index 100% rename from client/core/Spacer/index.js rename to src/core/Spacer/index.js diff --git a/client/core/Text/Text.js b/src/core/Text/Text.js similarity index 100% rename from client/core/Text/Text.js rename to src/core/Text/Text.js diff --git a/client/core/Text/index.js b/src/core/Text/index.js similarity index 100% rename from client/core/Text/index.js rename to src/core/Text/index.js diff --git a/client/core/Wrapper/Wrapper.js b/src/core/Wrapper/Wrapper.js similarity index 100% rename from client/core/Wrapper/Wrapper.js rename to src/core/Wrapper/Wrapper.js diff --git a/client/core/Wrapper/index.js b/src/core/Wrapper/index.js similarity index 100% rename from client/core/Wrapper/index.js rename to src/core/Wrapper/index.js diff --git a/client/core/Wrapper/styles.js b/src/core/Wrapper/styles.js similarity index 100% rename from client/core/Wrapper/styles.js rename to src/core/Wrapper/styles.js diff --git a/client/core/theme/index.js b/src/core/theme/index.js similarity index 100% rename from client/core/theme/index.js rename to src/core/theme/index.js diff --git a/client/core/theme/injectBaseStyles.js b/src/core/theme/injectBaseStyles.js similarity index 100% rename from client/core/theme/injectBaseStyles.js rename to src/core/theme/injectBaseStyles.js diff --git a/client/core/theme/theme.js b/src/core/theme/theme.js similarity index 100% rename from client/core/theme/theme.js rename to src/core/theme/theme.js diff --git a/client/home/HomePage/HomePage.js b/src/home/HomePage/HomePage.js similarity index 100% rename from client/home/HomePage/HomePage.js rename to src/home/HomePage/HomePage.js diff --git a/client/home/HomePage/index.js b/src/home/HomePage/index.js similarity index 100% rename from client/home/HomePage/index.js rename to src/home/HomePage/index.js diff --git a/client/manifest.json b/src/manifest.json similarity index 100% rename from client/manifest.json rename to src/manifest.json diff --git a/client/offline/offline.html b/src/offline/offline.html similarity index 100% rename from client/offline/offline.html rename to src/offline/offline.html diff --git a/client/offline/offline.js b/src/offline/offline.js similarity index 100% rename from client/offline/offline.js rename to src/offline/offline.js diff --git a/server/render/execComponentWillServerRender.js b/src/render/execComponentWillServerRender.js similarity index 100% rename from server/render/execComponentWillServerRender.js rename to src/render/execComponentWillServerRender.js diff --git a/server/render/fragments.js b/src/render/fragments.js similarity index 100% rename from server/render/fragments.js rename to src/render/fragments.js diff --git a/server/render/html.js b/src/render/html.js similarity index 100% rename from server/render/html.js rename to src/render/html.js diff --git a/server/render/renderMiddleware.js b/src/render/renderExpressMiddleware.js similarity index 93% rename from server/render/renderMiddleware.js rename to src/render/renderExpressMiddleware.js index 0dc64a1..7d56277 100644 --- a/server/render/renderMiddleware.js +++ b/src/render/renderExpressMiddleware.js @@ -6,8 +6,8 @@ import { renderToString } from 'react-dom/server'; import { StaticRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { ServerStyleSheet } from 'styled-components'; -import createStore from '../../client/store/createStore'; -import routes from '../../client/routes'; +import createStore from '../store/createStore'; +import routes from '../routes'; import execComponentWillServerRender from './execComponentWillServerRender'; import html from './html'; diff --git a/client/routes.js b/src/routes.js similarity index 100% rename from client/routes.js rename to src/routes.js diff --git a/server/index.js b/src/server.js similarity index 88% rename from server/index.js rename to src/server.js index b61bf7d..d730b81 100644 --- a/server/index.js +++ b/src/server.js @@ -6,7 +6,7 @@ import compression from 'compression'; import morgan from 'morgan'; import slashes from 'connect-slashes'; import Loadable from 'react-loadable'; -import renderMiddleware from './render/renderMiddleware'; +import renderExpressMiddleware from './render/renderExpressMiddleware'; const { PORT } = process.env; @@ -18,7 +18,7 @@ app.use('/serviceWorker.js', express.static('build/client/serviceWorker.js')); app.use('/manifest.json', express.static('build/client/manifest.json')); app.use('/build/client', express.static('build/client')); app.use(slashes(true)); -app.use(renderMiddleware); +app.use(renderExpressMiddleware); Loadable.preloadAll().then(() => { app.listen(PORT, () => { diff --git a/client/store/createStore.js b/src/store/createStore.js similarity index 77% rename from client/store/createStore.js rename to src/store/createStore.js index c6d4f07..8210b69 100644 --- a/client/store/createStore.js +++ b/src/store/createStore.js @@ -1,11 +1,12 @@ import { createStore, compose, applyMiddleware } from 'redux'; import reduxThunk from 'redux-thunk'; import { middleware as reduxPack } from 'redux-pack'; -import api from '../utils/api'; +import { makeRequest } from '../utils/request'; +import config from '../config'; import rootReducer from './rootReducer'; const middlewares = [ - reduxThunk.withExtraArgument({ api }), + reduxThunk.withExtraArgument({ request: makeRequest(config.apiUrl) }), reduxPack, ].filter(Boolean); diff --git a/client/store/rootReducer.js b/src/store/rootReducer.js similarity index 100% rename from client/store/rootReducer.js rename to src/store/rootReducer.js diff --git a/client/user/UserList/UserList.js b/src/user/UserList/UserList.js similarity index 100% rename from client/user/UserList/UserList.js rename to src/user/UserList/UserList.js diff --git a/client/user/UserList/index.js b/src/user/UserList/index.js similarity index 100% rename from client/user/UserList/index.js rename to src/user/UserList/index.js diff --git a/client/user/userActionCreators.js b/src/user/userActionCreators.js similarity index 59% rename from client/user/userActionCreators.js rename to src/user/userActionCreators.js index c08d00c..36b24b0 100644 --- a/client/user/userActionCreators.js +++ b/src/user/userActionCreators.js @@ -1,18 +1,18 @@ import * as userActionTypes from './userActionTypes'; -export const getAll = () => (dispatch, getState, { api }) => +export const getAll = () => (dispatch, getState, { request }) => dispatch({ type: userActionTypes.GET_ALL, - promise: api.get('/api', { + promise: request.get('/api', { results: 3, inc: 'name,location,picture', }), }); -export const getOne = () => (dispatch, getState, { api }) => +export const getOne = () => (dispatch, getState, { request }) => dispatch({ type: userActionTypes.GET_ONE, - promise: api.get('/api', { + promise: request.get('/api', { results: 1, inc: 'name,location,picture', }), diff --git a/client/user/userActionTypes.js b/src/user/userActionTypes.js similarity index 100% rename from client/user/userActionTypes.js rename to src/user/userActionTypes.js diff --git a/client/user/userHelpers.js b/src/user/userHelpers.js similarity index 100% rename from client/user/userHelpers.js rename to src/user/userHelpers.js diff --git a/client/user/userReducer.js b/src/user/userReducer.js similarity index 100% rename from client/user/userReducer.js rename to src/user/userReducer.js diff --git a/client/user/userReducer.test.js b/src/user/userReducer.test.js similarity index 100% rename from client/user/userReducer.test.js rename to src/user/userReducer.test.js diff --git a/client/utils/performanceMark.js b/src/utils/performanceMark.js similarity index 100% rename from client/utils/performanceMark.js rename to src/utils/performanceMark.js diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..02ede95 --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,51 @@ +/* eslint-disable no-param-reassign */ +import fetch from 'isomorphic-fetch'; +import queryString from 'query-string'; + +const request = async (url, data, opts) => { + let fullUrl = url; + + if (opts.method === 'GET' && data) { + const query = queryString.stringify(data, { arrayFormat: 'index' }); + fullUrl = `${url}?${query}`; + } + + const options = { + method: opts.method, + body: opts.method !== 'GET' ? JSON.stringify(data) : null, + credentials: opts.credentials || 'same-origin', + headers: opts.headers || { + 'Content-Type': 'application/json', + }, + }; + + const response = await fetch(fullUrl, options); + const json = await response.json(); + return response.ok ? json : Promise.reject(json); +}; + +export const makeRequest = (baseUrl, options = {}) => ({ + raw: request, + + get(url, data, opts = options) { + opts.method = 'GET'; + return request(`${baseUrl}${url}`, data, opts); + }, + + post(url, data, opts = options) { + opts.method = 'POST'; + return request(`${baseUrl}${url}`, data, opts); + }, + + put(url, data, opts = options) { + opts.method = 'PUT'; + return request(`${baseUrl}${url}`, data, opts); + }, + + delete(url, data, opts = options) { + opts.method = 'DELETE'; + return request(url, data, opts); + }, +}); + +export default request; diff --git a/client/utils/testHelpers.js b/src/utils/testHelpers.js similarity index 78% rename from client/utils/testHelpers.js rename to src/utils/testHelpers.js index 4dfffa0..9b954b8 100644 --- a/client/utils/testHelpers.js +++ b/src/utils/testHelpers.js @@ -1,7 +1,8 @@ import configureMockStore from 'redux-mock-store'; import reduxThunk from 'redux-thunk'; import { middleware as reduxPack, KEY } from 'redux-pack'; -import api from './api'; +import config from '../config'; +import { makeRequest } from './request'; export const makeReduxPackAction = (lifecycle, { type, payload, meta = {} }) => ({ type, @@ -21,6 +22,6 @@ export const removeReduxPackTransaction = (action) => ({ }); export const mockStore = configureMockStore([ - reduxThunk.withExtraArgument({ api }), + reduxThunk.withExtraArgument({ request: makeRequest(config.apiUrl) }), reduxPack, ]); diff --git a/client/vendor.js b/src/vendor.js similarity index 100% rename from client/vendor.js rename to src/vendor.js diff --git a/webpack.client.js b/webpack.client.js index 54f2e4d..d76e3f0 100644 --- a/webpack.client.js +++ b/webpack.client.js @@ -16,8 +16,8 @@ module.exports = { cache: !isProd, entry: { - main: './client/index.js', - vendor: './client/vendor.js', + main: './src/client.js', + vendor: './src/vendor.js', }, output: { @@ -95,8 +95,8 @@ module.exports = { }, }), new CopyWebpackPlugin([ - { from: './client/manifest.json' }, - { from: './client/offline', to: 'offline/[name].00000001.[ext]' }, + { from: './src/manifest.json' }, + { from: './src/offline', to: 'offline/[name].00000001.[ext]' }, ], { copyUnmodified: true }), new SWPrecacheWebpackPlugin({ cacheId: 'pwa', diff --git a/webpack.server.js b/webpack.server.js index d35933c..c066694 100644 --- a/webpack.server.js +++ b/webpack.server.js @@ -9,7 +9,7 @@ const __PWA_PUBLIC_PATH__ = process.env.PWA_PUBLIC_PATH; const isProd = process.env.NODE_ENV === 'production'; module.exports = { - entry: './server/index.js', + entry: './src/server.js', target: 'node',