There's no doubt that components are here to stay. Templating, in the traditional sense, is kind of fading. At best you wrap all your React components in a layout partial and call it a day. Every chunk of reusable UI is becoming Virtual DOM or is at least migrating that way.

But what if you're stuck with using tried and true template engines like JSP, Thyemleaf, Pug, or Razor? Are you doomed to Partial-Hell forever?

Not really. You can still build React-like components with Vanilla JS and HTML includes with the ultralight framework Domponent.

For the sake of brevity, let's define a component as having the following and let's base our Vanilla JS on React:

Declarative Markup Event Handling Cached DOM references Internal State

Just for good measure here are the component structures:

And here's our directory structure:

React



components/ | Counter/ | |Counter.js |Counter.scss

Domponent



components/ | Counter/ | |Counter.html |Counter.js |Counter.scss

Declarative Markup

Let's make a counter in React. Here's our JSX.



< div > < div > { this . state . count } < /div > < button type = " button " onClick = { this . decrement } > - 1 < /button > < button type = " button " onClick = { this . increment } > + 1 < /button > < /div >

Super simple. Super easy to understand. One button increments, one decrements, and a div shows the count.

How might our HTML look:



<div data-component= "Counter" > <p data-bind= "state:Counter.count" > 0 </p> <button data-action= "click->Counter.decrement" > -1 </button> <button data-action= "click->Counter.increment" > +1 </button> </div>

Let's pick this apart by the data- attributes:

data-component defines which Component your Markup is tied to. It acts as a root element for your component.

data-bind is a bit more complex. The left side of the colon : dictates that we are binding state. Much like this.state.count in React. The right side of the colon represents the Component we are retrieving state from. Then you break down the right side by its period . The left side is still the Component, while the right side is the key inside of the component state we are displaying. So here's a quick overview:



state:Component.stateKey

Event Handling

data-action is an attribute that accepts any Event type addEventListener accepts and can event accept options.

The basic html you need is



data-action="eventtype->component.method"

Now you can tie your button in your javascript like so:



import { Component } from " domponent " ; export default class Counter extends Component { constructor ( el ) { super ( el ); } increment () { this . setState ({ count : this . state . count + 1 }); } decrement () { this . setState ({ count : this . state . count - 1 }); } }

Cached DOM references

Each class in domponent has a $root which is the DOM node that contains data-component . You can reference it inside your component class with this.$root

data-ref works as a simple tag to tell Domponent to cache this node in memory.



data-ref="component.refName"

You can then access the element with this.refName where this is the component instance and `refName is the field name you give the cached element.

data-ref-array works similarly but it assigns the value as an array in your component and can hold multiple elements.

Internal State

Each Component has the following lifecycle methods:

stateWillUpdate

propsWillUpdate

propsDidUpdate

stateDidUpdate

These are exposed and will be called in order every time you fire this.setState . setState updates the internal state of the component and updates any relevant data-bind elements and any child components that rely on its state.

So that's it. You can check out some examples of the markup using some popular template engines here