Skip to content

Commit

Permalink
separate SSR render steps (#890)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip authored Aug 3, 2018
1 parent 9a9cff7 commit c0a947d
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 32 deletions.
104 changes: 74 additions & 30 deletions packages/electrode-redux-router-engine/lib/redux-router-engine.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

/* eslint-disable max-statements, max-params, prefer-spread, global-require, complexity */
/* eslint-disable max-statements, prefer-spread, global-require, complexity */

const Path = require("path");
const assert = require("assert");
Expand Down Expand Up @@ -66,32 +66,52 @@ class ReduxRouterEngine {
this._routesComponent = renderRoutes(this._routes);
}

async render(req, options = {}) {
startMatch(req, options = {}) {
const location = options.location || req.url || Url.parse(req.path);

try {
const match = this._matchRoute(req, this._routes, location);
options = Object.assign({}, options, { req, location });

if (match.length === 0) {
return {
status: 404,
message: `${pkg.name}: Path ${location.path} not found`
};
}
options.match = this._matchRoute(req, this._routes, location);

const methods = match[0].methods || "get";
return options;
}

if (methods.toLowerCase().indexOf(req.method.toLowerCase()) < 0) {
throw new Error(`${pkg.name}: ${location.path} doesn't allow request method ${req.method}`);
}
checkMatch(options) {
const location = options.location;
const match = options.match;

if (match.length === 0) {
return {
status: 404,
message: `${pkg.name}: Path ${location.path} not found`
};
}

const methods = match[0].methods || "get";

if (methods.toLowerCase().indexOf(options.req.method.toLowerCase()) < 0) {
throw new Error(
`${pkg.name}: ${location.path} doesn't allow request method ${options.req.method}`
);
}

return undefined;
}

return await this._handleRender(req, location, match, options);
async render(req, options) {
try {
options = this.startMatch(req, options);
const earlyOut = this.checkMatch(options);
if (earlyOut) return earlyOut;
await this.prepReduxStore(options);

return await this._handleRender(options);
} catch (err) {
this.options.logError.call(this, req, err);
return {
status: err.status || 500, // eslint-disable-line
message: err.message,
path: err.path || location.path,
path: err.path || options.location.path,
_err: err
};
}
Expand All @@ -102,20 +122,31 @@ class ReduxRouterEngine {
return matchRoutes(routes, location.pathname);
}

async _handleRender(req, location, match, options) {
const withIds = options.withIds !== undefined ? options.withIds : this.options.withIds;
const stringifyPreloadedState =
options.stringifyPreloadedState || this.options.stringifyPreloadedState;
async prepReduxStore(options) {
options.withIds = options.withIds !== undefined ? options.withIds : this.options.withIds;

const inits = [];

const match = options.match;

for (let ri = 1; ri < match.length; ri++) {
const route = match[ri].route;
const init = this._getRouteInit(route);
if (init) inits.push(init({ req, location, match, route, inits }));
if (init) {
inits.push(
init({
req: options.req,
location: options.location,
match: options.match,
route,
inits
})
);
}
}

let awaited = false;

const awaitInits = async () => {
if (awaited) return;
awaited = true;
Expand All @@ -126,18 +157,24 @@ class ReduxRouterEngine {

let topInit = this._getRouteInit(match[0].route);
if (topInit) {
topInit = topInit({ req, location, match, route: match[0].route, inits, awaitInits });
topInit = topInit({
req: options.req,
location: options.location,
match,
route: match[0].route,
inits,
awaitInits
});
}

if (topInit.then) {
await awaitInits();
topInit = await topInit;
}

let store;
if (topInit.store) {
// top route provided a ready made store, just use it
store = topInit.store;
options.store = topInit.store;
} else {
if (!awaited) await awaitInits();

Expand Down Expand Up @@ -170,25 +207,32 @@ class ReduxRouterEngine {
reducer = x => x;
}

store = createStore(reducer, initialState);
options.store = createStore(reducer, initialState);
}

const routeContext = {};
return options.store;
}

async _handleRender(options) {
const routeContext = (options.routeContext = {});
const stringifyPreloadedState =
options.stringifyPreloadedState || this.options.stringifyPreloadedState;

let html = this._renderToString(options);

let html = this._renderToString(req, location, store, routeContext, match, withIds);
if (html.then !== undefined) {
// a Promise?
html = await html;
}

if (this.options.componentRedirect && routeContext.action === "REPLACE") {
return