easy-render v0.2.0
easy-render
Easy-Render is a vDOM renderer designed to be as easy to use as possible.
import { render } from 'easy-render';
const name = 'Easy-Render';
render`
<p>Hello ${name}!</p>
`;
import { render, r } from 'easy-render';
const listElements = (elements = []) => {
render`
<ul>
${elements.map(v => r`
<li>${v}</li>
`)}
</ul>
`;
}
listElements();
let numbers = [];
setInterval(() => {
numbers.push(numbers.length);
listElements(numbers);
}, 1000);
r (render component)
r
should return a brynja builder. This way r
would be implicitly responsible for rendering its own subtree, independant from the entire tree.
type r = (staticSegments: TemplateStringsArray, ...dynamicSegments: DynamicSegments[]) => BrynjaBuilder
This would mean that the following code:
render`
<div>
${[
r`<h1>Hello World</h1>`
]}
</div>
`;
...would result in the following brynja builder:
const _0 = _=>_
.child('h1', _=>_
.text('Hello World')
)
render(_=>_
.child('div', _=>_
.do(_0)
)
);
This whould also mean that easy-render
would support full interop with brynja
:
import { render } from 'easy-render';
import { createComponent } from 'brynja';
const HelloWorld = createComponent(() => _=>_
.child('h1', _=>_
.text('Hello World')
)
);
render`
<div>
${[
HelloWorld()
]}
</div>
`;
...and vice versa... interestingly:
import { r } from 'easy-render';
import { render } from 'brynja';
const HelloWorld = () => r`
<h1>Hello World</h1>
`;
render(_=>_
.child('div', _=>_
.do(HelloWorld())
)
);
Internallay r
would make up the majority of the logic in the render
function (mock implementation):
export function Renderer(config: { rootElement: HTMLElement, rootElementBuilder?: BuilderCB }): IRenderer {
const brynja = BrynjaRenderer({
rootElement: config.rootElement,
});
return {
render: (staticSegments, ...dynamicSegments) => {
const brynjaBuilder = r(staticSegments, ...dynamicSegments);
// Render using brynja
brynja.render(_=>_
.do(config.rootElementBuilder ?? (_=> {
// Echo props from root node if no custom rootElementBuilder was provided
_.while(i => i < config.rootElement.attributes.length, (_, i)=> {
const attribute = config.rootElement.attributes.item(i);
if (attribute === null) { return; }
_.prop(attribute.name, attribute.value);
})
}))
.do(brynjaBuilder)
);
}
}
}
Mock implementation of r
:
const r = (staticSegments: TemplateStringsArray, ...dynamicSegments: DynamicSegments[]): BrynjaBuilder => {
const { xml, dynamics } = processTagFunctionData({
static: staticSegments,
dynamic: dynamicSegments,
});
const DOM = parseXML(xml);
const builder = constructBrynjaBuilder({ DOM, dynamics });
return builder;
}
WIP
import { render } from 'easy-render';
render`
<div class="box">
<button
click=${e => console.log(e)}
style=${{
background: 'orangered',
':hover': {
background: 'skyblue',
}
}}
>Click me</button>
</div>
`;
Data in ${}
may either be a CSS object, an event handler function, a string, a number, or a list of strings or numbers
1) Intermediate structure, to preserve clojures for functions, to reduce amount of parcing for style object, and to allow for caching the parsed XML result unless the string structure changes.
<div class="box">
<button
click="placeholder-function-0"
style="placeholder-object-0"
>Click me</button>
</div>
2) Check if intermediate structure is the same as the cached structure.
3) If cache miss: I) Pass the intermediate structure into an xml parser. II) Create a Brynja builder for the resulting XML elements.
4) Call the stored Brynja builder, passing in any placeholder values as arguments.
Same as the following:
import { render } from 'brynja';
const builder = (args) =>
render(_=>_
.child('div', _=>_
.class('box')
.child('button', _=>_
.on('click', args.function[0])
.style(args.object[0])
.text('Click me')
)
)
);
builder({
function: [
e => console.log(e),
],
object: [
{background: 'orangered', ':hover':{background: 'skyblue'}},
],
});
Resources:
- XML parser: https://github.com/TobiasNickel/tXml