88 lines
3.1 KiB
JavaScript
88 lines
3.1 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('<')
|
|
}
|
|
|
|
// 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, ...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(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
|
|
}
|