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

rehtm

v0.5.1

Published

create HTML using HTM

Downloads

113

Readme

npm bundle size npm GitHub Workflow Status

Create and hydrate HTML using HTM:

import { html, ref } from 'rehtm'

let count = 0
const span = ref()

document.body.append(html`
  <div onclick=${() => span.current.innerHTML = ++count}>
    Clicked <span ref=${span}>${count}</span> times!
  </div>
`)

▷ TRY IT

  • 🧬 Hydration for pre-rendered content (e.g. SSR)
  • ⚡ Functions as Event Listeners
  • 🔗 Element references (instead of element IDs)
  • 📦 Object properties for custom elements
  • 🚀 Cached HTML templates for performance
  • 🧩 Extensions for custom attribute and node types

Contents

Installation

Node:

npm i rehtm

Browser / Deno:

import { html } from 'https://esm.sh/rehtm'

Usage

👉 Render DOM:

import { html } from 'rehtm'

const name = 'World'
document.body.append(html`<div>Hellow ${name}!</div>`)
document.body.append(html`
  <button onclick=${() => alert('CLICKED!')}>
    Click ME!
  </button>
`)

👉 Use ref() to get references to created elements:

import { ref, html } from 'rehtm'

const el = ref()
document.body.append(html`<div ref=${el}>Hellow World!</div>`)

console.log(el.current)
// <div>Hellow World!</div>

👉 Set object properties:

const div = ref()
html`<div ref=${div} prop=${{x: 2}}>Some content</div>`

console.log(div.current.prop)
// > { x: 2 }

If the object properties are set on a custom element with a .setProperty() method, then that method will be called instead:

import { define, onProperty } from 'minicomp'
import { html, ref } from 'rehtm'


define('say-hi', () => {
  const target = ref()
  onProperty('to', person => target.current.textContent = person.name)

  return html`<div>Hellow <span ref=${target} /></div>`
})


const jack = { name: 'Jack', ... }
const jill = { name: 'Jill', ... }

document.body.append(html`
  <say-hi to=${jack} />
  <say-hi to=${jill} />
`)

💡 rehtm creates HTML templates for any string literal and reuses them when possible. The following elements are all constructed from the same template:

html`<div class=${'foo'}>Hellow ${'World'}</div>`
html`<div class=${'bar'}>Hellow ${'Welt'}</div>`
html`<div class=${'baz'}>Hellow ${'Universe'}</div>`

Hydration

👉 Use templates to breath life into content already rendered (for example, when it is rendered on the server):

import { template, ref } from 'rehtm'

const span = ref()
let count = 0

// 👇 create a template to hydrate existing DOM:
const tmpl = template`
  <div onclick=${() => span.current.textContent = ++count}>
    Clicked <span ref=${span}>${count}</span> times!
  </div>
`

// 👇 hydrate existing DOM:
tmpl.hydrate(document.querySelector('div'))
<div>
  Clicked <span>0</span> times.
</div>

▷ TRY IT

👉 Use .hydateRoot() to hydrate children of an element instead of the element itself:

const tmpl = template`
  <i>${'some stuff'}</i>
  <b>${'other stuff'}</b>
`

tmpl.hydrateRoot(document.querySelector('#root'))
<div id="root">
  <i>This will be reset.</i>
  <b>This also.</b>
</div>

💡 rehtm can hydrate DOM that is minorly different (for example, elements have different attributes). However it requires the same tree-structure to be able to hydrate pre-rendered DOM. This can be particularly tricky with the presence of text nodes that contain whitespace (e.g. newlines, tabs, etc.). To avoid this, make sure you are hydrating DOM that was created using the same template string with rehtm.

👉 Use .create() for using a template to create elements with different values:

const tmpl = template`<div>Hellow ${'World'}</div>`

tmpl.create('Jack')
// > <div>Hellow Jack</div>

💡 html template tag also creates templates and uses .create() method to generate elements. It caches each template based on its string parts, and reloads the same template the next time it comes upon the same string bits.

Global Document Object

You might want to use rehtm in an environment where there is no global document object present (for example, during server side rendering). For such situations, use the re() helper function to create html and template tags for a specific document object:

import { re } from 'rehtm'

const { html, template } = re(document)
document.body.append(html`<div>Hellow World!</div>`)

re() reuses same build for same document object, all templates remain cached.

Extension

You can create your own html and template tags with extended behavior. For that, you need a baseline DOM factory, extended with some extensions, passed to the build() function. For example, this is (roughly) how the default html and template tags are generated:

import {
  build,         // 👉 builds template tags from given DOM factory, with caching enabled.
  extend,        // 👉 extends given DOM factory with given extensions.
  domFactory,    // 👉 this creates a baseline DOM factory.
  
  // 👉 this extension allows using functions as event listeners
  functionalEventListenersExt,
  
  // 👉 this extension allows setting object properties
  objectPropsExt,
  
  // 👉 this extension enables references
  refExt,
} from 'rehtm'


const { html, template } = build(
  extend(
    domFactory(),
    functionalEventListenersExt,
    objectPropsExt,
    refExt,
  )
)

An extension can extend any of these four core functionalities:

  • create(type, props, children, fallback, self)
    Is used when creating a new element. children is an already processed array of values. Should return the created node.

  • attribute(node, name, value, fallback, self)
    Is used when setting an attribute on an element (a create() extension might bypass this).

  • append(node, value, fallback, self)
    Is used when appending a child to a node (a create() extension might bypass this). Should return the appended node (or its analouge).

  • fill(node, value, fallback, self)
    Is used when hydrating a node with some content.

👉 Each method is given a fallback(), which it can invoke to invoke prior extensions (or the original factory):

const myExt = {
  attribute(node, name, value, fallback) {
    if (name === 'some-attr') {
      // do the magic 
    } else {
      // not our thing, let others set this
      // particular attribute
      fallback()
    }
  }
}

You can also call fallback() with modified arguments:

fallback(node, modify(name), value.prop)

👉 Each method is also given a self object, which represents the final DOM factory. It can be used when you need to invoke other methods of the host factory:

const myExt = {
  create(tag, props, children, fallback, self) {
    if (tag === 'my-custom-thing') {
      const node = fallback('div')
      self.attribute(node, 'class', 'my-custom-thingy', self)

      if (props) {
        for (const name in props) {
          self.attribute(node, name, props[name], self)
        }
      }
      
      // ...
    } else {
      return fallback()
    }
  }
}

You can see some extension examples here. For examle, this is how the functional event listeners extension works:

export const functionalEventListenerExt = {
  attribute(node, name, value, fallback) {
    if (name.startsWith('on') && typeof value === 'function') {
      const eventName = name.slice(2).toLowerCase()
      node.addEventListener(eventName, value)
    } else {
      fallback()
    }
  }
}

Contribution

You need node, NPM to start and git to start.

# clone the code
git clone [email protected]:loreanvictor/minicomp.git
# install stuff
npm i

Make sure all checks are successful on your PRs. This includes all tests passing, high code coverage, correct typings and abiding all the linting rules. The code is typed with TypeScript, Jest is used for testing and coverage reports, ESLint and TypeScript ESLint are used for linting. Subsequently, IDE integrations for TypeScript and ESLint would make your life much easier (for example, VSCode supports TypeScript out of the box and has this nice ESLint plugin), but you could also use the following commands:

# run tests
npm test
# check code coverage
npm run coverage
# run linter
npm run lint
# run type checker
npm run typecheck