Last active
April 5, 2021 15:56
-
-
Save dschnare/bbe122c26af3f18ec508c88e6131b611 to your computer and use it in GitHub Desktop.
Simple EcmaScript template string-based templating system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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