create-react-app-ssr
Advanced tools
Comparing version 2.0.10 to 2.0.11
@@ -7,2 +7,3 @@ | ||
invalidateSSRCache: require('./lib/memcached').invalidateCache, | ||
webpackReactLoadable: require('./lib/webpack-react-loadable').webpackReactLoadable, | ||
} |
@@ -12,2 +12,6 @@ 'use strict'; | ||
var _path = require('path'); | ||
var _path2 = _interopRequireDefault(_path); | ||
var _express = require('express'); | ||
@@ -35,13 +39,49 @@ | ||
var defaultSettings = { | ||
// do you want to run ssr or not? | ||
// @TODO: this might be a function so to make this decision dynamic | ||
enabled: String(process.env.REACT_APP_SSR_ENABLED || 'yes'), | ||
cwd: String(process.env.REACT_APP_SSR_CWD || process.cwd()), | ||
root: String(process.env.REACT_APP_SSR_ROOT || 'src'), | ||
build: String(process.env.REACT_APP_SSR_BUILD || 'build'), | ||
// comma serparated list of routes that will skip ssr | ||
blacklist: String(process.env.REACT_APP_SSR_BLACKLIST || ''), | ||
// react app's folders (absolute path) | ||
// we need to know where to locate your client's source files | ||
// and your ready-to-ship assets. | ||
source: String(process.env.REACT_APP_SSR_ROOT || _path2.default.join(process.cwd(), 'src')), | ||
build: String(process.env.REACT_APP_SSR_BUILD || _path2.default.join(process.cwd(), 'build')), | ||
// @TODO: find out a better way to handle callback urls client-to-server | ||
// durig ssr. | ||
port: String(process.env.REACT_APP_SSR_PORT || '8080'), | ||
// strips away all the javascript tags. | ||
// basically renders a pure HTML static web page. | ||
disableJs: String(process.env.REACT_APP_SSR_DISABLE_JS || 'no'), | ||
blacklist: String(process.env.REACT_APP_SSR_BLACKLIST || ''), | ||
// maximum ssr execution time, useful to reduce server bottlenecks in case | ||
// apis or similar stuff get into the way | ||
renderTimeout: Number(process.env.REACT_APP_SSR_RENDER_TIMEOUT || 5000), | ||
// maximum number of ssr re-rendering waiting for asychronous code to finish | ||
// this is useful if the client enters in loops based on data loading that | ||
// does not preserve the results in between renderings | ||
// @TODO: document this with examples | ||
renderLimit: Number(process.env.REACT_APP_SSR_RENDER_LIMIT || 10), | ||
// CRA settings to handle images that might have beed embedded into the | ||
// shipped bundles, this is used by the "register-hook" | ||
embedExt: String(process.env.REACT_APP_SSR_EMBED_EXT || '.png,.jpg,jpeg,.gif,svg'), | ||
embedLimit: Number(process.env.REACT_APP_SSR_EMBED_LIMIT || 10000) | ||
embedLimit: Number(process.env.REACT_APP_SSR_EMBED_LIMIT || 10000), | ||
// allow the in-memory cache system to kick in. | ||
useCache: String(process.env.REACT_APP_SSR_USE_CACHE || 'yes'), | ||
// by default the SSR middleware will send out the produced HTML to the client | ||
// but this could be avoided if the app will implement some custom | ||
// post-processing logic. | ||
sendHtml: String(process.env.REACT_APP_SSR_SEND_HTML || 'yes'), | ||
// the HTML that is produced after a rendering is appended to the "req" | ||
// context variable for custom post processing. | ||
dataVar: String(process.env.REACT_APP_SSR_DATA_VAR || 'ssrOutput') | ||
}; | ||
@@ -48,0 +88,0 @@ |
@@ -141,7 +141,20 @@ "use strict"; | ||
case 0: | ||
if (!settings.serializer) { | ||
_context2.next = 4; | ||
break; | ||
} | ||
_context2.next = 3; | ||
return settings.serializer.remove(toBeRemoved); | ||
case 3: | ||
return _context2.abrupt("return"); | ||
case 4: | ||
_iteratorNormalCompletion = true; | ||
_didIteratorError = false; | ||
_iteratorError = undefined; | ||
_context2.prev = 3; | ||
_context2.prev = 7; | ||
for (_iterator = toBeRemoved[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
@@ -153,14 +166,14 @@ key = _step.value; | ||
} | ||
_context2.next = 11; | ||
_context2.next = 15; | ||
break; | ||
case 7: | ||
_context2.prev = 7; | ||
_context2.t0 = _context2["catch"](3); | ||
case 11: | ||
_context2.prev = 11; | ||
_context2.t0 = _context2["catch"](7); | ||
_didIteratorError = true; | ||
_iteratorError = _context2.t0; | ||
case 11: | ||
_context2.prev = 11; | ||
_context2.prev = 12; | ||
case 15: | ||
_context2.prev = 15; | ||
_context2.prev = 16; | ||
@@ -171,7 +184,7 @@ if (!_iteratorNormalCompletion && _iterator.return) { | ||
case 14: | ||
_context2.prev = 14; | ||
case 18: | ||
_context2.prev = 18; | ||
if (!_didIteratorError) { | ||
_context2.next = 17; | ||
_context2.next = 21; | ||
break; | ||
@@ -182,9 +195,9 @@ } | ||
case 17: | ||
return _context2.finish(14); | ||
case 21: | ||
return _context2.finish(18); | ||
case 18: | ||
return _context2.finish(11); | ||
case 22: | ||
return _context2.finish(15); | ||
case 19: | ||
case 23: | ||
case "end": | ||
@@ -194,3 +207,3 @@ return _context2.stop(); | ||
} | ||
}, _callee2, undefined, [[3, 7, 11, 19], [12,, 14, 18]]); | ||
}, _callee2, undefined, [[7, 11, 15, 23], [16,, 18, 22]]); | ||
})); | ||
@@ -205,3 +218,3 @@ | ||
var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(req, res, html) { | ||
var key; | ||
var key, cacheObject; | ||
return _regenerator2.default.wrap(function _callee3$(_context3) { | ||
@@ -228,10 +241,26 @@ while (1) { | ||
key = _context3.sent; | ||
cache[key.value] = { | ||
cacheObject = { | ||
ctm: Date.now(), | ||
exp: settings.getCacheExpiry(req, res), | ||
cnt: settings.onCacheWrites(req, res, html) | ||
// custom serializer implementation | ||
}; | ||
case 8: | ||
if (!settings.serializer) { | ||
_context3.next = 12; | ||
break; | ||
} | ||
_context3.next = 11; | ||
return settings.serializer.set(key, cacheObject); | ||
case 11: | ||
return _context3.abrupt("return"); | ||
case 12: | ||
cache[key.value] = cacheObject; | ||
case 13: | ||
case "end": | ||
@@ -251,3 +280,3 @@ return _context3.stop(); | ||
var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(req, res) { | ||
var key; | ||
var key, cacheObject; | ||
return _regenerator2.default.wrap(function _callee4$(_context4) { | ||
@@ -263,25 +292,44 @@ while (1) { | ||
if (cache[key.value]) { | ||
_context4.next = 5; | ||
if (!settings.serializer) { | ||
_context4.next = 9; | ||
break; | ||
} | ||
_context4.next = 6; | ||
return settings.serializer.get(key); | ||
case 6: | ||
_context4.t0 = _context4.sent; | ||
_context4.next = 10; | ||
break; | ||
case 9: | ||
_context4.t0 = cache[key.value]; | ||
case 10: | ||
cacheObject = _context4.t0; | ||
if (cacheObject) { | ||
_context4.next = 13; | ||
break; | ||
} | ||
return _context4.abrupt("return"); | ||
case 5: | ||
if (!(cache[key.value].exp < Date.now())) { | ||
_context4.next = 9; | ||
case 13: | ||
if (!(cacheObject.exp < Date.now())) { | ||
_context4.next = 17; | ||
break; | ||
} | ||
_context4.next = 8; | ||
_context4.next = 16; | ||
return remove(key.value); | ||
case 8: | ||
case 16: | ||
return _context4.abrupt("return"); | ||
case 9: | ||
return _context4.abrupt("return", settings.onCacheRead(req, res, cache[key.value])); | ||
case 17: | ||
return _context4.abrupt("return", settings.onCacheRead(req, res, cacheObject)); | ||
case 10: | ||
case 18: | ||
case "end": | ||
@@ -288,0 +336,0 @@ return _context4.stop(); |
@@ -14,3 +14,4 @@ 'use strict'; | ||
var readSourceFile = function readSourceFile(filePath) { | ||
var encoding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'utf8'; | ||
var parseJson = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | ||
var encoding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'utf8'; | ||
return new Promise(function (resolve, reject) { | ||
@@ -27,4 +28,16 @@ if (readSourceFile.cache[filePath]) { | ||
} | ||
readSourceFile.cache[filePath] = data; | ||
resolve(data); | ||
// optional json parse applied to the file | ||
var content = data; | ||
if (parseJson) { | ||
try { | ||
content = JSON.parse(data); | ||
} catch (err) { | ||
reject(err); | ||
return; | ||
} | ||
} | ||
readSourceFile.cache[filePath] = content; | ||
resolve(content); | ||
}); | ||
@@ -31,0 +44,0 @@ } catch (err) { |
@@ -26,4 +26,6 @@ 'use strict'; | ||
var _webpack = require('react-loadable/webpack'); | ||
var _mapModulesToBundles = require('./map-modules-to-bundles'); | ||
var _mapModulesToBundles2 = _interopRequireDefault(_mapModulesToBundles); | ||
var _readSourceFile = require('./read-source-file'); | ||
@@ -64,3 +66,3 @@ | ||
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(req, res, next) { | ||
var cachedHTML, filePath, htmlTemplate, initialState, prerender, helmet, jsBundles, cssBundles, bundleStats, html; | ||
var cachedHTML, filePath, htmlTemplate, initialState, prerender, helmet, mappedBundles, html; | ||
return _regenerator2.default.wrap(function _callee$(_context) { | ||
@@ -70,11 +72,16 @@ while (1) { | ||
case 0: | ||
_context.prev = 0; | ||
_context.next = 3; | ||
if (!(settings.useCache === 'yes')) { | ||
_context.next = 13; | ||
break; | ||
} | ||
_context.prev = 1; | ||
_context.next = 4; | ||
return cache.get(req, res); | ||
case 3: | ||
case 4: | ||
cachedHTML = _context.sent; | ||
if (!(typeof cachedHTML === 'string')) { | ||
_context.next = 7; | ||
_context.next = 8; | ||
break; | ||
@@ -86,14 +93,14 @@ } | ||
case 7: | ||
_context.next = 12; | ||
case 8: | ||
_context.next = 13; | ||
break; | ||
case 9: | ||
_context.prev = 9; | ||
_context.t0 = _context['catch'](0); | ||
case 10: | ||
_context.prev = 10; | ||
_context.t0 = _context['catch'](1); | ||
console.log('[ssr] read cache error for "' + req.url + '" - ' + _context.t0.message); | ||
case 12: | ||
_context.prev = 12; | ||
case 13: | ||
_context.prev = 13; | ||
@@ -105,12 +112,12 @@ console.log('[ssr] ' + req.url); | ||
if (staticRender) { | ||
_context.next = 28; | ||
_context.next = 29; | ||
break; | ||
} | ||
_context.prev = 15; | ||
_context.prev = 16; | ||
staticRender = require(_path2.default.join(settings.cwd, settings.root, 'app/ssr')).staticRender; | ||
staticRender = require(_path2.default.join(settings.source, 'app/ssr')).staticRender; | ||
if (staticRender) { | ||
_context.next = 19; | ||
_context.next = 20; | ||
break; | ||
@@ -121,5 +128,5 @@ } | ||
case 19: | ||
case 20: | ||
if (!(typeof staticRender !== 'function')) { | ||
_context.next = 21; | ||
_context.next = 22; | ||
break; | ||
@@ -130,5 +137,5 @@ } | ||
case 21: | ||
case 22: | ||
if (!(staticRender.ssrVersion !== _currentVersion2.default)) { | ||
_context.next = 23; | ||
_context.next = 24; | ||
break; | ||
@@ -139,9 +146,9 @@ } | ||
case 23: | ||
_context.next = 28; | ||
case 24: | ||
_context.next = 29; | ||
break; | ||
case 25: | ||
_context.prev = 25; | ||
_context.t1 = _context['catch'](15); | ||
case 26: | ||
_context.prev = 26; | ||
_context.t1 = _context['catch'](16); | ||
@@ -157,8 +164,8 @@ // fallback on a default static render that will visualize the error | ||
case 28: | ||
case 29: | ||
filePath = _path2.default.resolve(_path2.default.join(settings.build, 'index.html')); | ||
_context.next = 31; | ||
_context.next = 32; | ||
return (0, _readSourceFile2.default)(filePath); | ||
case 31: | ||
case 32: | ||
htmlTemplate = _context.sent; | ||
@@ -178,3 +185,3 @@ initialState = { | ||
}; | ||
_context.next = 35; | ||
_context.next = 36; | ||
return staticRender(req.url, initialState, { | ||
@@ -187,7 +194,7 @@ req: req, res: res, // proxy express handlers | ||
case 35: | ||
case 36: | ||
prerender = _context.sent; | ||
if (!prerender.context.url) { | ||
_context.next = 39; | ||
_context.next = 40; | ||
break; | ||
@@ -199,3 +206,3 @@ } | ||
case 39: | ||
case 40: | ||
@@ -205,31 +212,9 @@ // get SEO headers for the page | ||
// react-loadable -- get rendered chunks, if eny exists | ||
// include dynamically requested bundles (via react-loadable) | ||
jsBundles = []; | ||
cssBundles = []; | ||
_context.next = 43; | ||
return (0, _mapModulesToBundles2.default)(settings, prerender.modules); | ||
console.log(prerender.modules); | ||
try { | ||
bundleStats = require(_path2.default.join(settings.build, 'react-loadable.json')); | ||
jsBundles = (0, _webpack.getBundles)(bundleStats, prerender.modules).filter(function (bundle) { | ||
return bundle.file.indexOf('.js.map') === -1; | ||
}) // remove sourcemaps | ||
.filter(function (bundle) { | ||
return bundle.file.indexOf('.css') === -1; | ||
}) // remove css | ||
.map(function (bundle) { | ||
return '<script type="text/javascript" src="' + bundle.publicPath + '"></script>'; | ||
}); | ||
cssBundles = (0, _webpack.getBundles)(bundleStats, prerender.modules).filter(function (bundle) { | ||
return bundle.file.indexOf('.css') !== -1; | ||
}) // remove css | ||
.filter(function (bundle) { | ||
return bundle.file.indexOf('.map') === -1; | ||
}) // remove maps | ||
.map(function (bundle) { | ||
return '<link href="' + bundle.publicPath + '" rel="stylesheet">'; | ||
}); | ||
} catch (err) {} | ||
case 43: | ||
mappedBundles = _context.sent; | ||
html = (0, _prepHtml2.default)(htmlTemplate, (0, _extends3.default)({}, settings, { | ||
@@ -240,4 +225,4 @@ html: helmet.htmlAttributes.toString(), | ||
state: prerender.initialState, | ||
jsBundles: jsBundles, | ||
cssBundles: cssBundles | ||
jsBundles: mappedBundles.js, | ||
cssBundles: mappedBundles.css | ||
})); | ||
@@ -247,20 +232,44 @@ | ||
res.send(html); | ||
if (settings.sendHtml === 'yes') { | ||
res.send(html); | ||
} | ||
// store a cache version of the rendered html | ||
// try { | ||
// await cache.set(req, res, html) | ||
// } catch (err) { | ||
// console.log(`[ssr] write cache error for "${req.url}" - ${err.message}`) | ||
// } | ||
_context.next = 51; | ||
if (!(settings.useCache === 'yes')) { | ||
_context.next = 55; | ||
break; | ||
} | ||
_context.prev = 47; | ||
_context.next = 50; | ||
return cache.set(req, res, html); | ||
case 50: | ||
_context.next = 55; | ||
break; | ||
case 48: | ||
_context.prev = 48; | ||
_context.t2 = _context['catch'](12); | ||
case 52: | ||
_context.prev = 52; | ||
_context.t2 = _context['catch'](47); | ||
next(_context.t2); | ||
console.log('[ssr] write cache error for "' + req.url + '" - ' + _context.t2.message); | ||
case 51: | ||
case 55: | ||
// decorate the request with the produced html so to allow | ||
// the implementation of custom caching mechanism or post | ||
// rendering filters | ||
req[settings.dataVar] = html; | ||
next(); | ||
_context.next = 62; | ||
break; | ||
case 59: | ||
_context.prev = 59; | ||
_context.t3 = _context['catch'](13); | ||
next(_context.t3); | ||
case 62: | ||
case 'end': | ||
@@ -270,3 +279,3 @@ return _context.stop(); | ||
} | ||
}, _callee, undefined, [[0, 9], [12, 48], [15, 25]]); | ||
}, _callee, undefined, [[1, 10], [13, 59], [16, 26], [47, 52]]); | ||
})); | ||
@@ -273,0 +282,0 @@ |
@@ -23,5 +23,9 @@ 'use strict'; | ||
config.plugins = [].concat((0, _toConsumableArray3.default)(config.plugins), [new _webpack.ReactLoadablePlugin({ | ||
filename: settings.target || './build/react-loadable.json' | ||
})]); | ||
// it's possible to skip the plugin as with CRA there is already a | ||
// "asset-manifest.json" that SSR can use to map dynamically loaded bundles. | ||
if (settings.skipPlugin !== true) { | ||
config.plugins = [].concat((0, _toConsumableArray3.default)(config.plugins), [new _webpack.ReactLoadablePlugin({ | ||
filename: settings.target || './build/react-loadable.json' | ||
})]); | ||
} | ||
@@ -28,0 +32,0 @@ // "chunks:all" generates some unpredictable chunks based on "node_modules" |
{ | ||
"name": "create-react-app-ssr", | ||
"version": "2.0.10", | ||
"version": "2.0.11", | ||
"description": "Server Side Rendering for CRA 2.x (with redux, router, code splitting, ...)", | ||
@@ -9,3 +9,4 @@ "main": "index.js", | ||
"start": "node_modules/babel-cli/bin/babel.js --watch src --out-dir lib", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"book:serve": "node_modules/.bin/gitbook serve", | ||
"book:build": "node_modules/.bin/gitbook build . docs" | ||
}, | ||
@@ -47,2 +48,3 @@ "babel": { | ||
"babel-runtime": "^6.26.0", | ||
"gitbook-cli": "^2.3.2", | ||
"install-peers": "^1.0.3" | ||
@@ -49,0 +51,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
1
20
212247
9
23
1291