npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

str-html

v0.2.1

Published

Very simple templating engine relying on JavaScript's tagged template literals.

Downloads

3

Readme

str-html

str-html is a simple browser-side templating engine relying on JavaScript's tagged template literals feature.

With it you can create dynamic HTML elements in your code by just writing its HTML equivalent, while allowing the insertion of more complex values and inner elements:

const myElem = strHtml`<div class="my-class" attr2=${myAttributeValue}">
  ${someInnerHtmlElement}
  Some text
  <span class="some-other-element ${someClassName}">Some ${"other"} text</span>
</div>`;

document.body.appendChild(myElem);

The template string expression's placements are properly checked by str-html and sanitized by relying on the browser's API, so security worries can be kept to a minimum - at least like with regular JSX and the JS HTML API.

The goal of this library is to replace the need for a more complex UI solution like React/vue.js and so on, for simpler web projects where it would be overkill and prototypes where the library setup would be bothersome. Here, the dependency size, API, library magic, and specific behaviors are all kept to a minimum allowing you to just write your own UI in a readable manner while relying on the expected HTML behavior.

There's probably other similar solutions elsewhere with the huge amounts of UI libraries and templating engines on npm, but I didn't find the right balance between simplicity and usefulness for my own projects, so here we are.

Quick yet complete example

You can install str-html from npm/yarn:

npm install str-html

Or just copy-paste the ./main.mjs file in this repository's root (I don't care).

And then write something like:

// script.js

import strHtml from "str-html";

// Simply declare an element by writing its HTML.
const myElement = strHtml`<div class="some-class">Some initial text</div>`;

// `myElement` is now just the corresponding HTMLElement.
// Use it as you wish.
//
// For example, you can register an onclick handler:
myElement.onclick = function (evt) {
    // do things with it
    console.log("Received click event", evt);
};

// Or dynamically update its text content like usual
myElement.textContent = "Some other text";

// Attribute values and element inner contents can be declared as
// "${expression}" in which case they will be properly sanitized.
//
// You can also put newline/whitespaces where authorized by HTML.
const className = 'class1 class2 classWithAQuote"3';
const textContent = "Some text with <> characters";
const anotherElement = strHtml`<div class="some-outer-element">
  <div class=${className}>
    ${textContent}
  </div>
</div>`;

// You can insert an element in another element like this
const parentElt1 = strHtml`<div class="parent-element">${myElement}</div>`;

// You can also concatenate multiple values by relying on an array
const parentElt2 = strHtml`<div class="parent-element">
  ${[myElement, anotherElement]}
  You can even add some text before of after or in the array, as you wish
</div>`;

// That's it, you know how to use it now!

API

The ultimate goal of this library is to be very simple to understand. No magic (well, tagged template literals are kind of magic, but you get my point), and all HTML rules applies for the rest of your application.

The only rules to understand with this library are that in str-html template litterals:

  1. At least for now, only HTML elements and text (including "character references" - like "&amp;") are authorized - which mainly means no CDATA section, no HTML comments and no DOCTYPE. If any of the unauthorized elements is detected, str-html will throw.

    If you don't know what those are, you probably don't need them though.

  2. An ${expression}:

    1. Can only be set as an attribute value and inside an element's content.

      This is enforced by str-html, which will throw if an expression is found elsewhere.

      Note that you can put in both of those places multiple ${expression} and strings concatenated and interpolated, in which case it will behave like you may suspect: concatenated and interpolated.

      Example:

      console.log(
          strHtml`<div class="${"class1 "}class2 class3${" class4"}" />`
              .outerHTML
      );
      // Outputs: "<div class="class1 class2 class3 class4"></div>"
    2. Will behave differently depending on the type found in the expression:

      • null and undefined will be translated to an empty string.

      • HTMLElement, will be appended as a child element when set as another element's inner content.

        str-html will throw if an HTMLElement is set as an attribute's value (why would you do that?).

      • any other values but an array (see below) will have its toString method called on it and its result will be first sanitized then used.

      • An array. Same rules than for the corresponding single values, but repeated for each element of that array. From the first to the last item of that array. The obtained results are then concatenated.

