create HTML using HTM
npm install rehtm


Create and hydrate HTML using HTM:
``js
import { html, ref } from 'rehtm'
let count = 0
const span = ref()
document.body.append(html
)
`
- 𧬠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
- Contents
- Installation
- Usage
- Hydration
- Global Document Object
- Extensions
- Contribution
Installation
Node:
`bash
npm i rehtm
`Browser / Deno:
`js
import { html } from 'https://esm.sh/rehtm'
`
Usage
š Render DOM:
`js
import { html } from 'rehtm'const name = 'World'
document.body.append(html
)
`
š Add event listeners:`js
document.body.append(html)
`
š Use
ref() to get references to created elements:`js
import { ref, html } from 'rehtm'const el = ref()
document.body.append(html
)console.log(el.current)
//
Hellow World!
`
š Set object properties:
`js
const div = ref()
htmlconsole.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:
`js
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
})
const jack = { name: 'Jack', ... }
const jill = { name: 'Jill', ... }
document.body.append(html
)
`
> NOTE
>
> rehtm creates HTML templates for any string literal and reuses them
> when possible. The following elements are all constructed from the same template:
>
>
`js
> html
> html
> html
> `
Hydration
š Use
templates to breath life into content already rendered (for example, when it is rendered on the server):`js
import { template, ref } from 'rehtm'const span = ref()
let count = 0
// š create a template to hydrate existing DOM:
const tmpl = template
// š hydrate existing DOM:
tmpl.hydrate(document.querySelector('div'))
`
`html
Clicked 0 times.
`š Use
.hydateRoot() to hydrate children of an element instead of the element itself:`js
const tmpl = templatetmpl.hydrateRoot(document.querySelector('#root'))
`
`html
This will be reset.
This also.
`
> IMPORTANT
>
> 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:
`js
const tmpl = templatetmpl.create('Jack')
// >
Hellow Jack
`
> NOTE
>
>
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:`js
import { re } from 'rehtm'const { html, template } = re(document)
document.body.append(html
)
`
>
re() reuses same build for same document object, all templates remain cached.
Extensions
You can extend the default behavior of rehtm by creating your own
html and template tags with extended behavior. For that, you need to extend 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 created:`js
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 is an object with one or more of the following methods:
`ts
interface DOMFactoryExt {
create?(type, propes, children, fallback, self) => Node
// š called for creating a new element.
// should return the element created attribute?(node, name, value, fallback, self) => void
// š called for setting an attribute on an element
append?(node, value, fallback, self) => Node
// š called for appending a child to an element.
// should return the appended node (or its analouge)
fill?(node, value, fallback, self) => void
// š called for hydrating a node with some content
}
`
š Each method is given a
fallback(), which it can invoke to invoke prior extensions (or the original factory): `js
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. For convenience, arguments not provided will default to the original ones.`js
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:`js
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 should use some baseline DOM factory, extended with your extension, and potentially some other extensions, to create your own
html and template tags. You can choose either of these following DOM factories for your baseline:Factory | Description
--- | ---
stdFactory() | a DOM factory with all the behaviour of rehtm such as functional event listeners, object properties and references.
domFactory() | a bare-bones DOM factory with no extra behaviour. Use this if you don't want the additional behaviours provided by the default html and template tags.
nullFactory() | a DOM factory that does nothing. Use this if you want to customise everything including how default elements are created.Each of these factory functions accepts a
Document object, defaulting to the global document object. This is useful for when you (or a user of your extensions) wants to use some other document object (for example, during server side rendering).Use the
extend() function to create an extended factory:`js
const myFactory = extend(stdFactory(), myExtension1, myExtension2, ...)
`Use the
build() function to create html and template tags from your extended factory:`js
const { html, template } = build(myFactory)
`
You can see some extension examples here. For examle, this is how the functional event listeners extension works:
`js
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
`bash
clone the code
git clone git@github.com:loreanvictor/minicomp.git
`
`bash
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:
`bash
run tests
npm test
`
`bash
check code coverage
npm run coverage
`
`bash
run linter
npm run lint
`
`bash
run type checker
npm run typecheck
``