Skip to content

Instantly share code, notes, and snippets.

@dschnare
Last active April 5, 2021 15:56
Show Gist options
  • Save dschnare/bbe122c26af3f18ec508c88e6131b611 to your computer and use it in GitHub Desktop.
Save dschnare/bbe122c26af3f18ec508c88e6131b611 to your computer and use it in GitHub Desktop.
Simple EcmaScript template string-based templating system
/*
* Simple EcmaScript templating system.
*
* Templates are just functions that accept an object of
* properties and return a string.
*
* const Hello = (props = {}) => `
* Hello ${props.message || 'World'}!
* `
* console.log(Hello({ message: 'Mom' }))
*
* Templates can be created as a layout. A layout is a template with named slots.
* For layouts 'slot' is a function used to render a slot by name and specify
* optional default content.
*
* const InsideLayout = Layout((props, slot) => `
* This is some content at the top!
* ${slot('body', 'This is the default')}
* This is some content at the bottom!
* ${slot('bottomLine')}
* `)
*
* When creating a template from a layout, 'slot' is a function used to add content to a named slot.
* Add content to a slot by passing a template function to render the content. The props passed
* to these template functions is the same props passed to the layout when rendered.
*
* const HomePage = InsideLayout(slot => {
* slot('bottomLine', props => `This is the bottom line ${props.copyright || ''}`)
* })
*
* Then rendering the layout-based template is just a function call as usual.
*
* console.log(HomePage({ copyright: 2019 }))
*/
const assert = (ok, message = 'Assertion failed') => {
if (!ok) throw new Error(message)
}
/**
* Factory that creates a template generator.
*
* @example
* const InsideLayout = Layout((props, slot) => `
* This is some content at the top!
* ${slot('body', 'This is the default')}
* This is some content at the bottom!
* ${slot('bottomLine')}
* `)
* const HomePage = InsideLayout(slot => {
* slot('bottomLine', props => `This is the bottom line ${props.copyright || ''}`)
* })
* console.log(HomePage({ copyright: 2019 }))
*/
const Layout = template => {
assert(typeof template === 'function', 'template must be a funciton')
return slotProvider => {
assert(typeof slotProvider === 'function', 'slotProvider must be a function')
const slots = Object.create(null)
slotProvider((slotName, content) => {
assert(typeof content === 'function', `slot '${slotName}' content must be a function`)
const a = sections[slotName] || []
a.push(content)
sections[slotName] = a
})
return (props = {}) => {
const renderSlot = (slotName, defaultContent = '') => {
assert(typeof defaultContent === 'string', `slot '${slotName}' default content must be a string`)
const a = slots[slotName] || []
return a.map(content => content({ ...props })).join('\n') || defaultContent
}
return template(props, renderSlot)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment