@ewizardjs/prerenderer
Version:
Fast, flexible, framework-agnostic prerendering for sites and SPAs.
191 lines (155 loc) • 7 kB
JavaScript
;
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
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"); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var JSDOM = require('jsdom/lib/old-api.js').jsdom;
var serializeDocument = require('jsdom/lib/old-api.js').serializeDocument;
var promiseLimit = require('promise-limit');
var shim = function shim(window) {
window.SVGElement = window.HTMLElement;
window.localStorage = window.sessionStorage = {
getItem: function getItem(key) {
return this[key];
},
setItem: function setItem(key, value) {
this[key] = value;
}
};
};
var getPageContents = function getPageContents(window, options, originalRoute) {
options = options || {};
return new Promise(function (resolve, reject) {
var int = void 0;
function captureDocument() {
var result = {
originalRoute: originalRoute,
route: originalRoute,
html: serializeDocument(window.document)
};
if (int != null) {
clearInterval(int);
}
window.close();
return result;
}
// CAPTURE WHEN AN EVENT FIRES ON THE DOCUMENT
if (options.renderAfterDocumentEvent) {
window.document.addEventListener(options.renderAfterDocumentEvent, function () {
return resolve(captureDocument());
});
// CAPTURE ONCE A SPECIFC ELEMENT EXISTS
} else if (options.renderAfterElementExists) {
var doc = window.document;
int = setInterval(function () {
if (doc.querySelector(options.renderAfterElementExists)) resolve(captureDocument());
}, 100);
// CAPTURE AFTER A NUMBER OF MILLISECONDS
} else if (options.renderAfterTime) {
setTimeout(function () {
return resolve(captureDocument());
}, options.renderAfterTime);
// DEFAULT: RUN IMMEDIATELY
} else {
resolve(captureDocument());
}
});
};
var JSDOMRenderer = function () {
function JSDOMRenderer(rendererOptions) {
_classCallCheck(this, JSDOMRenderer);
this._rendererOptions = rendererOptions || {};
if (this._rendererOptions.maxConcurrentRoutes == null) this._rendererOptions.maxConcurrentRoutes = 0;
if (this._rendererOptions.inject && !this._rendererOptions.injectProperty) {
this._rendererOptions.injectProperty = '__PRERENDER_INJECTED';
}
}
_createClass(JSDOMRenderer, [{
key: 'initialize',
value: function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
return _context.abrupt('return', Promise.resolve());
case 1:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function initialize() {
return _ref.apply(this, arguments);
}
return initialize;
}()
}, {
key: 'renderRoutes',
value: function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regenerator2.default.mark(function _callee2(routes, Prerenderer) {
var _this = this;
var rootOptions, limiter, results;
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
rootOptions = Prerenderer.getOptions();
limiter = promiseLimit(this._rendererOptions.maxConcurrentRoutes);
results = Promise.all(routes.map(function (route) {
return limiter(function () {
return new Promise(function (resolve, reject) {
JSDOM.env({
url: `http://127.0.0.1:${rootOptions.server.port}${route}`,
features: {
FetchExternalResources: ['script'],
ProcessExternalResources: ['script'],
SkipExternalResources: false
},
created: function created(err, window) {
if (err) return reject(err);
// Injection / shimming must happen before we resolve with the window,
// otherwise the page will finish loading before the injection happens.
if (_this._rendererOptions.inject) {
window[_this._rendererOptions.injectProperty] = _this._rendererOptions.inject;
}
window.addEventListener('error', function (event) {
console.error(event.error);
});
shim(window);
resolve(window);
}
});
}).then(function (window) {
return getPageContents(window, _this._rendererOptions, route);
});
});
})).catch(function (e) {
console.error(e);
return Promise.reject(e);
});
return _context2.abrupt('return', results);
case 4:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
function renderRoutes(_x, _x2) {
return _ref2.apply(this, arguments);
}
return renderRoutes;
}()
}, {
key: 'destroy',
value: function destroy() {
// NOOP
}
}]);
return JSDOMRenderer;
}();
module.exports = JSDOMRenderer;