Convert context to a factory.

This commit is contained in:
Joshua Bemenderfer 2020-09-23 20:37:34 -04:00
parent 05c575bb26
commit 6f24dd523b
7 changed files with 57 additions and 62 deletions

View File

@ -40,8 +40,7 @@ const styles = css`
` `
export async function Footer (ctx, { path, title }) { export async function Footer (ctx, { path, title }) {
ctx.css.add(styles) ctx.addCSS(styles)
ctx.body = await body(ctx, { path, title }) ctx.setBody(await body(ctx, { path, title }))
return ctx return ctx
} }

View File

@ -5,7 +5,7 @@ import base from '../styles/base.js'
const body = async (ctx, { path, title }) => html` const body = async (ctx, { path, title }) => html`
<header> <header>
<h3 style="flex: 1;">Title: ${sanitize(title)}</h3> <h3 style="flex: 1;">Title: ${sanitize(title)}</h3>
${await ctx.b(NavBar, { path })} ${await ctx.renderBody(NavBar, { path })}
</header> </header>
` `
@ -17,8 +17,8 @@ const styles = css`
` `
export async function Header (ctx, { path, title }) { export async function Header (ctx, { path, title }) {
ctx.css.add(styles) ctx.addCSS(styles)
ctx.body = await body(ctx, { path, title }) ctx.setBody(await body(ctx, { path, title }))
return ctx return ctx
} }

View File

@ -52,7 +52,7 @@ const styles = css`
` `
export function NavBar (ctx, { path }) { export function NavBar (ctx, { path }) {
ctx.css.add(styles) ctx.addCSS(styles)
ctx.body = body(path) ctx.setBody(body(path))
return ctx return ctx
} }

View File

@ -8,7 +8,7 @@ const head = ({ title }) => html`
` `
const body = async (ctx, { path, title }) => html` const body = async (ctx, { path, title }) => html`
${await ctx.b(Header, { path, title })} ${await ctx.renderBody(Header, { path, title })}
<main id="app"> <main id="app">
<section> <section>
<h1>About Page - ${sanitize(title)}</h1> <h1>About Page - ${sanitize(title)}</h1>
@ -23,7 +23,7 @@ const body = async (ctx, { path, title }) => html`
</ul> </ul>
</section> </section>
</main> </main>
${await ctx.b(Footer, { path, title })} ${await ctx.renderBody(Footer, { path, title })}
` `
const styles = css` const styles = css`
@ -33,10 +33,10 @@ const styles = css`
` `
export async function AboutPage (ctx, { path, title }) { export async function AboutPage (ctx, { path, title }) {
ctx.css.add(base) ctx.addCSS(base)
ctx.css.add(styles) ctx.addCSS(styles)
ctx.head.add(head({ title })) ctx.addHead(head({ title }))
ctx.body = await body(ctx, { path, title }) ctx.setBody(await body(ctx, { path, title }))
return ctx return ctx
} }

View File

@ -8,7 +8,7 @@ const head = ({ title }) => html`
` `
const body = async (ctx, { path, title }) => html` const body = async (ctx, { path, title }) => html`
${await ctx.b(Header, { path, title })} ${await ctx.renderBody(Header, { path, title })}
<main id="app"> <main id="app">
<section> <section>
<h1>Home Page - ${sanitize(title)}</h1> <h1>Home Page - ${sanitize(title)}</h1>
@ -17,7 +17,7 @@ const body = async (ctx, { path, title }) => html`
</p> </p>
</section> </section>
</main> </main>
${await ctx.b(Footer, { path, title })} ${await ctx.renderBody(Footer, { path, title })}
` `
const styles = css` const styles = css`
@ -27,10 +27,10 @@ const styles = css`
` `
export async function HomePage (ctx, { path, title }) { export async function HomePage (ctx, { path, title }) {
ctx.css.add(base) ctx.addCSS(base)
ctx.css.add(styles) ctx.addCSS(styles)
ctx.head.add(head({ title })) ctx.addHead(head({ title }))
ctx.body = await body(ctx, { path, title }) ctx.setBody(await body(ctx, { path, title }))
return ctx return ctx
} }

View File

@ -1,7 +1,7 @@
import { html, render } from '../templater.js' import { html, render } from '../templater.js'
async function Root (ctx, component, ...params) { async function Root (ctx, component, ...params) {
const result = await ctx.render(component, ...params) const result = await render(ctx, component, ...params)
const scripts = Array.from(result.js).join(';\n') const scripts = Array.from(result.js).join(';\n')
const css = Array.from(result.css).join('\n') const css = Array.from(result.css).join('\n')
@ -25,5 +25,5 @@ async function Root (ctx, component, ...params) {
} }
export function page(component, ...params) { export function page(component, ...params) {
return render(Root, component, ...params) return render(null, Root, component, ...params)
} }

View File

@ -18,24 +18,41 @@ export function sanitize(str) {
return str.split('<').join('&lt;') return str.split('<').join('&lt;')
} }
// Shorthand to render a component and retrieve the body. function contextFactory (parentCtx) {
async function b(component, ...params) { const head = new Set()
return (await this.render(component, ...params)).body 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
} }
// Shorthand to render a component and retrieve the head. return {
async function h(component, ...params) { head,
return (await this.render(component, ...params)).head css,
js,
body,
renderBody,
addHead,
addCSS,
addJS,
setBody
} }
// 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
} }
/** /**
@ -45,22 +62,9 @@ async function j(component, ...params) {
* @returns {Object<head: Set, css: Set, js: Set, body: String|null, render: Function>} * @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. * An object containing head elements, css, js, and body text returned by the rendered component.
*/ */
export async function render(component, ...params) { export async function render (parentCtx, component, ...params) {
// Create a new context to pass to a component for each rendering. // Create a new context to pass to a component for each rendering.
const ctx = { const ctx = contextFactory(parentCtx)
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. // Attempt to load result from cache by component name and params.
const key = hash([component.name, params]) const key = hash([component.name, params])
@ -74,14 +78,6 @@ export async function render(component, ...params) {
// Store result to cache if it wasn't already present. // Store result to cache if it wasn't already present.
if (!stored) toCache(key, result) 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 context object to caller.
return result return result
} }