UNPKG

react-ssr

Version:

A simplified solution to React server side rendering.

346 lines (273 loc) 12.8 kB
'use strict'; 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;