@platform/react.ssr
Version:
A lightweight SSR (server-side-rendering) system for react apps bundled with ParcelJS and hosted on S3.
135 lines (134 loc) • 4.55 kB
JavaScript
import { constants, http, jsYaml, util } from '../common';
import { Route } from './Route';
export class Site {
constructor(args) {
const { index, manifest } = args;
this.index = index;
this.manifest = manifest;
this._regexes = toDomainRegexes(this.def.domain);
if (!this.name) {
throw new Error(`A site definition must have a name.`);
}
}
static async formatMany(args) {
const { baseUrl, loadBundleManifest } = args;
if (!Array.isArray(args.input)) {
const error = `The manifest YAML "sites" field is not an array.`;
throw new Error(error);
}
let sites = [];
for (const input of args.input) {
const site = await Site.formatOne({ input, baseUrl, loadBundleManifest });
sites = site ? [...sites, site] : sites;
}
return sites;
}
static async formatOne(args) {
const { input, baseUrl } = args;
if (typeof input !== 'object') {
return;
}
const name = (input.name || '').trim();
let domain = input.domain || '';
domain = Array.isArray(domain) ? domain : [domain];
domain = domain.map((hostname) => util.stripHttp(hostname));
const bundle = util.asString(input.bundle).replace(/\/*$/, '');
let routes = typeof input.routes === 'object' ? input.routes : {};
routes = Object.keys(routes).reduce((acc, next) => {
const input = routes[next];
if (input) {
const route = Route.format({ input });
if (route) {
acc[next] = route;
}
}
return acc;
}, {});
let files = [];
let entries = [];
let size = '-';
let bytes = -1;
if (args.loadBundleManifest) {
const bundleUrl = `${baseUrl}/${bundle}/${constants.PATH.BUNDLE_MANIFEST}`;
const res = await http.get(bundleUrl);
const bundleManifest = res.ok ? jsYaml.safeLoad(res.body) : undefined;
if (bundleManifest) {
size = bundleManifest.size;
bytes = bundleManifest.bytes;
files = bundleManifest.files || [];
entries = bundleManifest.entries || [];
}
}
const site = {
name,
domain,
baseUrl,
bundle,
routes,
size,
bytes,
files,
entries,
};
return site;
}
get def() {
return this.manifest.sites[this.index];
}
get name() {
return (this.def.name || '').trim();
}
get domain() {
return this.def.domain;
}
get bundle() {
return this.def.bundle;
}
get bundleUrl() {
const base = util.stripSlashes(this.def.baseUrl);
const path = util.stripSlashes(this.bundle);
return `${base}/${path}`;
}
get version() {
return util.firstSemver(this.bundle) || '0.0.0';
}
get size() {
return this.def.size;
}
get files() {
return this.def.files || [];
}
get routes() {
if (!this._routes) {
const site = this.def;
const routes = Object.keys(this.def.routes).map(key => site.routes[key]);
this._routes = routes.map(route => Route.create({ site, route }));
}
return this._routes;
}
isMatch(domain) {
const isMatch = (domain, regex) => {
const res = regex.exec(domain);
return Array.isArray(res) && res[0] === domain;
};
const domains = Array.isArray(domain) ? domain : [domain];
const regexes = this._regexes;
return domains.some(d => this.domain.includes(d) || regexes.some(r => isMatch(d, r)));
}
route(pathname) {
return pathname ? this.routes.find(route => route.isMatch(pathname)) : undefined;
}
redirectUrl(pathname) {
const path = util.stripSlashes(pathname);
const file = this.files.find(file => path.endsWith(file.path));
return file ? `${this.bundleUrl}/${file.path}` : '';
}
toObject() {
return Object.assign({}, this.def);
}
}
Site.create = (args) => new Site(args);
export function toDomainRegexes(domains) {
const toRegex = (domain) => new RegExp(util.stripSlashes(domain));
return domains.filter(domain => util.isDomainRegex(domain)).map(domain => toRegex(domain));
}