component-demo/templater.js
2020-09-23 20:37:34 -04:00

84 lines
2.4 KiB
JavaScript

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('&lt;')
}
function contextFactory (parentCtx) {
const head = new Set()
const css = new Set()
const js = new Set()
let body = ''
const addHead = str => {
head.add(str)
if (parentCtx) parentCtx.addHead(str)
}
const addCSS = str => {
css.add(str)
if (parentCtx) parentCtx.addCSS(str)
}
const addJS = str => {
js.add(str)
if (parentCtx) parentCtx.addJS(str)
}
const setBody = str => (body = str)
const renderBody = async (component, ...params) => {
debugger;
(await render(parentCtx, component, ...params)).body
}
return {
head,
css,
js,
body,
renderBody,
addHead,
addCSS,
addJS,
setBody
}
}
/**
* Render a component and return the head elements, css, js, and body contents of it and its children.
* @param {Function<context, ...any>: context} component A function
* @param {...any} params Arguments to pass to the component being rendered
* @returns {Object<head: Set, css: Set, js: Set, body: String|null, render: Function>}
* An object containing head elements, css, js, and body text returned by the rendered component.
*/
export async function render (parentCtx, component, ...params) {
// Create a new context to pass to a component for each rendering.
const ctx = contextFactory(parentCtx)
// 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)
// Return result context object to caller.
return result
}