beyond
Version:
The Full Stack Universal Typescript Framework
181 lines (146 loc) • 5.3 kB
JavaScript
const fetch = require('node-fetch');
const DynamicProcessor = global.utils.DynamicProcessor(Map);
/**
* Keeps an updated list of static rendered widgets
*/
module.exports = class extends DynamicProcessor {
get dp() {
return 'application.widgets';
}
#application;
#js;
get js() {
return this.#js;
}
#errors = [];
get errors() {
return this.#errors;
}
#warnings = [];
get warnings() {
return this.#warnings;
}
constructor(application) {
super();
this.#application = application;
this.#js = new (require('./js'))(application);
super.setup(new Map([['modules', { child: application.modules }]]));
}
async url(key, distribution) {
if (!distribution.ssr) {
throw new Error('Application must have ssr configured to be able to process static widgets');
}
if (!this.has(key)) {
throw new Error(`Static widget "${key}" not found`);
}
const { ssr } = this.#application;
await ssr.ready(distribution);
const port = await ssr.port(distribution);
const { properties, uri, language, attributes: attrs } = this.get(key);
const { name } = properties.element;
return (() => {
const url = `http://localhost:${port}/widget?name=${name}`;
let qs = '';
properties.is === 'page' && (qs = `&uri=${uri}`);
language && (qs += `&language=${language}`);
if (attrs?.length) {
const attributes = new Map(attrs);
const names = [...attributes.keys()].join(',');
qs += `&attrs=${names}`;
attributes.forEach((value, param) => (qs += `&attrs.${param}=${value}`));
}
return encodeURI(`${url}${qs}`);
})();
}
async process(key, distribution) {
const url = await this.url(key, distribution);
try {
const response = await fetch(url);
const { html, css, store, specs, errors } = await response.json();
if (errors?.length) return { errors };
const output = { html, css };
specs.render?.csr && (output.store = store);
return output;
} catch (exc) {
return { errors: [exc.message] };
}
}
_prepared(require) {
const modules = this.children.get('modules').child;
modules.forEach(module => {
if (!require(module) || !require(module.bundles)) return false;
if (!module.bundles.has('widget')) return;
const bundle = module.bundles.get('widget');
require(bundle);
});
}
_process() {
const modules = this.children.get('modules').child;
const warnings = (this.#warnings = []);
void warnings.length; // To avoid a "never queried" warning
this.clear();
modules.forEach(module => {
if (!module.bundles.has('widget')) return;
const bundle = module.bundles.get('widget');
if (!bundle.valid) return;
const { properties } = bundle;
const widget = properties.element.name;
if (!properties.render?.sr) return;
const process = language => {
if (properties.is === 'page') {
const uris = new Set();
const { route } = properties;
if (!route) return;
!route.includes('$') && uris.add(route);
const list = properties.render.sr;
list.forEach?.(uri => uris.add(uri));
uris.forEach(uri => {
const key = `${language}${widget}//${uri}`;
const resource = `${widget}.${require('./checksum')(key)}.js`;
const props = { specifier: bundle.specifier, properties, uri, resource };
language && (props.language = language.slice(0, 2));
this.set(resource, props);
});
} else if (properties.is === 'layout') {
// The layouts does not have attributes, so the key is just the widget element name
const key = `${language}${widget}`;
const resource = `${widget}.${require('./checksum')(key)}.js`;
const props = { specifier: bundle.specifier, properties };
language && (props.language = language.slice(0, 2));
this.set(resource, props);
} else if (!properties.is) {
let items = properties.render.sr;
items = properties.render.sr instanceof Array ? items : [{}];
const attributes = new Set(properties.element.attrs);
items.forEach((item, index) => {
if (typeof item !== 'object') {
warnings.push(`Widget "${widget}" "sr" render entry of index [${index}] is invalid`);
return;
}
const iattrs = new Map(Object.entries(item));
const valid = [...iattrs.keys()].reduce((prev, curr) => prev && attributes.has(curr), true);
if (!valid) {
warnings.push(
`Widget "${widget}" "sr" render entry of index [${index}] has invalid attributes`
);
return;
}
let key = language;
[...iattrs].sort((a, b) => (a[0] < b[0] ? 1 : 0)).forEach(([k, v]) => (key += `${k}//${v}///`));
const resource = `${widget}.${require('./checksum')(key)}.js`;
const props = { specifier: bundle.specifier, properties, attributes: [...iattrs] };
language && (props.language = language.slice(0, 2));
this.set(resource, props);
});
}
};
const { multilanguage } = properties.render;
if (multilanguage) {
const languages = this.#application.languages.supported;
languages.forEach(language => process(`${language}:`));
} else {
process('');
}
});
}
};