Skip to content

A vdom renderer with an easy to understand and easy use functional interface.

License

Notifications You must be signed in to change notification settings

Olian04/easy-render

Repository files navigation

easy-render

NPM Version Bundle Size Available Types License CircleCI Test Coverage

Easy-Render is a vDOM renderer designed to be as easy to use as possible.

Why Easy-Render

  • It's small.
  • It requires NO transpilation, everything runs as is in the browser.
  • Everything is 100% typed and ready for Typescript!

Installation

NPM:

npm install easy-render

CDN:

<script src="https://cdn.jsdelivr.net/npm/easy-render/cdn/easy-render.js"></script>
<script>
  const { render } = easyRender;
</script>

Help me help you

Please ⭐️ this repository!

Buy Me A Coffee

Demos

Setup - Hello World

You can setup easy-render in one of two ways.

Using the default "render" method

The default render method expects a dom element with id 'root' to exsist.

import { render } from 'easy-render';

render`
  <h2>Hello World!</h1>
`;

Setting up your own Renderer instance

import { Renderer } from 'easy-render';

const { render } = Renderer({
  rootElement: document.getElementById('root')
});

render`
  <h2>Hello World!</h1>
`;

Very much a work in progress

This library is still very much a work in progress and anything in this readme, ESPECIALLY beyond this point, is to be considered volatile and is likely to not work correctly or at all.

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;
}

MVP

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>
  1. Check if intermediate structure is the same as the cached structure.

  2. If cache miss: I) Pass the intermediate structure into an xml parser. II) Create a Brynja builder for the resulting XML elements.

  3. 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'}},
  ],
});

About

A vdom renderer with an easy to understand and easy use functional interface.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published