@platform/react.ssr
Version:
A lightweight SSR (server-side-rendering) system for react apps bundled with ParcelJS and hosted on S3.
138 lines (137 loc) • 4.75 kB
JavaScript
import * as dotenv from 'dotenv';
import { defaultValue, fs, semver, util } from './common';
import { Manifest } from './manifest';
dotenv.config();
const { stripSlashes } = util;
const DEFAULT = {
FILE: 'ssr.yml',
};
const ERROR = {
noFile: (path) => `An "${DEFAULT.FILE}" configuration file does not exist at: ${path}`,
};
export class Config {
constructor(args) {
this.def = args.def;
}
get secret() {
return toValue(this.def.secret);
}
get builder() {
const builder = this.def.builder || {};
builder.bundles = builder.bundles || 'bundles';
builder.entries = builder.entries || '';
return builder;
}
get s3() {
const s3 = this.def.s3 || {};
const path = s3.path || {};
const endpoint = util.stripHttp(s3.endpoint || '');
const cdn = util.stripHttp(s3.cdn || '');
const accessKey = toValue(s3.accessKey);
const secret = toValue(s3.secret);
const bucket = s3.bucket || '';
const api = {
endpoint,
cdn,
accessKey,
secret,
bucket,
path: {
base: stripSlashes(path.base || ''),
manifest: stripSlashes(path.manifest || ''),
bundles: stripSlashes(path.bundles || ''),
},
get config() {
return { endpoint, accessKey, secret };
},
get fs() {
return fs.s3(api.config);
},
async versions(options = {}) {
const s3 = api.fs;
const prefix = `${api.path.base}/${api.path.bundles}`;
const list = s3.list({
bucket,
prefix,
});
const dirs = (await list.dirs).items.map(({ key }) => ({ key, version: fs.basename(key) }));
const versions = semver.sort(dirs.map(item => item.version));
return options.sort === 'DESC' ? versions.reverse() : versions;
},
};
return api;
}
get baseUrl() {
const s3 = this.s3;
return `https://${s3.cdn || s3.endpoint}/${s3.path.base}`;
}
get manifest() {
const filePath = fs.resolve(this.def.manifest || 'manifest.yml');
const s3 = this.s3;
const manifestUrl = `https://${s3.endpoint}/${s3.bucket}/${s3.path.base}/${s3.path.manifest}`;
const baseUrl = this.baseUrl;
const config = this;
const api = {
local: {
path: filePath,
get exists() {
return fs.pathExists(filePath);
},
async load(args = {}) {
const { loadBundleManifest } = args;
return Manifest.fromFile({ path: filePath, baseUrl, loadBundleManifest });
},
async ensureLatest(args = {}) {
if (!(await api.local.exists)) {
await config.createFromTemplate();
}
const remote = await api.s3.pull({ force: true });
if (remote.ok) {
const minimal = defaultValue(args.minimal, true);
await remote.save(filePath, { minimal });
}
return api.local.load();
},
},
s3: {
url: manifestUrl,
async pull(args = {}) {
return Manifest.get(Object.assign({}, args, { manifestUrl, baseUrl }));
},
},
};
return api;
}
async createFromTemplate() {
const source = fs.join(__dirname, '../tmpl/manifest.yml');
const target = this.manifest.local.path;
await fs.ensureDir(fs.dirname(target));
await fs.copy(source, target);
return { source, target };
}
}
Config.create = async (options = {}) => {
const path = fs.resolve(options.path || DEFAULT.FILE);
if (!(await fs.pathExists(path))) {
throw new Error(ERROR.noFile(path));
}
else {
const def = await fs.file.loadAndParse(path);
return new Config({ def });
}
};
Config.createSync = (options = {}) => {
const path = fs.resolve(options.path || DEFAULT.FILE);
if (!fs.pathExistsSync(path)) {
throw new Error(`An "ssr.yml" configuration file does not exist at: ${path}`);
}
else {
const def = fs.file.loadAndParseSync(path);
return new Config({ def });
}
};
const toValue = (value) => {
value = value || '';
value = process.env[value] ? process.env[value] : value;
return value || '';
};