@ewizardjs/prerenderer
Version:
Fast, flexible, framework-agnostic prerendering for sites and SPAs.
200 lines (159 loc) • 7.02 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"); } }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var Server = require('./server');
var PortFinder = require('portfinder');
var PACKAGE_NAME = '[Prerenderer]';
var OPTION_SCHEMA = {
staticDir: {
type: String,
required: true
},
indexPath: {
type: String,
required: false
}
};
function validateOptionsSchema(schema, options, parent) {
var errors = [];
Object.keys(schema).forEach(function (key) {
// Required options
if (schema[key].required && !options[key]) {
errors.push(`"${parent || ''}${key}" option is required!`);
return;
// Options with default values or potential children.
} else if (!options[key] && (schema[key].default || schema[key].children)) {
options[key] = schema[key].default != null ? schema[key].default : {};
// Non-required empty options.
} else if (!options[key]) return;
// Array-type options
if (Array.isArray(schema[key].type) && schema[key].type.indexOf(options[key].constructor) === -1) {
console.log(schema[key].type.indexOf(options[key].constructor));
errors.push(`"${parent || ''}${key}" option must be a ${schema[key].type.map(function (t) {
return t.name;
}).join(' or ')}!`);
// Single-type options.
} else if (!Array.isArray(schema[key].type) && options[key].constructor !== schema[key].type) {
errors.push(`"${parent || ''}${key}" option must be a ${schema[key].type.name}!`);
return;
}
if (schema[key].children) {
errors.push.apply(errors, _toConsumableArray(validateOptionsSchema(schema[key].children, options[key], key)));
return;
}
});
errors.forEach(function (error) {
console.error(`${PACKAGE_NAME} ${error}`);
});
return errors;
}
var Prerenderer = function () {
function Prerenderer(options) {
_classCallCheck(this, Prerenderer);
this._options = options || {};
this._server = new Server(this);
this._renderer = options.renderer;
if (this._renderer && this._renderer.preServer) this._renderer.preServer(this);
if (!this._options) throw new Error(`${PACKAGE_NAME} Options must be defined!`);
if (!this._options.renderer) {
throw new Error(`${PACKAGE_NAME} No renderer was passed to prerenderer.
If you are not sure wihch renderer to use, see the documentation at https://github.com/tribex/prerenderer.`);
}
if (!this._options.server) this._options.server = {};
var optionValidationErrors = validateOptionsSchema(OPTION_SCHEMA, this._options);
if (optionValidationErrors.length !== 0) throw new Error(`${PACKAGE_NAME} Options are invalid. Unable to prerender!`);
}
_createClass(Prerenderer, [{
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:
_context.t1 = this._options.server.port;
if (_context.t1) {
_context.next = 5;
break;
}
_context.next = 4;
return PortFinder.getPortPromise();
case 4:
_context.t1 = _context.sent;
case 5:
_context.t0 = _context.t1;
if (_context.t0) {
_context.next = 8;
break;
}
_context.t0 = 13010;
case 8:
this._options.server.port = _context.t0;
_context.next = 11;
return this._server.initialize();
case 11:
_context.next = 13;
return this._renderer.initialize();
case 13:
return _context.abrupt('return', Promise.resolve());
case 14:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function initialize() {
return _ref.apply(this, arguments);
}
return initialize;
}()
}, {
key: 'destroy',
value: function destroy() {
this._renderer.destroy();
this._server.destroy();
}
}, {
key: 'getServer',
value: function getServer() {
return this._server;
}
}, {
key: 'getRenderer',
value: function getRenderer() {
return this._renderer;
}
}, {
key: 'getOptions',
value: function getOptions() {
return this._options;
}
}, {
key: 'modifyServer',
value: function modifyServer(server, stage) {
if (this._renderer.modifyServer) this._renderer.modifyServer(this, server, stage);
}
}, {
key: 'renderRoutes',
value: function renderRoutes(routes) {
return this._renderer.renderRoutes(routes, this)
// Handle non-ASCII or invalid URL characters in routes by normalizing them back to unicode.
// Some browser environments may change unicode or special characters in routes to percent encodings.
// We need to convert them back for saving in the filesystem.
.then(function (renderedRoutes) {
renderedRoutes.forEach(function (rendered) {
rendered.route = decodeURIComponent(rendered.route);
});
return renderedRoutes;
});
}
}]);
return Prerenderer;
}();
module.exports = Prerenderer;