react-ssr
Version:
A simplified solution to React server side rendering.
346 lines (273 loc) • 12.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _server = require('react-dom/server');
var _server2 = _interopRequireDefault(_server);
var _reactRouterDom = require('react-router-dom');
var _qs = require('qs');
var _qs2 = _interopRequireDefault(_qs);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
var Q = require('q');
var url = require('url');
var debug = require('debug')('react-ssr:serverRender');
var _require = require('react-router-config'),
matchRoutes = _require.matchRoutes,
renderRoutes = _require.renderRoutes;
var DefaultTemplate = require('./components/DefaultTemplate');
var findAllDataCalls = require('./helpers/findAllDataCalls');
var _require2 = require('./ssrContext'),
SSRProvider = _require2.SSRProvider;
require('regenerator-runtime/runtime.js');
var fetchPageFromCache = function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(redisClient, key) {
var data;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
data = void 0;
_context.prev = 1;
_context.next = 4;
return redisClient.get(key);
case 4:
data = _context.sent;
_context.next = 10;
break;
case 7:
_context.prev = 7;
_context.t0 = _context['catch'](1);
console.warn('Failed to get cached page for ' + key);
case 10:
return _context.abrupt('return', data);
case 11:
case 'end':
return _context.stop();
}
}
}, _callee, undefined, [[1, 7]]);
}));
return function fetchPageFromCache(_x, _x2) {
return _ref.apply(this, arguments);
};
}();
var storePageInCache = function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(redisClient, key, data, cacheExpiry) {
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.prev = 0;
_context2.next = 3;
return redisClient.set(key, data, 'ex', cacheExpiry);
case 3:
_context2.next = 8;
break;
case 5:
_context2.prev = 5;
_context2.t0 = _context2['catch'](0);
console.warn('Failed to set cached page for ' + key);
case 8:
case 'end':
return _context2.stop();
}
}
}, _callee2, undefined, [[0, 5]]);
}));
return function storePageInCache(_x3, _x4, _x5, _x6) {
return _ref2.apply(this, arguments);
};
}();
var serverRender = function () {
var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(_ref3, req, res) {
var _ref3$Html = _ref3.Html,
Html = _ref3$Html === undefined ? DefaultTemplate : _ref3$Html,
_ref3$Providers = _ref3.Providers,
Providers = _ref3$Providers === undefined ? function (_ref5) {
var children = _ref5.children;
return _react2.default.createElement(
_react.Fragment,
null,
children
);
} : _ref3$Providers,
routes = _ref3.routes,
disable = _ref3.disable,
_ref3$ignore = _ref3.ignore,
ignore = _ref3$ignore === undefined ? [] : _ref3$ignore,
_ref3$cache = _ref3.cache,
cache = _ref3$cache === undefined ? {
mode: 'none',
duration: 1800,
redisClient: null,
keyPrefix: '',
keySuffix: '',
ignoreQueryParams: false,
queryParamsToKeep: []
} : _ref3$cache;
var splitUrl, urlWithoutQuery, html, _ref6, redisClient, _ref6$ignoreQueryPara, ignoreQueryParams, _ref6$queryParamsToKe, queryParamsToKeep, extensionRegex, extension, hasRedis, cacheActive, readCache, writeCache, urlForCache, queryParams, params, queryString, uniqueKeySuffix, uniqueKeyPrefix, key, cachedPage, matchedRoutes, lastRoute, parsedUrl, dataCalls, statusCode;
return regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
splitUrl = req.url.split('?');
urlWithoutQuery = splitUrl[0];
if (!(disable || ignore.includes(urlWithoutQuery))) {
_context4.next = 5;
break;
}
html = _server2.default.renderToString(_react2.default.createElement(Html, { expressRequest: req }));
return _context4.abrupt('return', res.send('<!DOCTYPE html>' + html));
case 5:
_ref6 = cache || {}, redisClient = _ref6.redisClient, _ref6$ignoreQueryPara = _ref6.ignoreQueryParams, ignoreQueryParams = _ref6$ignoreQueryPara === undefined ? false : _ref6$ignoreQueryPara, _ref6$queryParamsToKe = _ref6.queryParamsToKeep, queryParamsToKeep = _ref6$queryParamsToKe === undefined ? [] : _ref6$queryParamsToKe;
extensionRegex = /(?:\.([^.]+))?$/;
extension = extensionRegex.exec(urlWithoutQuery)[1];
hasRedis = redisClient && typeof redisClient.exists === 'function' && typeof redisClient.get === 'function';
cacheActive = hasRedis && cache && cache.mode === 'full';
readCache = cacheActive && (req.useCacheForRequest || res.locals.useSsrCacheForRequest || req.readCache);
writeCache = cacheActive && (req.useCacheForRequest || res.locals.useSsrCacheForRequest || req.writeCache);
urlForCache = ignoreQueryParams ? urlWithoutQuery : req.url;
if (!extension) {
_context4.next = 15;
break;
}
return _context4.abrupt('return', res.sendStatus(404));
case 15:
if (!readCache) {
_context4.next = 31;
break;
}
queryParams = splitUrl[1];
if (ignoreQueryParams && queryParamsToKeep.length && queryParams) {
params = _qs2.default.parse(queryParams);
queryString = '';
queryParamsToKeep.forEach(function (paramToKeepInCacheUrl, index) {
var paramValue = params[paramToKeepInCacheUrl];
if (paramValue) {
if (queryString) {
queryString += '&'; // adds & as multiple query params
}
queryString += paramToKeepInCacheUrl + '=' + paramValue;
}
});
if (queryString) {
queryString = '?' + queryString;
}
urlForCache = '' + urlWithoutQuery + queryString;
}
uniqueKeySuffix = res.locals.ssrCacheKeySuffix || cache.keySuffix;
uniqueKeyPrefix = res.locals.ssrCacheKeyPrefix || cache.keyPrefix;
key = '' + uniqueKeyPrefix + urlForCache + uniqueKeySuffix;
_context4.next = 23;
return redisClient.exists(key);
case 23:
if (!_context4.sent) {
_context4.next = 31;
break;
}
_context4.next = 26;
return fetchPageFromCache(redisClient, key);
case 26:
cachedPage = _context4.sent;
if (!cachedPage) {
_context4.next = 31;
break;
}
if (uniqueKeyPrefix) {
res.set('X-Cache-Prefix', uniqueKeyPrefix);
}
if (uniqueKeySuffix) {
res.set('X-Cache-Suffix', uniqueKeySuffix);
}
return _context4.abrupt('return', res.status(200).send(cachedPage));
case 31:
matchedRoutes = matchRoutes(routes, urlWithoutQuery);
lastRoute = matchedRoutes[matchedRoutes.length - 1] || {};
parsedUrl = url.parse(req.url) || {};
dataCalls = findAllDataCalls(matchedRoutes, { req: req, res: res, url: parsedUrl.pathname });
statusCode = lastRoute && lastRoute.route && lastRoute.route.path && lastRoute.route.path.includes('*') ? 404 : 200;
if (!parsedUrl.pathname) {
debug('Parsed URL has no path name.');
}
debug('Routes? ', routes);
Q.allSettled(dataCalls).then(function () {
var _ref7 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(fetchedProps) {
var filteredProps, preventCaching, state, app, wrapper, page, status, _uniqueKeyPrefix, _uniqueKeySuffix, _key;
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
debug('Fetched props... ', fetchedProps);
filteredProps = {};
preventCaching = false;
fetchedProps.forEach(function (props) {
var fetchedObject = props.value;
var keyOfFetchedObject = Object.keys(fetchedObject)[0];
var objectOfFetchedValues = fetchedObject[keyOfFetchedObject];
if (!objectOfFetchedValues._excludeFromHydration) {
filteredProps[keyOfFetchedObject] = objectOfFetchedValues;
}
if (objectOfFetchedValues._preventCaching) preventCaching = true;
});
state = {
_dataFromServerRender: JSON.parse(JSON.stringify(filteredProps))
};
app = _server2.default.renderToString(_react2.default.createElement(
SSRProvider,
{ value: filteredProps },
_react2.default.createElement(
Providers,
null,
_react2.default.createElement(
_reactRouterDom.StaticRouter,
{ location: req.url, context: {} },
renderRoutes(routes)
)
)
));
wrapper = _server2.default.renderToString(_react2.default.createElement(
Html,
{ state: state, expressRequest: req },
app
));
page = '<!DOCTYPE html>' + wrapper;
status = req.status || statusCode;
if (!(!preventCaching && writeCache && status >= 200 && status < 300)) {
_context3.next = 15;
break;
}
_uniqueKeyPrefix = res.locals.ssrCacheKeyPrefix || cache.keyPrefix;
_uniqueKeySuffix = res.locals.ssrCacheKeySuffix || cache.keySuffix;
_key = '' + _uniqueKeyPrefix + urlForCache + _uniqueKeySuffix;
_context3.next = 15;
return storePageInCache(redisClient, _key, page, cache.duration);
case 15:
res.status(status).send(page);
case 16:
case 'end':
return _context3.stop();
}
}
}, _callee3, undefined);
}));
return function (_x10) {
return _ref7.apply(this, arguments);
};
}()).catch(function (err) {
res.status(400).send('400: An error has occurred: ' + err);
});
case 39:
case 'end':
return _context4.stop();
}
}
}, _callee4, undefined);
}));
return function serverRender(_x7, _x8, _x9) {
return _ref4.apply(this, arguments);
};
}();
exports.default = serverRender;