That's it you understand the totality of this library!

About void elements

Some elements, like <br> or <input> cannot have an inner content as per the HTML spec and do not need/have a closing tag, those are called "void elements" in HTML linguo.

In str-html they have been handled as is the more logical for library users:

  • The HTML-compliant way of declaring one without closing it is supported, you can picture it as if a closing tag was automatically added just after its opening tag.

  • If you put content inside a void element, it will be actually added to its parent element, exactly like if you did the same thing on a browser with the innerHTML API (at least on browsers I checked).

  • Closed void elements are just skipped

This should lead to a sensible way of handling them regardless of your preferences. For example, with <br>, any of those will be parsed the same way:

strHtml`<p>first line<br>second line</p>`; // HTML way
strHtml`<p>first line<br/>second line</p>`; // Self-closing way
strHtml`<p>first line<br></br>second line</p>`; // Second closing tag
strHtml`<p>first line<br>second line</br></p>`; // User may have not understood `<br>`

Is it fast?

I have no idea as I wasn't bothered yet to bench it.

It does has some overhead over native browser API due to the string parsing involved. For places where you really need the toppest performances, a huge advantage of str-html is that, because it just outputs an HTMLElement and can read them, it is possible to combine it with the fastest framework available: native browser API!

For example, you can do:

// Declare 1000 HTMLElement with native browser API
const elements = [];
for (let i = 0; i < 1000; i++) {
    const element = document.createElement("div");
    element.className = "some-class-name";
    element.textContent = `Some text content for ${i}`;
    elements.push(element);
}

// Include them in a `str-html` template to produce a parent HTMLElement in a
// more readable way.
const parentElt = strHtml`<div class="parent-elt">${elements}</div>`;

// And the other way arround, include `parentElt` in a element created through
// native browser API
const wrapperElt = document.createElement("div");
wrapperElt.appendChild(parentElt);

Again, maybe it turns out that str-html is already very fast for your need, it is for mine at least.

How do I declare a data binding?

You don't, that's what's beautiful!

HTMLElement produced by str-html are not dynamic: they produce an HTMLElement which corresponds to the values declared in it at this instant, you can just picture it like a regular JS template literal.

If you ever want to update one of this HTMLElement's content, just use regular browser API:

let counter = 0;

// Put the current value of counter in an element
const counterElt = strHtml`<div>${counter}</div>`;

counter++; // we increment the counter, now set to `1`

// `counterElt` still contains `0` right now, but to update it you can just do:
counterElt.textContent = counter;

Note that an HTMLElement added through str-html keeps the same original reference:

let counter = 0;
const counterElt = strHtml`<div>${counter}</div>`;

// `parentElt` will contain `counterElt`
const parentElt = strHtml`<div>${counterElt}</div>`;

// Update `counterElt` even inside `parentElt`
counter++;
counterElt.textContent = counter;

I want to add interactions to my element

Just rely on usual HTML event handlers:

const mySuperButton = strHtml`<button class="my-button">Click me</button>`;
mySuperButton.onClick = () => {
  mySuperButton.textContent = "You clicked!";
  alert("Great job!");
};

// Note that you can just target an inner element through the usual HTML API
const myElementWithAButtonInside = strHtml`<div>
  <button class="inner-button">Click me</button>`
  <div class="some-other-huge-inner-div-I-dont-want-to-listen-to-for-clicks" />
</div>`;
const innerButtonElt = myElementWithAButtonInside
  .getElementByClassName("inner-button")[0];
innerButtonElt.onClick = () => alert("Done!");

Why not allowing expressions everywhere?

Technically, it wouldn't be hard to also allow it as element names and attribute names, or even as a collection of attribute names and values, but I found that it complexified too much the API for something I don't really need.

My opinion may change in the future if I need this.

Why not adding a simple notion of components?

After some initial considerations, I think HTML custom elements already answer that need, so you might consider them.

Though, str-html is no react, if you really need custom component, dynamic updates and complex state management, you may want to look-up for another more complete solution.