import { hash, fromCache, toCache } from './cache.js' // Tagged template literal helpers. Operates in the same fashion as un-tagged template literals. function tmpl(strings, ...values) { return strings .map((str, index) => values[index] ? str + values[index] : str) .join('').trim() } // Used for syntax highlighting in supported editors. export const html = tmpl export const css = tmpl export const javascript = tmpl // Stupid-simple "sanitization". Just enough for this demo, not for production. export function sanitize(str) { if (typeof str !== 'string') return str return str.split('<').join('<') } // Shorthand to render a component and retrieve the body. async function b(component, ...params) { return (await this.render(component, ...params)).body } // Shorthand to render a component and retrieve the head. async function h(component, ...params) { return (await this.render(component, ...params)).head } // Shorthand to render a component and retrieve the css. async function c(component, ...params) { return (await this.render(component, ...params)).css } // Shorthand to render a component and retrieve the js. async function j(component, ...params) { return (await this.render(component, ...params)).js } /** * Render a component and return the head elements, css, js, and body contents of it and its children. * @param {Function: context} component A function * @param {...any} params Arguments to pass to the component being rendered * @returns {Object} * An object containing head elements, css, js, and body text returned by the rendered component. */ export async function render(component, ...params) { // Create a new context to pass to a component for each rendering. const ctx = { head: new Set(), css: new Set(), js: new Set(), body: null, } // Add render functions to the context object, bound to the current context. // This ensures any calls to render child components will have the parent context bound as "this", allowing copying // child render results into parent context without having to pass the parent context explicitly to every child component. ctx.render = render.bind(ctx) ctx.b = b.bind(ctx) ctx.h = h.bind(ctx) ctx.c = c.bind(ctx) ctx.j = j.bind(ctx) // Attempt to load result from cache by component name and params. const key = hash([component.name, params]) const stored = fromCache(key) if (stored) { console.info(`READING COMPONENT "${component.name}" FROM CACHE`) } // Render component or load from cache. const result = stored || await component(ctx, ...params) // Store result to cache if it wasn't already present. if (!stored) toCache(key, result) // Results with a parent context will copy child context fields into parent context. // This enables straightforward component-level caching of body, head, css, and js at the cost of higher memory usage. if (this) { result.head.forEach(r => this.head.add(r)) result.css.forEach(r => this.css.add(r)) result.js.forEach(r => this.js.add(r)) } // Return result context object to caller. return result }