@spartacus/setup
Version:
Includes features that makes Spartacus and it's setup easier and streamlined.
804 lines (787 loc) • 38.8 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('@nguniversal/express-engine/tokens'), require('@spartacus/core')) :
typeof define === 'function' && define.amd ? define('@spartacus/setup/ssr', ['exports', 'fs', '@nguniversal/express-engine/tokens', '@spartacus/core'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.spartacus = global.spartacus || {}, global.spartacus.setup = global.spartacus.setup || {}, global.spartacus.setup.ssr = {}), global.fs, global.expressEngineTokens, global.core));
}(this, (function (exports, fs, tokens, core) { 'use strict';
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () {
return e[k];
}
});
}
});
}
n['default'] = e;
return Object.freeze(n);
}
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b)
if (Object.prototype.hasOwnProperty.call(b, p))
d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function () {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s)
if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s)
if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if (d = decorators[i])
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); };
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
return Reflect.metadata(metadataKey, metadataValue);
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try {
step(generator.next(value));
}
catch (e) {
reject(e);
} }
function rejected(value) { try {
step(generator["throw"](value));
}
catch (e) {
reject(e);
} }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function () { if (t[0] & 1)
throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f)
throw new TypeError("Generator is already executing.");
while (_)
try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done)
return t;
if (y = 0, t)
op = [op[0] & 2, t.value];
switch (op[0]) {
case 0:
case 1:
t = op;
break;
case 4:
_.label++;
return { value: op[1], done: false };
case 5:
_.label++;
y = op[1];
op = [0];
continue;
case 7:
op = _.ops.pop();
_.trys.pop();
continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
_ = 0;
continue;
}
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
_.label = op[1];
break;
}
if (op[0] === 6 && _.label < t[1]) {
_.label = t[1];
t = op;
break;
}
if (t && _.label < t[2]) {
_.label = t[2];
_.ops.push(op);
break;
}
if (t[2])
_.ops.pop();
_.trys.pop();
continue;
}
op = body.call(thisArg, _);
}
catch (e) {
op = [6, e];
y = 0;
}
finally {
f = t = 0;
}
if (op[0] & 5)
throw op[1];
return { value: op[0] ? op[1] : void 0, done: true };
}
}
var __createBinding = Object.create ? (function (o, m, k, k2) {
if (k2 === undefined)
k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
}) : (function (o, m, k, k2) {
if (k2 === undefined)
k2 = k;
o[k2] = m[k];
});
function __exportStar(m, o) {
for (var p in m)
if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p))
__createBinding(o, m, p);
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m)
return m.call(o);
if (o && typeof o.length === "number")
return {
next: function () {
if (o && i >= o.length)
o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m)
return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done)
ar.push(r.value);
}
catch (error) {
e = { error: error };
}
finally {
try {
if (r && !r.done && (m = i["return"]))
m.call(i);
}
finally {
if (e)
throw e.error;
}
}
return ar;
}
/** @deprecated */
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
/** @deprecated */
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++)
s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2)
for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar)
ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || from);
}
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator)
throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n])
i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try {
step(g[n](v));
}
catch (e) {
settle(q[0][3], e);
} }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length)
resume(q[0][0], q[0][1]); }
}
function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
}
function __asyncValues(o) {
if (!Symbol.asyncIterator)
throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function (v) { resolve({ value: v, done: d }); }, reject); }
}
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) {
Object.defineProperty(cooked, "raw", { value: raw });
}
else {
cooked.raw = raw;
}
return cooked;
}
;
var __setModuleDefault = Object.create ? (function (o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function (o, v) {
o["default"] = v;
};
function __importStar(mod) {
if (mod && mod.__esModule)
return mod;
var result = {};
if (mod != null)
for (var k in mod)
if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
}
function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}
function __classPrivateFieldGet(receiver, state, kind, f) {
if (kind === "a" && !f)
throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
}
function __classPrivateFieldSet(receiver, state, value, kind, f) {
if (kind === "m")
throw new TypeError("Private method is not writable");
if (kind === "a" && !f)
throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
}
function getRequestOrigin(req) {
// If express is resolving and trusting X-Forwarded-Host, we want to take it
// into an account to properly generate request origin.
var trustProxyFn = req.app.get('trust proxy fn');
var forwardedHost = req.get('X-Forwarded-Host');
if (forwardedHost && trustProxyFn(req.connection.remoteAddress, 0)) {
if (forwardedHost.indexOf(',') !== -1) {
// Note: X-Forwarded-Host is normally only ever a
// single value, but this is to be safe.
forwardedHost = forwardedHost
.substring(0, forwardedHost.indexOf(','))
.trimRight();
}
return req.protocol + '://' + forwardedHost;
}
else {
return req.protocol + '://' + req.get('host');
}
}
function getRequestUrl(req) {
return getRequestOrigin(req) + req.originalUrl;
}
var RenderingCache = /** @class */ (function () {
function RenderingCache(options) {
this.options = options;
this.renders = new Map();
}
RenderingCache.prototype.setAsRendering = function (key) {
this.renders.set(key, { rendering: true });
};
RenderingCache.prototype.isRendering = function (key) {
var _a;
return !!((_a = this.renders.get(key)) === null || _a === void 0 ? void 0 : _a.rendering);
};
RenderingCache.prototype.store = function (key, err, html) {
var _a, _b;
var entry = { err: err, html: html };
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.ttl) {
entry.time = Date.now();
}
if ((_b = this.options) === null || _b === void 0 ? void 0 : _b.cacheSize) {
this.renders.delete(key);
if (this.renders.size >= this.options.cacheSize) {
this.renders.delete(this.renders.keys().next().value);
}
}
this.renders.set(key, entry);
};
RenderingCache.prototype.get = function (key) {
return this.renders.get(key);
};
RenderingCache.prototype.clear = function (key) {
this.renders.delete(key);
};
RenderingCache.prototype.isReady = function (key) {
var entry = this.renders.get(key);
var isRenderPresent = (entry === null || entry === void 0 ? void 0 : entry.html) || (entry === null || entry === void 0 ? void 0 : entry.err);
return isRenderPresent && this.isFresh(key);
};
RenderingCache.prototype.isFresh = function (key) {
var _a, _b, _c, _d;
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.ttl)) {
return true;
}
return Date.now() - ((_c = (_b = this.renders.get(key)) === null || _b === void 0 ? void 0 : _b.time) !== null && _c !== void 0 ? _c : 0) < ((_d = this.options) === null || _d === void 0 ? void 0 : _d.ttl);
};
return RenderingCache;
}());
exports.RenderingStrategy = void 0;
(function (RenderingStrategy) {
RenderingStrategy[RenderingStrategy["ALWAYS_CSR"] = -1] = "ALWAYS_CSR";
RenderingStrategy[RenderingStrategy["DEFAULT"] = 0] = "DEFAULT";
RenderingStrategy[RenderingStrategy["ALWAYS_SSR"] = 1] = "ALWAYS_SSR";
})(exports.RenderingStrategy || (exports.RenderingStrategy = {}));
/**
* Returns the full url for the given SSR Request.
*/
var getDefaultRenderKey = getRequestUrl;
/**
* The rendered pages are kept in memory to be served on next request. If the `cache` is set to `false`, the
* response is evicted as soon as the first successful response is successfully returned.
*/
var OptimizedSsrEngine = /** @class */ (function () {
function OptimizedSsrEngine(expressEngine, ssrOptions) {
this.expressEngine = expressEngine;
this.ssrOptions = ssrOptions;
this.currentConcurrency = 0;
this.renderingCache = new RenderingCache(this.ssrOptions);
this.templateCache = new Map();
/**
* When the config `reuseCurrentRendering` is enabled, we want perform
* only one render for one rendering key and reuse the html result
* for all the pending requests for the same rendering key.
* Therefore we need to store the callbacks for all the pending requests
* and invoke them with the html after the render completes.
*
* This Map should be used only when `reuseCurrentRendering` config is enabled.
* It's indexed by the rendering keys.
*/
this.renderCallbacks = new Map();
}
Object.defineProperty(OptimizedSsrEngine.prototype, "engineInstance", {
get: function () {
return this.renderResponse.bind(this);
},
enumerable: false,
configurable: true
});
/**
* When SSR page can not be returned in time, we're returning index.html of
* the CSR application.
* The CSR application is returned with the "Cache-Control: no-store" response-header. This notifies external cache systems to not use the CSR application for the subsequent request.
*/
OptimizedSsrEngine.prototype.fallbackToCsr = function (response, filePath, callback) {
response.set('Cache-Control', 'no-store');
callback(undefined, this.getDocument(filePath));
};
OptimizedSsrEngine.prototype.getRenderingKey = function (request) {
var _a;
return ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.renderKeyResolver)
? this.ssrOptions.renderKeyResolver(request)
: getDefaultRenderKey(request);
};
OptimizedSsrEngine.prototype.getRenderingStrategy = function (request) {
var _a;
return ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.renderingStrategyResolver)
? this.ssrOptions.renderingStrategyResolver(request)
: exports.RenderingStrategy.DEFAULT;
};
/**
* When returns true, the server side rendering should be performed.
* When returns false, the CSR fallback should be returned.
*
* We should not render, when there is already
* a pending rendering for the same rendering key
* (unless the `reuseCurrentRendering` config option is enabled)
* OR when the concurrency limit is exceeded.
*/
OptimizedSsrEngine.prototype.shouldRender = function (request) {
var _a, _b;
var renderingKey = this.getRenderingKey(request);
var concurrencyLimitExceeded = this.isConcurrencyLimitExceeded(renderingKey);
var fallBack = this.renderingCache.isRendering(renderingKey) &&
!((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering);
if (fallBack) {
this.log("CSR fallback: rendering in progress (" + (request === null || request === void 0 ? void 0 : request.originalUrl) + ")");
}
else if (concurrencyLimitExceeded) {
this.log("CSR fallback: Concurrency limit exceeded (" + ((_b = this.ssrOptions) === null || _b === void 0 ? void 0 : _b.concurrency) + ")");
}
return ((!fallBack &&
!concurrencyLimitExceeded &&
this.getRenderingStrategy(request) !== exports.RenderingStrategy.ALWAYS_CSR) ||
this.getRenderingStrategy(request) === exports.RenderingStrategy.ALWAYS_SSR);
};
/**
* Checks for the concurrency limit
*
* @returns true if rendering this request would exceed the concurrency limit
*/
OptimizedSsrEngine.prototype.isConcurrencyLimitExceeded = function (renderingKey) {
var _a, _b;
// If we can reuse a pending render for this request, we don't take up a new concurrency slot.
// In that case we don't exceed the concurrency limit even if the `currentConcurrency`
// already reaches the limit.
if (((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering) &&
this.renderingCache.isRendering(renderingKey)) {
return false;
}
return ((_b = this.ssrOptions) === null || _b === void 0 ? void 0 : _b.concurrency)
? this.currentConcurrency >= this.ssrOptions.concurrency
: false;
};
/**
* Returns true, when the `timeout` option has been configured to non-zero value OR
* when the rendering strategy for the given request is ALWAYS_SSR.
* Otherwise, it returns false.
*/
OptimizedSsrEngine.prototype.shouldTimeout = function (request) {
var _a;
return (!!((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.timeout) ||
this.getRenderingStrategy(request) === exports.RenderingStrategy.ALWAYS_SSR);
};
/**
* Returns the timeout value.
*
* In case of the rendering strategy ALWAYS_SSR, it returns the config `forcedSsrTimeout`.
* Otherwise, it returns the config `timeout`.
*/
OptimizedSsrEngine.prototype.getTimeout = function (request) {
var _a, _b, _c, _d;
return this.getRenderingStrategy(request) === exports.RenderingStrategy.ALWAYS_SSR
? (_b = (_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.forcedSsrTimeout) !== null && _b !== void 0 ? _b : 60000
: (_d = (_c = this.ssrOptions) === null || _c === void 0 ? void 0 : _c.timeout) !== null && _d !== void 0 ? _d : 0;
};
/**
* If there is an available cached response for this rendering key,
* it invokes the given render callback with the response and returns true.
*
* Otherwise, it returns false.
*/
OptimizedSsrEngine.prototype.returnCachedRender = function (request, callback) {
var _a;
var key = this.getRenderingKey(request);
if (this.renderingCache.isReady(key)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var cached = this.renderingCache.get(key);
callback(cached.err, cached.html);
if (!((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.cache)) {
// we drop cached rendering if caching is disabled
this.renderingCache.clear(key);
}
return true;
}
return false;
};
/**
* Handles the request and invokes the given `callback` with the result html / error.
*
* The result might be ether:
* - a CSR fallback with a basic `index.html` content
* - a result rendered by the original Angular Universal express engine
* - a result from the in-memory cache (which was previously rendered by Angular Universal express engine).
*/
OptimizedSsrEngine.prototype.renderResponse = function (filePath, options, callback) {
var _this = this;
var request = options.req;
var response = options.res || options.req.res;
if (this.returnCachedRender(request, callback)) {
this.log("Render from cache (" + (request === null || request === void 0 ? void 0 : request.originalUrl) + ")");
return;
}
if (!this.shouldRender(request)) {
this.fallbackToCsr(response, filePath, callback);
return;
}
var requestTimeout;
if (this.shouldTimeout(request)) {
// establish timeout for rendering
var timeout_1 = this.getTimeout(request);
requestTimeout = setTimeout(function () {
requestTimeout = undefined;
_this.fallbackToCsr(response, filePath, callback);
_this.log("SSR rendering exceeded timeout " + timeout_1 + ", fallbacking to CSR for " + (request === null || request === void 0 ? void 0 : request.originalUrl), false);
}, timeout_1);
}
else {
// Here we respond with the fallback to CSR, but we don't `return`.
// We let the actual rendering task to happen in the background
// to eventually store the rendered result in the cache.
this.fallbackToCsr(response, filePath, callback);
}
var renderingKey = this.getRenderingKey(request);
var renderCallback = function (err, html) {
var _a;
if (requestTimeout) {
// if request is still waiting for render, return it
clearTimeout(requestTimeout);
callback(err, html);
_this.log("Request is resolved with the SSR rendering result (" + (request === null || request === void 0 ? void 0 : request.originalUrl) + ")");
// store the render only if caching is enabled
if ((_a = _this.ssrOptions) === null || _a === void 0 ? void 0 : _a.cache) {
_this.renderingCache.store(renderingKey, err, html);
}
else {
_this.renderingCache.clear(renderingKey);
}
}
else {
// store the render for future use
_this.renderingCache.store(renderingKey, err, html);
}
};
this.handleRender({
filePath: filePath,
options: options,
renderCallback: renderCallback,
request: request,
});
};
OptimizedSsrEngine.prototype.log = function (message, debug) {
if (debug === void 0) { debug = true; }
var _a;
if (!debug || ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.debug)) {
console.log(message);
}
};
/** Retrieve the document from the cache or the filesystem */
OptimizedSsrEngine.prototype.getDocument = function (filePath) {
var doc = this.templateCache.get(filePath);
if (!doc) {
// fs.readFileSync could be missing in a browser, specifically
// in a unit tests with { node: { fs: 'empty' } } webpack configuration
doc = (fs__namespace === null || fs__namespace === void 0 ? void 0 : fs__namespace.readFileSync) ? fs__namespace.readFileSync(filePath, 'utf-8') : '';
this.templateCache.set(filePath, doc);
}
return doc;
};
/**
* Delegates the render to the original _Angular Universal express engine_.
*
* In case when the config `reuseCurrentRendering` is enabled and **if there is already a pending
* render task for the same rendering key**, it doesn't delegate a new render to Angular Universal.
* Instead, it waits for the current rendering to complete and then reuse the result for all waiting requests.
*/
OptimizedSsrEngine.prototype.handleRender = function (_e) {
var _this = this;
var filePath = _e.filePath, options = _e.options, renderCallback = _e.renderCallback, request = _e.request;
var _a, _b;
if (!((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering)) {
this.startRender({
filePath: filePath,
options: options,
renderCallback: renderCallback,
request: request,
});
return;
}
var renderingKey = this.getRenderingKey(request);
if (!this.renderCallbacks.has(renderingKey)) {
this.renderCallbacks.set(renderingKey, []);
}
(_b = this.renderCallbacks.get(renderingKey)) === null || _b === void 0 ? void 0 : _b.push(renderCallback);
if (!this.renderingCache.isRendering(renderingKey)) {
this.startRender({
filePath: filePath,
options: options,
request: request,
renderCallback: function (err, html) {
// Share the result of the render with all awaiting requests for the same key:
var _a;
// Note: we access the Map at the moment of the render finished (don't store value in a local variable),
// because in the meantime something might have deleted the value (i.e. when `maxRenderTime` passed).
(_a = _this.renderCallbacks
.get(renderingKey)) === null || _a === void 0 ? void 0 : _a.forEach(function (cb) { return cb(err, html); }); // pass the shared result to all waiting rendering callbacks
_this.renderCallbacks.delete(renderingKey);
},
});
}
this.log("Request is waiting for the SSR rendering to complete (" + (request === null || request === void 0 ? void 0 : request.originalUrl) + ")");
};
/**
* Delegates the render to the original _Angular Universal express engine_.
*
* There is no way to abort the running render of Angular Universal.
* So if the render doesn't complete in the configured `maxRenderTime`,
* we just consider the render task as hanging (note: it's a potential memory leak!).
* Later on, even if the render completes somewhen in the future, we will ignore
* its result.
*/
OptimizedSsrEngine.prototype.startRender = function (_e) {
var _this = this;
var filePath = _e.filePath, options = _e.options, renderCallback = _e.renderCallback, request = _e.request;
var _a, _b;
var renderingKey = this.getRenderingKey(request);
// Setting the timeout for hanging renders that might not ever finish due to various reasons.
// After the configured `maxRenderTime` passes, we consider the rendering task as hanging,
// and release the concurrency slot and forget all callbacks waiting for the render's result.
var maxRenderTimeout = setTimeout(function () {
var _a;
_this.renderingCache.clear(renderingKey);
maxRenderTimeout = undefined;
_this.currentConcurrency--;
if ((_a = _this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering) {
_this.renderCallbacks.delete(renderingKey);
}
_this.log("Rendering of " + (request === null || request === void 0 ? void 0 : request.originalUrl) + " was not able to complete. This might cause memory leaks!", false);
}, (_b = (_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.maxRenderTime) !== null && _b !== void 0 ? _b : 300000); // 300000ms == 5 minutes
this.log("Rendering started (" + (request === null || request === void 0 ? void 0 : request.originalUrl) + ")");
this.renderingCache.setAsRendering(renderingKey);
this.currentConcurrency++;
this.expressEngine(filePath, options, function (err, html) {
if (!maxRenderTimeout) {
// ignore this render's result because it exceeded maxRenderTimeout
_this.log("Rendering of " + request.originalUrl + " completed after the specified maxRenderTime, therefore it was ignored.", false);
return;
}
clearTimeout(maxRenderTimeout);
_this.log("Rendering completed (" + (request === null || request === void 0 ? void 0 : request.originalUrl) + ")");
_this.currentConcurrency--;
renderCallback(err, html);
});
};
return OptimizedSsrEngine;
}());
/**
* Returns Spartacus providers to be passed to the Angular express engine (in SSR)
*
* @param options
*/
function getServerRequestProviders() {
return [
{
provide: core.SERVER_REQUEST_URL,
useFactory: getRequestUrl,
deps: [tokens.REQUEST],
},
{
provide: core.SERVER_REQUEST_ORIGIN,
useFactory: getRequestOrigin,
deps: [tokens.REQUEST],
},
];
}
/**
* The wrapper over the standard ngExpressEngine, that provides tokens for Spartacus
* @param ngExpressEngine
*/
var NgExpressEngineDecorator = /** @class */ (function () {
function NgExpressEngineDecorator() {
}
/**
* Returns the higher order ngExpressEngine with provided tokens for Spartacus
*
* @param ngExpressEngine
*/
NgExpressEngineDecorator.get = function (ngExpressEngine, optimizationOptions) {
return decorateExpressEngine(ngExpressEngine, optimizationOptions);
};
return NgExpressEngineDecorator;
}());
function decorateExpressEngine(ngExpressEngine, optimizationOptions) {
if (optimizationOptions === void 0) { optimizationOptions = {
concurrency: 20,
timeout: 3000,
}; }
return function (setupOptions) {
var _a;
var engineInstance = ngExpressEngine(Object.assign(Object.assign({}, setupOptions), { providers: __spreadArray(__spreadArray([], __read(getServerRequestProviders())), __read(((_a = setupOptions.providers) !== null && _a !== void 0 ? _a : []))) }));
// apply optimization wrapper if optimization options were defined
return optimizationOptions
? new OptimizedSsrEngine(engineInstance, optimizationOptions)
.engineInstance
: engineInstance;
};
}
/**
* Generated bundle index. Do not edit.
*/
exports.NgExpressEngineDecorator = NgExpressEngineDecorator;
exports.OptimizedSsrEngine = OptimizedSsrEngine;
exports.RenderingCache = RenderingCache;
exports.getDefaultRenderKey = getDefaultRenderKey;
exports.getServerRequestProviders = getServerRequestProviders;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=spartacus-setup-ssr.umd.js.map