From 77ba21ee5a93277f1277d1824a86360911f4f216 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 19 Feb 2016 12:35:47 -0500 Subject: [PATCH 1/5] no data fetching --- package.json | 2 +- src/client.js | 80 ++---------- src/routes/Editor/index.js | 2 + src/routes/Post/containers/PostPage.js | 15 ++- src/routes/Post/index.js | 1 + src/routes/PostList/index.js | 6 +- src/server/server.js | 165 +++++++------------------ 7 files changed, 75 insertions(+), 196 deletions(-) diff --git a/package.json b/package.json index b49a5b7a4..ae55f3886 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "react": "^0.14.7", "react-dom": "^0.14.7", "react-redux": "^4.4.0", - "react-router": "^1.0.3", + "react-router": "^2.0.0", "redial": "^0.4.1", "redux": "^3.3.1", "redux-thunk": "^1.0.3", diff --git a/src/client.js b/src/client.js index 5e4dfffea..1d083903b 100644 --- a/src/client.js +++ b/src/client.js @@ -1,87 +1,29 @@ -// import 'babel-polyfill'; -import { trigger } from 'redial'; - +import 'babel-polyfill'; import React from 'react'; import { render } from 'react-dom'; -import createBrowserHistory from 'history/lib/createBrowserHistory'; -import useQueries from 'history/lib/useQueries'; -import { Router, match } from 'react-router'; -import { createStore, applyMiddleware, compose } from 'redux'; +import { Router, match, browserHistory } from 'react-router'; import { Provider } from 'react-redux'; -import thunk from 'redux-thunk'; -import {callAPIMiddleware} from './middleware/callAPIMiddleware'; import { StyleSheet } from 'aphrodite'; // Your app's reducer and routes: -import createReducer from './reducers'; import createRoutes from './routes/root'; import { configureStore } from './store'; -// Render the app client-side to a given container element: -// Your server rendered response needs to expose the state of the store, e.g. -// const initialState = window.INITIAL_STATE || {}; // Set up Redux (note: this API requires redux@>=3.1.0): const store = configureStore(initialState); - -const { dispatch } = store; - -// Set up history for router and listen for changes: -const history = useQueries(createBrowserHistory)(); - -// const gen = genRoutes(store); +const { pathname, search, hash } = window.location; +const location = `${pathname}${search}${hash}`; const routes = createRoutes(store); -console.log('Client: ' + routes); - -const callProvidedHooks = (location, callback) => { - - // Match routes based on location object: - match({ routes, location }, (routerError, redirectLocation, renderProps) => { - // Get array of route components: - const components = renderProps.routes.map(route => route.component); - - // Define locals to be provided to all lifecycle hooks: - const locals = { - path: renderProps.location.pathname, - query: renderProps.location.query, - params: renderProps.params, - // Allow lifecycle hooks to dispatch Redux actions: - dispatch, - }; - - // Don't fetch data for initial route, server has already done the work: - const invoke = window.INITIAL_STATE ? Promise.resolve.bind(Promise) : trigger; - - invoke('fetch', components, locals) // Fetch mandatory data dependencies for 2nd route change onwards - .then(() => trigger('defer', components, locals)) // Fetch deferred, client-only data dependencies - .then(() => trigger('done', components, locals)) // Finally, trigger 'done' lifecycle hooks - .then(callback); - }); -}; - -// Handle initial rendering -history.listen((location) => { - if (window.INITIAL_STATE) { - // Delete global data so that subsequent data fetches can occur: - callProvidedHooks(location, () => { - delete window.INITIAL_STATE; - delete window.renderedClassNames; - }); - } -}); - -// -// // // // Handle following rendering -history.listenBefore(callProvidedHooks); StyleSheet.rehydrate(window.renderedClassNames); // Render app with Redux and router context to container element: -render(( - - - -), document.getElementById('root')); +match({ routes, location }, () => { + render(( + + + + ), document.getElementById('root')); +}); diff --git a/src/routes/Editor/index.js b/src/routes/Editor/index.js index d620e2f58..ea2d2549e 100644 --- a/src/routes/Editor/index.js +++ b/src/routes/Editor/index.js @@ -1,3 +1,5 @@ +if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require); + export default { path: 'edit', getComponent(location, cb) { diff --git a/src/routes/Post/containers/PostPage.js b/src/routes/Post/containers/PostPage.js index 39bd75da5..bae15b920 100644 --- a/src/routes/Post/containers/PostPage.js +++ b/src/routes/Post/containers/PostPage.js @@ -6,10 +6,6 @@ import PrimaryText from '../../../components/PrimaryText'; import { StyleSheet, css } from 'aphrodite'; import { layout } from '../../../constants'; -const hooks = { - defer: ({ dispatch, params: { slug } }) => dispatch(loadPost(slug)), -}; - const PostPage = ({ title, content }) => { return (
@@ -19,6 +15,15 @@ const PostPage = ({ title, content }) => { ); }; +PostPage.need = [(params) => { + return loadPost.bind(null, params.slug)(); +}, +]; + +PostPage.contextTypes = { + router: React.PropTypes.object, +}; + function mapStateToProps(state) { return { title: state.currentPost.data.title, @@ -35,4 +40,4 @@ const styles = StyleSheet.create({ }, }); -export default provideHooks(hooks)(connect(mapStateToProps)(PostPage)); +export default connect(mapStateToProps)(PostPage); diff --git a/src/routes/Post/index.js b/src/routes/Post/index.js index 6bf181f64..170d57bc3 100644 --- a/src/routes/Post/index.js +++ b/src/routes/Post/index.js @@ -1,4 +1,5 @@ import { injectAsyncReducer } from '../../store'; +if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require); export default function createRoutes(store) { return { diff --git a/src/routes/PostList/index.js b/src/routes/PostList/index.js index c3949b463..7ead541fd 100644 --- a/src/routes/PostList/index.js +++ b/src/routes/PostList/index.js @@ -11,9 +11,7 @@ const PostListPage = ({ posts }) => {posts.map((post, i) => )}
; -const hooks = { - fetch: ({ dispatch }) => dispatch(loadPosts()), -}; +PostListPage.need = [loadPosts]; function mapStateToProps(state) { return { @@ -30,4 +28,4 @@ const styles = StyleSheet.create({ }, }); -export default provideHooks(hooks)(connect(mapStateToProps)(PostListPage)); +export default connect(mapStateToProps)(PostListPage); diff --git a/src/server/server.js b/src/server/server.js index c28750281..11303f2f9 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -11,10 +11,8 @@ import morgan from 'morgan'; import { trigger } from 'redial'; import React from 'react'; -import { renderToString } from 'react-dom/server'; -import createMemoryHistory from 'history/lib/createMemoryHistory'; -import useQueries from 'history/lib/useQueries'; -import { Router, RoutingContext, match } from 'react-router'; +import ReactDOM from 'react-dom/server'; +import { createMemoryHistory, RouterContext, match } from 'react-router'; import { createStore, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; @@ -43,70 +41,6 @@ server.use(morgan('dev')); server.use('/api/v0/posts', require('./api/posts')); server.use('/api/v0/post', require('./api/post')); -const redial = (path) => new Promise((resolve, reject) => { - // Set up Redux (note: this API requires redux@>=3.1.0): - const store = configureStore(); - - // console.log(oldRoutes); - const routes = createRoutes(store); - - // console.log(routes); - - const { dispatch } = store; - - // Set up history for router: - const history = useQueries(createMemoryHistory)(); - const location = history.createLocation(path); - - // Match routes based on location object: - match({ routes, history, location }, (routerError, redirectLocation, renderProps) => { - // Get array of route components: - // console.log(routes); - // console.log(gen); - try { - const components = renderProps.routes.map(route => route.component); - console.log(renderProps); - - // console.log(components); - - // Define locals to be provided to all fetcher functions: - const locals = { - path: renderProps.location.pathname, - query: renderProps.location.query, - params: renderProps.params, - - // Allow fetcher functions to dispatch Redux actions: - dispatch, - }; - - // Wait for async actions to complete, then render: - trigger('fetch', components, locals) - .then(() => { - const state = store.getState(); - console.log('STATE:'); - console.log(state); - const html = renderToString( - - - - ); - - console.log('HTML'); - console.log(html); - - resolve({ state, html }); - }) - .catch(e => { - console.log(e); - reject(e); - }); - } catch (e) { - console.log(e); - } - - }); -}); - if (isDeveloping) { const compiler = webpack(config); const middleware = webpackMiddleware(compiler, { @@ -131,11 +65,18 @@ if (isDeveloping) { server.use('/build/static', express.static(__dirname + '../../../build/static')); } -server.get('*', (req, res) => { - redial(req.path).then(result => { - console.log(result); - }).catch(e => console.log(e)); - res.status(200).send(` +const fetchComponentDataBeforeRender = (dispatch, components, params) => { + const needs = components.reduce((prev, current) => { + return (current.need || []) + .concat((current.WrappedComponent ? current.WrappedComponent.need : []) || []) + .concat(prev); + }, []); + const promises = needs.map(need => dispatch(need())); + return Promise.all(promises); +}; + +const renderFullPage = (data, initialState) => { + return ` @@ -144,56 +85,46 @@ server.get('*', (req, res) => { React Starter + -
+
${data.html}
+ + - `); -}); + + `; +}; -// UNCOMMENT to DISABLE ISOMORPHISM -// server.get('/', (req, res) => { -// res.status(200).send(` -// -// -// -// -// -// React Starter -// -// -// -// -//
-// -// -// -// -// `); -// }); -// -// res.status(200).send(` -// -// -// -// -// -// React Starter -// -// -// -// -// -//
${result.html}
-// -// -// -// -// -// -// `); +server.get('*', (req, res) => { + const store = configureStore(); + const routes = createRoutes(store); + const history = createMemoryHistory(req.path); + + match({ routes, history }, (err, redirectLocation, renderProps) => { + if (err) { + console.error(err); + return res.status(500).send('Internal server error'); + } + + if (!renderProps) + return res.status(404).send('Not found'); + + const InitialView = ( + + + + ); + + const data = StyleSheetServer.renderStatic( + () => ReactDOM.renderToString(InitialView) + ); + const initialState = store.getState(); + res.status(200).send(renderFullPage(data, initialState)); + }); +}); server.listen(port, '0.0.0.0', function onStart(err) { if (err) { From ac9cdd2c4c46941dfe11033e558a7b4fcbde83f4 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 19 Feb 2016 12:48:22 -0500 Subject: [PATCH 2/5] add polyfills and cwm6 data fetching --- src/routes/Post/index.js | 2 +- src/routes/PostList/index.js | 35 ++++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/routes/Post/index.js b/src/routes/Post/index.js index 170d57bc3..6e3e13a34 100644 --- a/src/routes/Post/index.js +++ b/src/routes/Post/index.js @@ -1,5 +1,5 @@ -import { injectAsyncReducer } from '../../store'; if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require); +import { injectAsyncReducer } from '../../store'; export default function createRoutes(store) { return { diff --git a/src/routes/PostList/index.js b/src/routes/PostList/index.js index 7ead541fd..35a63bb4c 100644 --- a/src/routes/PostList/index.js +++ b/src/routes/PostList/index.js @@ -1,17 +1,30 @@ -import { provideHooks } from 'redial'; import React, { PropTypes } from 'react'; -import {loadPosts} from './actions'; +import { bindActionCreators } from 'redux'; +import { loadPosts } from './actions'; import { connect } from 'react-redux'; import PostListItem from './components/PostListItem'; import { StyleSheet, css } from 'aphrodite'; -const PostListPage = ({ posts }) => -
-

PostListPage

- {posts.map((post, i) => )} -
; +class PostListPage extends React.Component { + constructor(props) { + super(props); -PostListPage.need = [loadPosts]; + } + + componentWillMount() { + this.props.loadPosts(); + } + + render() { + const { posts } = this.props; + return ( +
+

PostListPage

+ {posts.map((post, i) => )} +
+ ); + } +} function mapStateToProps(state) { return { @@ -19,6 +32,10 @@ function mapStateToProps(state) { }; } +function mapDispatchToProps(dispatch) { + return bindActionCreators({ loadPosts }, dispatch); +} + const styles = StyleSheet.create({ blue: { color: "blue", @@ -28,4 +45,4 @@ const styles = StyleSheet.create({ }, }); -export default connect(mapStateToProps)(PostListPage); +export default connect(mapStateToProps, mapDispatchToProps)(PostListPage); From bc70408df2c8ad792299eb18836a1f739ae60343 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 19 Feb 2016 13:00:28 -0500 Subject: [PATCH 3/5] It works! --- src/client.js | 43 ++++++++++++++++++++++++-- src/routes/Post/containers/PostPage.js | 15 +++------ src/routes/PostList/index.js | 37 +++++++--------------- src/server/server.js | 39 ++++++++++++++++------- 4 files changed, 84 insertions(+), 50 deletions(-) diff --git a/src/client.js b/src/client.js index 1d083903b..52489cc8a 100644 --- a/src/client.js +++ b/src/client.js @@ -1,7 +1,9 @@ import 'babel-polyfill'; +import { trigger } from 'redial'; + import React from 'react'; import { render } from 'react-dom'; -import { Router, match, browserHistory } from 'react-router'; +import { Router, match, browserHistory as history } from 'react-router'; import { Provider } from 'react-redux'; import { StyleSheet } from 'aphrodite'; @@ -13,17 +15,54 @@ const initialState = window.INITIAL_STATE || {}; // Set up Redux (note: this API requires redux@>=3.1.0): const store = configureStore(initialState); +const { dispatch } = store; const { pathname, search, hash } = window.location; const location = `${pathname}${search}${hash}`; const routes = createRoutes(store); StyleSheet.rehydrate(window.renderedClassNames); +const callProvidedHooks = (location, callback) => { + // Match routes based on location object: + match({ routes, location }, (routerError, redirectLocation, renderProps) => { + // Get array of route components: + const components = renderProps.routes.map(route => route.component); + + // Define locals to be provided to all lifecycle hooks: + const locals = { + path: renderProps.location.pathname, + query: renderProps.location.query, + params: renderProps.params, + + // Allow lifecycle hooks to dispatch Redux actions: + dispatch, + }; + const invoke = window.INITIAL_STATE ? Promise.resolve.bind(Promise) : trigger; + + invoke('fetch', components, locals) // Fetch mandatory data dependencies for 2nd route change onwards + .then(() => trigger('defer', components, locals)) // Fetch deferred, client-only data dependencies + .then(() => trigger('done', components, locals)) // Finally, trigger 'done' lifecycle hooks + .then(callback); + }); +}; + +// Handle initial rendering +history.listen((location) => { + if (window.INITIAL_STATE) { + // Delete global data so that subsequent data fetches can occur: + callProvidedHooks(location, () => delete window.INITIAL_STATE); + } +}); + +// Handle following rendering +history.listenBefore(callProvidedHooks); + // Render app with Redux and router context to container element: + match({ routes, location }, () => { render(( - + ), document.getElementById('root')); }); diff --git a/src/routes/Post/containers/PostPage.js b/src/routes/Post/containers/PostPage.js index bae15b920..39bd75da5 100644 --- a/src/routes/Post/containers/PostPage.js +++ b/src/routes/Post/containers/PostPage.js @@ -6,6 +6,10 @@ import PrimaryText from '../../../components/PrimaryText'; import { StyleSheet, css } from 'aphrodite'; import { layout } from '../../../constants'; +const hooks = { + defer: ({ dispatch, params: { slug } }) => dispatch(loadPost(slug)), +}; + const PostPage = ({ title, content }) => { return (
@@ -15,15 +19,6 @@ const PostPage = ({ title, content }) => { ); }; -PostPage.need = [(params) => { - return loadPost.bind(null, params.slug)(); -}, -]; - -PostPage.contextTypes = { - router: React.PropTypes.object, -}; - function mapStateToProps(state) { return { title: state.currentPost.data.title, @@ -40,4 +35,4 @@ const styles = StyleSheet.create({ }, }); -export default connect(mapStateToProps)(PostPage); +export default provideHooks(hooks)(connect(mapStateToProps)(PostPage)); diff --git a/src/routes/PostList/index.js b/src/routes/PostList/index.js index 35a63bb4c..c3949b463 100644 --- a/src/routes/PostList/index.js +++ b/src/routes/PostList/index.js @@ -1,30 +1,19 @@ +import { provideHooks } from 'redial'; import React, { PropTypes } from 'react'; -import { bindActionCreators } from 'redux'; -import { loadPosts } from './actions'; +import {loadPosts} from './actions'; import { connect } from 'react-redux'; import PostListItem from './components/PostListItem'; import { StyleSheet, css } from 'aphrodite'; -class PostListPage extends React.Component { - constructor(props) { - super(props); +const PostListPage = ({ posts }) => +
+

PostListPage

+ {posts.map((post, i) => )} +
; - } - - componentWillMount() { - this.props.loadPosts(); - } - - render() { - const { posts } = this.props; - return ( -
-

PostListPage

- {posts.map((post, i) => )} -
- ); - } -} +const hooks = { + fetch: ({ dispatch }) => dispatch(loadPosts()), +}; function mapStateToProps(state) { return { @@ -32,10 +21,6 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return bindActionCreators({ loadPosts }, dispatch); -} - const styles = StyleSheet.create({ blue: { color: "blue", @@ -45,4 +30,4 @@ const styles = StyleSheet.create({ }, }); -export default connect(mapStateToProps, mapDispatchToProps)(PostListPage); +export default provideHooks(hooks)(connect(mapStateToProps)(PostListPage)); diff --git a/src/server/server.js b/src/server/server.js index 11303f2f9..2caf5852e 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -102,7 +102,7 @@ server.get('*', (req, res) => { const store = configureStore(); const routes = createRoutes(store); const history = createMemoryHistory(req.path); - + const { dispatch } = store; match({ routes, history }, (err, redirectLocation, renderProps) => { if (err) { console.error(err); @@ -112,17 +112,32 @@ server.get('*', (req, res) => { if (!renderProps) return res.status(404).send('Not found'); - const InitialView = ( - - - - ); - - const data = StyleSheetServer.renderStatic( - () => ReactDOM.renderToString(InitialView) - ); - const initialState = store.getState(); - res.status(200).send(renderFullPage(data, initialState)); + const { components } = renderProps; + + // Define locals to be provided to all lifecycle hooks: + const locals = { + path: renderProps.location.pathname, + query: renderProps.location.query, + params: renderProps.params, + + // Allow lifecycle hooks to dispatch Redux actions: + dispatch, + }; + + trigger('fetch', components, locals) + .then(() => { + const initialState = store.getState(); + const InitialView = ( + + + + ); + const data = StyleSheetServer.renderStatic( + () => ReactDOM.renderToString(InitialView) + ); + res.status(200).send(renderFullPage(data, initialState)); + }) + .catch(e => console.log(e)); }); }); From e2e4aa91c34f9476e2918126821b261d2b3347f2 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 19 Feb 2016 13:03:58 -0500 Subject: [PATCH 4/5] clean things up --- README.md | 48 +++++++++++++- src/routes-old.js | 14 ---- src/server/server-copy.js | 135 -------------------------------------- 3 files changed, 46 insertions(+), 151 deletions(-) delete mode 100644 src/routes-old.js delete mode 100644 src/server/server-copy.js diff --git a/README.md b/README.md index e3d5b3c32..f57faabf5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,46 @@ -# react-production-starter -Isomorphic React Starter with Redux, React Router, Redial, CSS Modules, Express, Webpack. +# React Production Starter + +While there are tons of react/redux/react-router boilerplates on Github, this one is built to scale: it comes with multiple entry points (a.k.a. codesplitting), asynchronous "lazy" react-router routes, and asynchronous redux reducers. These features allow you to separate your app into as many mini-apps as you'd like to without affecting initial page load. + +Out of the box, the app is client-side only. However, refactoring to a universal (isomorphic) app is relatively straightforward. The only thing you'll have to decide on is which data-resolving logic you want. See `server-iso.js`, `client-iso.js`, `routes-iso.js` for an example that uses Redial (formerly react-fetchr). + + +Folder Structure: +```bash +. +├── /build/ # The folder for compiled output +├── /node_modules/ # 3rd-party libraries and utilities +├── /src/ # The source code of the application +│ ├── /components/ # Global React components +│ ├── /reducers/ # Synchronous reducers (reducers required for initial page load) +│ ├── /middleware/ # Redux middleware (comes with callAPIMiddileware) +│ ├── /routes/ # React-router routes +│ | ├── /App/ # Main wrapper component +│ | ├── /PostList/ # PostList page +│ | ├── /Editor/ # Editor (async stub) +│ | ├── /Post/ # Post (async) +│ | | ├── /components/ # Post components (async) +│ | | ├── actions.js # Post actions (async) +│ | | ├── reducer.js # Post reducer (async) +│ | | ├── index.js # Post Route (async) +│ | ├── /root.js # React-router root +│ ├── /client.js # Client-side entry point +│ ├── /store.js # Redux store configuration +│ ├── /constants.js # Global constants (Action types, Aphrodite layout/style vars) +│ ├── /(routes-iso.js) # (Synchronous vanilla react-router routes, in case you want to use those) +│ ├── /server/ # Server +│ | ├── /api/ # API endpoints +│ | | ├── /posts.js # Posts endpoint +│ | | ├── /post.js # Single Post endpoint +│ | ├── /fakeDB.js # Database Stub +│ | ├── /server.js # Express app +│ | ├── /server-iso.js # Express app with isomorphic rendering (must use routes-iso.js) +│ | ├── /index.js # Server entry point (with babel-register) +├── /test/ # Mocha tests (e.g. xxx_spec.js) +├── /coverage/ # Code coverage data +│── .env # **Server-side configuration variables** +│── Procfile # Heroku startup commands +│── package.json # The list of 3rd party libraries and utilities and NPM scripts +│── webpack.config.dev.js # Webpack Development Configuration File +└── webpack.config.prod.js # Webpack Production Configuration File +``` diff --git a/src/routes-old.js b/src/routes-old.js deleted file mode 100644 index dd2d870c3..000000000 --- a/src/routes-old.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { Router, Route, IndexRoute } from 'react-router'; -import App from './routes/App'; -import PostPage from './routes/Post'; -import PostListPage from './routes/PostList'; -import EditorPage from './routes/Editor/components/Editor'; - -export default ( - - - - - -); diff --git a/src/server/server-copy.js b/src/server/server-copy.js deleted file mode 100644 index c418154d9..000000000 --- a/src/server/server-copy.js +++ /dev/null @@ -1,135 +0,0 @@ -import path from 'path'; -import express from 'express'; -import webpack from 'webpack'; -import webpackMiddleware from 'webpack-dev-middleware'; -import webpackHotMiddleware from 'webpack-hot-middleware'; -import config from '../webpack.config.dev'; -import cookieParser from 'cookie-parser'; -import helmet from 'helmet'; -import bodyParser from 'body-parser'; -import morgan from 'morgan'; - -import { trigger } from 'redial'; -import React from 'react'; -import { renderToString } from 'react-dom/server'; -import createMemoryHistory from 'history/lib/createMemoryHistory'; -import useQueries from 'history/lib/useQueries'; -import { RoutingContext, match } from 'react-router'; -import { createStore, applyMiddleware } from 'redux'; -import { Provider } from 'react-redux'; -import thunk from 'redux-thunk'; - -// Your app's reducer and routes: -import reducer from './reducers'; -import routes from './routes'; - -const isDeveloping = process.env.NODE_ENV !== 'production'; -const port = isDeveloping ? 5000 : process.env.PORT; -const server = global.server = express(); - -server.disable('x-powered-by'); -server.set('port', port); -server.use(helmet()); -server.use(bodyParser.urlencoded({ extended: false })); -server.use(bodyParser.json()); -server.use(cookieParser()); -server.use(morgan('dev')); - -// server.use('/api/v0/posts', require('./api/extract')); -// server.use('/api/v0/premail', require('./api/premail')); - -// Render the app server-side for a given path: -const redial = (path) => new Promise((resolve, reject) => { - // Set up Redux (note: this API requires redux@>=3.1.0): - const store = createStore( - reducer, - applyMiddleware(thunk) - ); - const { dispatch } = store; - - // Set up history for router: - const history = useQueries(createMemoryHistory)(); - const location = history.createLocation(path); - - // Match routes based on location object: - match({ routes, location }, (routerError, redirectLocation, renderProps) => { - // Get array of route components: - const components = renderProps.routes.map(route => route.component); - - // Define locals to be provided to all fetcher functions: - const locals = { - path: renderProps.location.pathname, - query: renderProps.location.query, - params: renderProps.params, - - // Allow fetcher functions to dispatch Redux actions: - dispatch, - }; - - // Wait for async actions to complete, then render: - trigger('fetch', components, locals) - .then(() => { - const data = store.getState(); - const html = renderToString( - - - - ); - - resolve({ data, html }); - }) - .catch(reject); - }); -}); - -if (isDeveloping) { - const compiler = webpack(config); - const middleware = webpackMiddleware(compiler, { - publicPath: config.output.publicPath, - contentBase: 'src', - stats: { - colors: true, - hash: false, - timings: true, - chunks: false, - chunkModules: false, - modules: false, - }, - }); - server.use(middleware); - server.use(webpackHotMiddleware(compiler, { - log: console.log, - })); -} else { - server.use('/static', express.static(__dirname + '/dist')); -} - -server.get('/', (req, res) => { - redial(req.path).then(result => { - res.status(200).send(` - - - - - - React Email Workflow - - - - -
${result.html}
- - - - - `); - }); -}); - -server.listen(port, '0.0.0.0', function onStart(err) { - if (err) { - console.log(err); - } - - console.info('==> 🌎 Listening on port %s. Open up http://0.0.0.0:%s/ in your browser.', port, port); -}); From 2afa26a59ca89e7f921d3f4cdd107cb3aff15795 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Fri, 19 Feb 2016 13:16:19 -0500 Subject: [PATCH 5/5] try to resolve merge conflicts --- Procfile | 1 + app.json | 11 +++++++++++ package.json | 13 ++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 Procfile create mode 100644 app.json diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..9fd1e96ad --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: npm run build && npm run start:prod diff --git a/app.json b/app.json new file mode 100644 index 000000000..e515c66de --- /dev/null +++ b/app.json @@ -0,0 +1,11 @@ +{ + "name": "react-production-starter", + "scripts": { + }, + "env": { + + }, + "addons": [ + + ] +} diff --git a/package.json b/package.json index ae55f3886..095c0c675 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,10 @@ "test": "mocha --compilers js:babel-core/register --recursive", "test:watch": "npm test -- --watch", "start": "NODE_ENV=development node ./src/server", - "start:prod": "NODE_ENV=production node ./src/server", - "build:client": "npm run clean && webpack -d --inline --config ./webpack.config.prod.js", - "clean": "rm -rf build" + "start:prod": "npm run build && NODE_ENV=production node ./src/server", + "build": "npm run clean && webpack -p --inline --config ./webpack.config.prod.js", + "clean": "rm -rf build", + "postinstall": "npm run build" }, "repository": { "type": "git", @@ -39,14 +40,12 @@ "morgan": "^1.6.1", "react": "^0.14.7", "react-dom": "^0.14.7", + "react-helmet": "^2.3.1", "react-redux": "^4.4.0", "react-router": "^2.0.0", - "redial": "^0.4.1", "redux": "^3.3.1", "redux-thunk": "^1.0.3", - "superagent": "^1.7.2" - }, - "devDependencies": { + "superagent": "^1.7.2", "autoprefixer": "^6.3.1", "babel-core": "^6.5.1", "babel-loader": "^6.2.2",