Merge pull request #1543 from comus/patch-1

separate client side and server side routing
This commit is contained in:
Xavier Cazalot 2017-01-26 09:32:10 +01:00 committed by GitHub
commit fcdf8505f2
9 changed files with 143 additions and 78 deletions

View file

@ -9,12 +9,6 @@ const ReactRouterSSR = {
clientOptions = {};
}
let history = browserHistory;
if(typeof clientOptions.historyHook === 'function') {
history = clientOptions.historyHook(history);
}
Meteor.startup(function() {
const rootElementName = clientOptions.rootElement || 'react-app';
const rootElementType = clientOptions.rootElementType || 'div';
@ -47,6 +41,12 @@ const ReactRouterSSR = {
});
}
let history = browserHistory;
if(typeof clientOptions.historyHook === 'function') {
history = clientOptions.historyHook(history);
}
let app = (
<Router
history={history}

View file

@ -79,7 +79,7 @@ ReactRouterSSR.Run = function(routes, clientOptions, serverOptions) {
let history = createMemoryHistory(req.url);
if (typeof serverOptions.historyHook === 'function') {
history = serverOptions.historyHook(history);
history = serverOptions.historyHook(req, res, history);
}
ReactRouterMatch({ history, routes, location: req.url }, Meteor.bindEnvironment((err, redirectLocation, renderProps) => {
@ -194,6 +194,7 @@ function generateSSRData(clientOptions, serverOptions, req, res, renderProps) {
global.__STYLE_COLLECTOR_MODULES__ = [];
global.__STYLE_COLLECTOR__ = '';
req.css = '';
renderProps = {
...renderProps,
@ -203,9 +204,9 @@ function generateSSRData(clientOptions, serverOptions, req, res, renderProps) {
// fetchComponentData(serverOptions, renderProps);
let app = <RouterContext {...renderProps} />;
if (typeof clientOptions.wrapperHook === 'function') {
if (typeof serverOptions.wrapperHook === 'function') {
const loginToken = req.cookies['meteor_login_token'];
app = clientOptions.wrapperHook(app, loginToken);
app = serverOptions.wrapperHook(req, res, app, loginToken);
}
if (serverOptions.preRender) {
@ -221,7 +222,7 @@ function generateSSRData(clientOptions, serverOptions, req, res, renderProps) {
css = global.__STYLE_COLLECTOR__;
if (typeof serverOptions.dehydrateHook === 'function') {
const data = serverOptions.dehydrateHook();
const data = serverOptions.dehydrateHook(req, res);
InjectData.pushData(res, 'dehydrated-initial-data', JSON.stringify(data));
}
@ -229,6 +230,8 @@ function generateSSRData(clientOptions, serverOptions, req, res, renderProps) {
serverOptions.postRender(req, res);
}
css = css + req.css;
// I'm pretty sure this could be avoided in a more elegant way?
const context = FastRender.frContext.get();
const data = context.getData();

View file

@ -4,7 +4,7 @@ HoC that provides access to flash messages stored in Redux state and actions to
*/
import { Actions, addAction, addReducer } from 'meteor/nova:lib';
import { getActions, addAction, addReducer } from 'meteor/nova:lib';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
@ -43,8 +43,8 @@ addReducer({
messages: (state = [], action) => {
// default values
const flashType = typeof action.flashType === 'undefined' ? 'error' : action.flashType;
const currentMsg = typeof action.i === 'undefined' ? {} : state[action.i];
const currentMsg = typeof action.i === 'undefined' ? {} : state[action.i];
switch(action.type) {
case 'FLASH':
return [
@ -57,7 +57,7 @@ addReducer({
{ ...currentMsg, seen: true },
...state.slice(action.i + 1),
];
case 'CLEAR':
case 'CLEAR':
return [
...state.slice(0, action.i),
{ ...currentMsg, show: false },
@ -72,7 +72,7 @@ addReducer({
});
const mapStateToProps = state => ({ messages: state.messages, });
const mapDispatchToProps = dispatch => bindActionCreators(Actions.messages, dispatch);
const mapDispatchToProps = dispatch => bindActionCreators(getActions().messages, dispatch);
const withMessages = component => connect(mapStateToProps, mapDispatchToProps)(component);

View file

@ -1,5 +1,5 @@
// import and re-export
export { Components, registerComponent, replaceComponent, getRawComponent, getComponent, copyHoCs, populateComponentsApp, createCollection, Callbacks, addCallback, removeCallback, runCallbacks, runCallbacksAsync, GraphQLSchema, Routes, addRoute, getRoute, populateRoutesApp, Utils, getSetting, Strings, addStrings, configureStore, Actions, addAction, Reducers, addReducer, Middleware, addMiddleware, Headtags } from 'meteor/nova:lib';
export { Components, registerComponent, replaceComponent, getRawComponent, getComponent, copyHoCs, populateComponentsApp, createCollection, Callbacks, addCallback, removeCallback, runCallbacks, runCallbacksAsync, GraphQLSchema, Routes, addRoute, getRoute, populateRoutesApp, Utils, getSetting, Strings, addStrings, configureStore, getActions, addAction, getReducers, addReducer, getMiddleware, addMiddleware, Headtags } from 'meteor/nova:lib';
export { default as App } from "./components/App.jsx";
export { default as Layout } from "./components/Layout.jsx";

View file

@ -18,7 +18,7 @@ export { Routes, addRoute, getRoute, populateRoutesApp } from './routes.js';
export { Utils } from './utils.js';
export { getSetting } from './settings.js';
export { Strings, addStrings } from './strings.js';
export { configureStore, Actions, addAction, Reducers, addReducer, Middleware, addMiddleware } from './redux.js';
export { configureStore, getActions, addAction, getReducers, addReducer, getMiddleware, addMiddleware } from './redux.js';
export { Headtags } from './headtags.js';
export default Telescope;

View file

@ -1,7 +1,7 @@
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
export const configureStore = (reducers, initialState = {}, middleware) => createStore(
// reducers
// reducers
combineReducers(reducers),
// initial state
initialState,
@ -12,25 +12,28 @@ export const configureStore = (reducers, initialState = {}, middleware) => creat
)
);
export let Actions = {};
let actions = {};
export const addAction = addedAction => {
Actions = {...Actions, ...addedAction};
return Actions;
};
actions = {...actions, ...addedAction};
export let Reducers = {};
return actions;
};
export const getActions = () => actions;
let reducers = {};
export const addReducer = addedReducer => {
Reducers = {...Reducers, ...addedReducer};
return Reducers;
};
reducers = {...reducers, ...addedReducer};
export let Middleware = [];
return reducers;
};
export const getReducers = () => reducers;
let middleware = [];
export const addMiddleware = middlewareOrMiddlewareArray => {
const addedMiddleware = Array.isArray(middlewareOrMiddlewareArray) ? middlewareOrMiddlewareArray : [middlewareOrMiddlewareArray];
Middleware = [...Middleware, ...addedMiddleware];
return Middleware;
middleware = [...middleware, ...addedMiddleware];
return middleware;
};
export const getMiddleware = () => middleware;

View file

@ -1,23 +1,22 @@
import React from 'react';
import { ReactRouterSSR } from 'meteor/reactrouter:react-router-ssr';
import Helmet from 'react-helmet';
import Cookie from 'react-cookie';
import ReactDOM from 'react-dom';
import ApolloClient from 'apollo-client';
import { getDataFromTree, ApolloProvider } from 'react-apollo';
import { meteorClientConfig } from 'meteor/nova:apollo';
import { Components, populateComponentsApp, Actions, runCallbacks, addRoute, Routes, populateRoutesApp, configureStore, addReducer, addMiddleware } from 'meteor/nova:core';
import { ApolloProvider } from 'react-apollo';
import { applyRouterMiddleware } from 'react-router';
import { useScroll } from 'react-router-scroll';
import { ReactRouterSSR } from 'meteor/reactrouter:react-router-ssr';
import { meteorClientConfig } from 'meteor/nova:apollo';
import { Components, populateComponentsApp, getActions, runCallbacks, addRoute, Routes, populateRoutesApp, configureStore, addReducer, addMiddleware } from 'meteor/nova:core';
Meteor.startup(function initNovaRoutesAndApollo() {
// note: route defined here because it "shouldn't be removable"
addRoute({name:"app.notfound", path:"*", componentName: 'Error404'});
// uncomment for debug
// console.log('// --> starting routing');
// init the application components and routes, including components & routes from 3rd-party packages
populateComponentsApp();
populateRoutesApp();
@ -34,64 +33,45 @@ Meteor.startup(function initNovaRoutesAndApollo() {
};
/*
Hooks client side and server side definition
Hooks client side definition
*/
let history;
let initialState;
let store;
let client;
// Use history hook to get a reference to the history object
const historyHook = newHistory => history = newHistory;
const clientOptions = {
historyHook,
rehydrateHook: state => {
// console.log('rehydrated state', state);
initialState = state
},
wrapperHook(app, loginToken) {
// console.log('wrapper hook initial state', initialState);
// configure apollo
client = new ApolloClient(meteorClientConfig({cookieLoginToken: loginToken}));
client = new ApolloClient(meteorClientConfig());
const reducers = addReducer({apollo: client.reducer()});
const middleware = addMiddleware(client.middleware());
// configure the redux store
store = configureStore(reducers, initialState, middleware);
return <ApolloProvider store={store} client={client}>{app}</ApolloProvider>
},
historyHook(newHistory) {
// Use history hook to get a reference to the history object
history = newHistory
return history;
},
props: {
onUpdate: () => {
runCallbacks('router.onUpdate');
// clear all previous messages
store.dispatch(Actions.messages.clearSeen());
store.dispatch(getActions().messages.clearSeen());
},
render: applyRouterMiddleware(useScroll())
},
wrapperHook(app, loginToken) {
// console.log('wrapper hook initial state', initialState);
return <ApolloProvider store={store} client={client}>{app}</ApolloProvider>
},
};
const serverOptions = {
historyHook,
htmlHook: (html) => {
const head = Helmet.rewind();
return html.replace('<head>', '<head>'+ head.title + head.meta + head.link);
},
preRender: (req, res, app) => {
Cookie.plugToRequest(req, res);
//console.log('preRender hook', app);
// console.log(req.cookies);
return Promise.await(getDataFromTree(app));
},
dehydrateHook: () => {
// console.log(client.store.getState());
return client.store.getState();
},
// fetchDataHook: (components) => components,
};
ReactRouterSSR.Run(AppRoutes, clientOptions, serverOptions);
ReactRouterSSR.Run(AppRoutes, clientOptions, {});
});

View file

@ -0,0 +1,78 @@
import React from 'react';
import Helmet from 'react-helmet';
import Cookie from 'react-cookie';
import ApolloClient from 'apollo-client';
import { getDataFromTree, ApolloProvider } from 'react-apollo';
import { ReactRouterSSR } from 'meteor/reactrouter:react-router-ssr';
import { meteorClientConfig } from 'meteor/nova:apollo';
import { Components, populateComponentsApp, addRoute, Routes, populateRoutesApp, configureStore, getReducers, getMiddleware } from 'meteor/nova:core';
Meteor.startup(function initNovaRoutesAndApollo() {
// note: route defined here because it "shouldn't be removable"
addRoute({name:"app.notfound", path:"*", componentName: 'Error404'});
// uncomment for debug
// console.log('// --> starting routing');
// init the application components and routes, including components & routes from 3rd-party packages
populateComponentsApp();
populateRoutesApp();
const indexRoute = _.filter(Routes, route => route.path === '/')[0];
const childRoutes = _.reject(Routes, route => route.path === '/');
delete indexRoute.path; // delete the '/' path to avoid warning
const AppRoutes = {
path: '/',
component: Components.App,
indexRoute,
childRoutes,
};
/*
Hooks server side definition
*/
const serverOptions = {
historyHook(req, res, newHistory) {
// Use history hook to get a reference to the history object
req.history = newHistory
return req.history;
},
wrapperHook(req, res, app, loginToken) {
// console.log('wrapper hook');
// configure apollo
req.apolloClient = new ApolloClient(meteorClientConfig({ cookieLoginToken: loginToken }));
const reducers = { ...getReducers(), apollo: req.apolloClient.reducer() };
const middleware = [...getMiddleware(), req.apolloClient.middleware()];
// configure the redux store
req.store = configureStore(reducers, {}, middleware);
return <ApolloProvider store={req.store} client={req.apolloClient}>{app}</ApolloProvider>
},
preRender(req, res, app) {
Cookie.plugToRequest(req, res);
//console.log('preRender hook', app);
// console.log(req.cookies);
return Promise.await(getDataFromTree(app));
},
dehydrateHook(req, res) {
// console.log(client.store.getState());
return req.apolloClient.store.getState();
},
postRender(req, res) {
// console.log('postrender hook');
},
htmlHook(html) {
const head = Helmet.rewind();
return html.replace('<head>', '<head>'+ head.title + head.meta + head.link);
},
};
ReactRouterSSR.Run(AppRoutes, {}, serverOptions);
});

View file

@ -15,6 +15,7 @@ Package.onUse(function (api) {
'nova:apollo@1.0.0',
]);
api.mainModule('lib/routing.jsx', ['client', 'server']);
api.mainModule('lib/routing-server.jsx', 'server');
api.mainModule('lib/routing-client.jsx', 'client');
});