UNPKG

@platform/react.ssr

Version:

A lightweight SSR (server-side-rendering) system for react apps bundled with ParcelJS and hosted on S3.

156 lines (155 loc) 5.62 kB
import { defaultValue, fs, http, time, util } from '../common'; import { Site } from './Site'; let URL_CACHE = {}; export class Manifest { constructor(args) { this.def = args.def; this.baseUrl = util.stripSlashes(args.baseUrl); this.status = args.status || 200; } static reset() { URL_CACHE = {}; } static async fromFile(args) { const { baseUrl, loadBundleManifest } = args; const path = fs.resolve(args.path); if (!(await fs.pathExists(path))) { throw new Error(`Manifest file does not exist: '${args.path}'`); } const yaml = await fs.readFile(path, 'utf-8'); const def = await Manifest.parse({ yaml, baseUrl, loadBundleManifest }); return Manifest.create({ def, baseUrl }); } static async fromUrl(args) { const { manifestUrl, loadBundleManifest } = args; const errorResponse = (status, error) => { return { ok: false, status, error: new Error(error) }; }; try { const res = await http.get(args.manifestUrl); if (!res.ok) { const error = res.status === 403 ? `The manifest YAML has not been made "public" on the internet.` : `Failed while pulling manifest YAML from cloud.`; return errorResponse(403, error); } const baseUrl = args.baseUrl || manifestUrl; const manifest = await Manifest.fromYaml({ yaml: res.body, baseUrl, loadBundleManifest }); return { ok: true, status: 200, manifest, }; } catch (error) { return errorResponse(500, error); } } static async fromYaml(args) { const { yaml, baseUrl, loadBundleManifest } = args; const def = await Manifest.parse({ yaml, baseUrl, loadBundleManifest }); return Manifest.create({ def, baseUrl }); } static async parse(args) { const { loadBundleManifest } = args; const baseUrl = args.baseUrl.replace(/\/manifest.yml$/, ''); const yaml = util.parseYaml(args.yaml); if (!yaml.ok || !yaml.data) { const error = `Failed to parse manifest YAML. ${yaml.error.message}`; throw new Error(error); } let sites = []; const input = yaml.data.sites; sites = await Site.formatMany({ input, baseUrl, loadBundleManifest }); const manifest = { sites }; return manifest; } static async get(args) { const { ttl } = args; const key = `${args.manifestUrl}:${args.loadBundleManifest || 'false'}`; let cached = URL_CACHE[key]; if (!args.force && cached && !isCacheExpired({ key, ttl })) { return cached.manifest; } const res = await Manifest.fromUrl(args); if (res.manifest) { const manifest = res.manifest; cached = { manifest, time: time.now.timestamp }; URL_CACHE[key] = cached; } return URL_CACHE[key].manifest; } get ok() { return this.status.toString().startsWith('2'); } get sites() { if (!this._sites) { const manifest = this.def; this._sites = this.def.sites.map((def, index) => Site.create({ index, manifest })); } return this._sites; } get site() { return { byName: (name) => { name = (name || '').trim(); return this.sites.find(site => site.name.trim() === name); }, byHost: (domain) => { domain = util.stripHttp(domain || ''); return this.sites.find(site => site.isMatch(domain || '')); }, }; } get change() { return { site: (id) => { const name = typeof id === 'string' ? id : id.name; return { bundle: async (args) => { const site = this.site.byName(name); if (!site) { return undefined; } const bundle = args.value; const def = Object.assign({}, this.def); def.sites[site.index].bundle = bundle; const manifest = Manifest.create({ def, baseUrl: this.baseUrl }); if (args.saveTo) { await manifest.save(args.saveTo); } return manifest; }, }; }, }; } toObject() { return Object.assign({ status: this.status }, this.def); } async save(path, options = {}) { const def = Object.assign({}, this.def); if (defaultValue(options.minimal, true)) { def.sites.forEach(site => { delete site.files; delete site.entries; delete site.baseUrl; delete site.size; delete site.bytes; }); } path = fs.resolve(path); await fs.ensureDir(fs.dirname(path)); await fs.file.stringifyAndSave(path, def); } } Manifest.create = (args) => new Manifest(args); function isCacheExpired(args) { const { key, ttl } = args; const cached = URL_CACHE[key]; if (!cached || typeof ttl !== 'number') { return false; } const age = time.now.timestamp - cached.time; return age > ttl; }