DEV Community

Diogo Neves
Diogo Neves

Posted on

BemtvJS - Overview

BemtvJS:
Template-based reactivity,
Brackethtml,
CSS-In-Template,
SuperComponent,
CSS-In-JS,
Hooks,
DOM events,
CompVars,
Transformation functions,
Automatic routing,
and Code-Splitting.

BemtvJS is a UI library that takes an approach contrary to most libraries and frameworks. Usually UI tools observe the state (variables and properties) and when it changes it updates the template and applies the result to the DOM, Bemtv makes the template itself check if the state has changed and update itself.

Brackethtml

Brackethtml is a markup language built into Bemtv that allows you to reduce HTML redundancy, support it and look like it:

const btn = `button[Click me!]`;
Enter fullscreen mode Exit fullscreen mode

Brackethtml also allows insertion of CSS-In-Template, which is simply CSS-In-JS inserted directly into the template:

const btn = `button[color:blue; ~ Click me!]`;
Enter fullscreen mode Exit fullscreen mode

SuperComponent

Creating a component:

import { _ } from "bemtv";

_`App`();
Enter fullscreen mode Exit fullscreen mode

The returned value is called a SuperComponent. Internally, each time the component is used, a lightweight instance is created that manages this new rendering of the component.

These instances assume “control” whenever an action occurs in them, where the reaction is the execution of a previously passed callback, normally in Hooks and DOM events.

CSS-In-JS

In addition to the style that can be applied directly to the template, a great option is to use the css() method:

import { _ } from "bemtv";

const { css, template } = _`App`();

css`
  color: blue;
  font-size: 20px;
`;

template`h1[Hello world!]`;
Enter fullscreen mode Exit fullscreen mode

Hooks

Each component instance goes through a series of steps, we can run functions called hooks in each of these steps:

import { _ } from "bemtv";

const { onInit, onMount, onUpdate, onUnmount } = _`App`();

onInit(() => {
  // Called(only once) when the instance is initialized.
});

onMount(() => {
  // Called(only once) after the component template has been mounted in the DOM.
});

onUpdate(() => {
  // Called after the template update is applied to the DOM.
});

onUnmount(() => {
  // Called(only once) after the component is removed/unmounted from the template it was in.
});
Enter fullscreen mode Exit fullscreen mode

DOM Events

We can use DOM events directly from SuperComponent:

import { _ } from "bemtv";

const { click$, mouseover$ } = _`App`();

click$(() => {}, { capture: true });

mouseover$(() => console.log("Hey!"));
Enter fullscreen mode Exit fullscreen mode

CompVars

We can pass an object as an argument to the component, this object is called compVars and all properties added to it are isolated to each rendering of the component, including data structures like Array, Set, Map and Object.

Knowing our “variables”, Bemtv manages to provide really sweet syntactic sugars.

An example is that we can add the path to the property directly in the template using the $ symbol:

import { _ } from "bemtv";

const { template, render } = _`Message`({ message: "Hey!" });

template`button[Clicked: $message ]`;

render();
Enter fullscreen mode Exit fullscreen mode

If we have a property with the same name as an HTML attribute, we can use the @ symbol to tell Bemtv to treat the property as a key and value:

import { _ } from "bemtv";

const { template, render } = _`Img`({
  src: "avatar.png",
  alt: "My avatar",
});

template`img[ @src @alt ]`;

render();
Enter fullscreen mode Exit fullscreen mode

Transformation Functions

The Transformation functions allow us to add a “mark” to a data structure and tell Bemtv to transform the data structure only when necessary, this allows us to focus only on the data structure.

import { tFn } from "bemtv";

const tListJoin = tFn((list) => list.join(" br[] "));
Enter fullscreen mode Exit fullscreen mode

The above example creates a transform function that can be used with lists, and when the list is requested by the template it executes that function and adds the result to the template:

import { _, tFn} from "bemtv";

const tListJoin = tFn((list) => list.join(" br[] "));

const { template } = _`List`({
  list: tListJoin(["user1", "user2", "user3"]),
});

template`div[Users: $list ]
Enter fullscreen mode Exit fullscreen mode

Whenever this list is changed (eg $.list.push(item)), Bemtv will detect and use the transform function again and render the change.

Automatic routing

Bemtv uses an innovative automatic route creation system, this is possible because the components can behave like routes/pages.

Bemtv is able to automatically figure out when a “normal” component should also be treated as a route:

A regular component:

import { _ } from "bemtv";

const { template } = _`AboutUs`();

template`We are cool!`;
Enter fullscreen mode Exit fullscreen mode

The component responsible for rendering the App:

import { _ } from "bemtv";

const { template, render } = _`App`();

template`
      Welcome!  br[]br[]

      #[] br[]br[]

      #AboutUs[ Link to about us ]`;

render();
Enter fullscreen mode Exit fullscreen mode

The second component uses the #[] symbol, it is within it that the routes are rendered.

Note the #AboutUs[...], this is where the “magic” happens.
First, Bemtv will read the App component template and find that the AboutUs component
is also a route (thanks to the # before it), and when the template is rendered, everything inside the #AboutUs[...] component will be wrapped in an a tag with the href attribute pointing to the route.

The route address will be the component name in kebab-case: /about-us.

When the user clicks on the link, the AboutUs component will be rendered in #[].

Bemtv will also figure out that the component is a route whenever we access some method of the component that targets routes, even if it is not called (thanks to Proxies).

Code-Splitting

To automate the component import process, Bemtv offers the autoImportComponents() function which accepts an object where the properties name must be the components name and their values ​​must be a function that imports the component using dynamic imports(dynamic import):

import { autoImportComponents } from "bemtv";

autoImportComponents({
  Counter() {
    import("./components/Counter");
  },
});
Enter fullscreen mode Exit fullscreen mode

Bemtv will import the component as soon as the component is used in a template, however, it will ignore the component until it is available.

Closure

These are not all the functionalities offered by Bemtv, for example, we are not talking about the innovative data sharing system between the components.
You can check everything in the project repository:

https://github.com/diogoneves07/bemtvjs.

Bemtv is a recent project, you are welcome to be part of the construction of this tool.

Any questions or if you want to contribute to the project, you can contact me or open a PR.

Thanks for getting this far!

Top comments (0)