Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

hydro-js

Krutsch4.6kMIT1.8.8TypeScript support: included

A lightweight reactive library

reactive, libary, javascript, dom, modern, fast, ssr

readme

100% Coverage

hydro-js

A lightweight (below 5K compressed) reactive UI library via template literal tags.
Support in all modern Browsers and with Server-Side Rendering!

Installation

To bootstrap a new app:

$ npm init hydro-app@latest <project> // or npx create-hydro-app@latest <project>

or integrate in an existing app:

$ npm install hydro-js
import { render, html } from 'hydro-js';

Alternatively you can use a CDN:

<script type="module">
  import { render, html } from "https://unpkg.com/hydro-js";
</script>

Examples

Concept

There are multiple things this library can do. The first thing is generating HTML from strings. This is mostly done by the Range Web API. There are already ways to do that, like Element.insertAdjacentHTML(), but this has some drawbacks, as it does not create Table Elements, like <colgroup>, <tbody>, <tfoot>, <thead> and <tr>. Furthermore, the html function deals with inline events, objects, Handlebars / {{ Mustache }} etc. Using this function will feel like writing JSX without a build step.

The render function is used for mounting and unmounting Elements to/from the DOM and for executing lifecycle hooks. Optionally, it can diff both HTML Trees and reuse Elements (optionally too). This is not using a virtual DOM.

The functions calls for render and DOM Updates are queued and worked on during a browser's idle periods.

In order to make the DOM reactive, ES6 Proxy objects are being used to map data against an array of DOM Elements. Whenever the setter is being called, the Proxy will check the mapping and update the DOM granularly. No re-renders are needed!

Almost all intern maps are using WeakMap with DOM Elements or Proxy objects as keys and thus memory is cleared efficiently.

Documentation

html

args: string
returns: DocumentFragment | Element | Text

Takes a string and transforms it to HTML. Used for internal bookkeeping too.

Example

html`<p>Text</p>`;

render

args:

  • new Element (ReturnType<typeof html> | reactive Proxy)
  • old Element (ReturnType<typeof html> | string)
  • shouldSchedule?: boolean (default: true)

returns: function that unmounts the new Element

Accepts the return value of html and replaces it with old Element. If it is a string, it will be resolved with querySelector. If there is no second parameter, the Element will be appended to the body.

Example

render(html`<p>Text</p>`);

setGlobalSchedule

args: boolean

Will enable/disable the schedule logic for render and DOM Updates. Intern value defaults to true.

setReuseElements

args: boolean

Will enable/disable the reuse of Elements in the diffing phase of render. Intern value defaults to true.

setInsertDiffing

args: boolean

If enabled, it will insert the new DOM Tree to the DOM before diffing. This will asssure that reused Elements will not lose their state (e.g. <video> in Chrome. Intern value defaults to false.

setReactivity

args: Node

Inserts Proxy values in the template HTML. This is useful, when HTML already exists, i.e. in a HTML file and you want to set the hydro Proxy Objects for the handlebars. Also, this can set event listener and remove the inline listener. This is a good way to send HTML over the wire.

Example 1

// <p id="value">{{value}}</p> in HTML
const template = $("#value");
hydro.value = "Hello World";
setReactivity(template);

Example 2 (with event)

// <p id="value" onclick="placeholder">{{value}}</p> in HTML
const template = $("#value")!;
hydro.value = "Hello World";
setReactivity(template, { placeholder: () => console.log("clicked") }); // placeholder should be unique

onRender

args:

  • function
  • elem (ReturnType<typeof html>)
  • ...args for passed function

Calls the passed in function with ...args, after the Element is being inserted by render;

Example

const elem = html`<p>Hello World</p>`;
onRender(() => console.log("rendered elem"), elem);
render(elem);

onCleanup

args:

  • function
  • elem (ReturnType<typeof html>)
  • ...args for passed function

Calls the passed in function with ...args, after the Element is being diffed out by render or removed by unmount;

Example

const elem = html`<p>Hello World</p>`;
onCleanup(() => console.log("removed elem"), elem);
const unmount = render(elem);
unmount();

reactive

args: value: any
returns: unique Proxy

Returns a Proxy object that can be used within html. The Proxy is wrapping a function that can set the value. There are two ways to call the function (see Nested Reactivity 2. If the Proxy will be called with a function, then the argument of the passed in function will be provided as the current value for the Proxy, otherwise it will take the new argument as new value. The actual value will be set on the hydro Proxy.
Special behaviour for (prev) functions: the old value will be kept, if the returned value is undefined.

Example

const data = reactive({ value: 42 });
render(html`<p>${data.value} €</p>`);
data((prev) => (prev.value = 21)); // Change the value

observe

args:

  • ReturnType<typeof reactive>
  • function

Calls the function whenever the value of reactive Proxy changes. This is only one layer deep but chaining properties on reactive Proxys will return a Proxy too. Observing a prop of an object will look like:

observe(person.name, ...)

Example

const person = reactive({ name: "Steve" });
observe(person.name, (newValue) => console.log(`Name changed to ${newValue}`));
person.name.setter("Definitely not Steve"); // Change the value

unobserve

args:

  • ReturnType<typeof reactive>

Removes all observers from the reactive Proxy. This will not be called recursively for properties.

watchEffect

args: function returns: a stop function

This works similarly to Vue3 watchEffect: To apply and automatically re-apply a side effect based on reactive state, we can use the watchEffect method. It runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed.

Example

const count = reactive(0);
watchEffect(() => console.log(getValue(count)));
// -> logs 0

count(1);
// -> logs 1

getValue

args: ReturnType<typeof reactive>
returns: currently set value

Returns the value inside the the Proxy. getValue is needed because a reactive Proxy does not have access to the value.

Example

const person = reactive({ name: "Steve" });
console.log(getValue(person.name)); // Get curent name

setAsyncUpdate

args:

  • ReturnType<typeof reactive>
  • boolean

Sets the schedule behavior for DOM Updates that are connected to this Proxy. This will not be called recursively for properties.

unset

args: ReturnType<typeof reactive>

Deletes the Proxy object and removes all observers (both recursively). This is important for keeping memory low. This happens by setting the value to null.

ternary

args:

  • condition: function | ReturnType<typeof reactive>
  • trueVal: any
  • falseVal: any
  • proxy?: ReturnType<typeof reactive>

returns: ReturnType<typeof reactive>

In order to track a ternary in a template literal, this function has to be used. The proxy parameter (4th) is optional, if the first parameter is a reactive Proxy. Otherwise, the condition function is being executed, whenever the Proxy value changes, which will update the DOM to either the trueVal or the falseVal, depening on the return value. If trueVal is a function, then it will be executed. The same applies for falseVal.

Example

const toggleValue = reactive(true);
render(html` <button>${ternary(toggleValue, "ON", "OFF")}</button> `);
setTimeout(() => toggleValue(false), 1e3); // Will re-validate the ternary after 1s

hydro

The actual Proxy in the library. This cannot be used with getValue, observe, ternary or unset but it offers the same functionality in a different manner.
Special behaviour for promises: the library will await promises and will set its value to the unwrapped value. If the Promise rejects, the value will be unset.
Special behaviour for null: null will delete all properties and observer for a value

properties:

  • isProxy: boolean (default: true)
  • asyncUpdate: boolean, (default: true, derived from globalSchedule)
  • observe: function, args: string as key, fn: function
  • unobserve: function, args: string | undefined - unobserve key or all, , fn: function
  • getObservers: function, returns: map with all observers

Example

hydro.fruit = "Banana";
render(html`<span>{{ fruit }}</span>`);

view

Render the elements whenever the data changes. It will handle the operation for deletion, addition, swapping etc. This defaults to a non-keyed solution but it can be changed by calling setReuseElements with false.

args:

  • root: string (CSS selector)
  • data: ReturnType<typeof reactive>
  • renderFunction: function, args: item: any, i: number

Example

const data = reactive([{ id: 4, label: "Red Onions" }])
view('.table', data, (item, i) => <tr>Reactive: {data[i].id}, Non-reactive: {item.id}</tr>)

emit

args:

  • event: string
  • data: any
  • who: EventTarget
  • options: object (default: { bubbles: true })

Emits an event from the EventTarget who. This event bubbles by default.

Example 1

render(
  html`<div onfav=${({ detail: cake }) => console.log(cake)}>
    <p onclick=${({ target }) => emit("fav", "Cheesecake", target)}>
      Click to emit your favorite cake 🍰
    </p>
  </div>`
);

Example 2

// With event options
render(
  html`<div onfav=${({ detail: cake }) => console.log(cake)}>
    <p
      onclick=${{
        options: {
          once: true,
        },
        event: ({ target }) => emit("fav", "Strawberry Cake", target),
      }}
    >
      Click to emit your favorite cake 🍰
    </p>
  </div>`
);

$

Shortcut for querySelector.

$$

Shortcut for querySelectorAll.

internals

An object with internal data / functions for testing or deeper dives for developers. This only includes a compare function for DOM Elements at the moment.

Attributes

  • bind: binds a piece of data to an element. This is only useful, when an element should be removed from the DOM, when the data is being set to null.

Example

const data = reactive({ name: "Pet" });
render(html`<p bind=${data}>${data.name}</p>`);
setTimeout(() => unset(data), 1000); // will remove the element

SSR

For Integrations, please refer here: https://github.com/Krutsch/hydro-js-integrations

Further

To enable HTML highlighting in your files, you could use leet-html in VS Code.

changelog

Changelog

1.8.8 2025-05-29

  • fix missing function

1.8.7 2025-05-29

  • fix bug when rendering the html element in a html element

1.8.6 2025-05-22

  • update bool attr list and testing elements

1.8.5 2025-05-21

  • undo change as the error may be in the integration

1.8.4 2025-05-21

  • fix document instance for jsdom

1.8.3 2025-05-16

  • move integrations to own package

1.8.2 2025-05-12

-fix: move happy-dom and jsdom to deps

1.8.1 2025-05-12

  • fix: build the files and fix types

1.8.0 2025-05-12

  • ship hydro-js/server for better Vite SSR integration

1.7.1 2025-04-08

  • fix reactive bug when using SSR with happy-dom

1.7.0 2025-04-07

  • feat: make library happy-dom compatible

1.6.0 2025-04-04

  • improve prev change
  • remove code from previous change
  • feat: make library jsdom compatible

1.5.24 2025-04-02

1.5.23 2025-04-02

  • make use of window object in order to work on the server for upcoming SSR

1.5.22 2024-09-29

  • export bool attrs

1.5.21 2024-09-25

  • fix a TypeScript type

1.5.20 2024-09-19

  • fix correct boolean setting for attr
  • add new internal variables for a new project
  • improve scheduling

1.5.19 2024-06-07

  • add function to toggle whether connected should be considered (defaults to false). This can be useful with non-rendered elements in combination with a router

1.5.18 2024-02-21

  • enable DocumentFragment in h function

1.5.17 2024-02-16

  • little refactor of html function

1.5.16- 2024-02-16

  • Fix bug where false variable matched incorrectly in the switch

1.5.15- 2024-02-14

  • Fix bug where web components where not registered correctly in the h function

1.5.14- 2023-04-04

  • feat: add inert boolean attribute

1.5.13- 2022-04-30

  • Undo attribute set twice bugfix as the reactive value was called falsly

1.5.12- 2022-04-30

  • Fix bug where attribute was set twice
  • Fix bug where bool attribute was incorrectly set on JSX elements with reactive function

1.5.11- 2022-04-30

  • Fix bug where old events where not correctly removed

1.5.10- 2022-01-27

  • Fix intense memory bug, that lead to Bug too

1.5.9- 2022-01-23

  • Minor perf upgrade
  • Fix bug where setReactivity returned too early

1.5.8- 2022-01-19

  • Minor perf upgrade
  • Added code example

1.5.7- 2022-01-06

  • Performance and Memory improvements

1.5.6- 2022-01-05

  • Repair h function

1.5.5- 2022-01-02

  • Improve TypeScript types of $ and $$

1.5.4- 2021-12-30

  • Perf Tweak in view function

1.5.3- 2021-12-30

  • Performance Improvements

1.5.2- 2021-12-23

  • Fix bug where attributes where not copied from (html|head|body) element

1.5.1- 2021-12-23

  • Fix bug where document-fragment was added to internal tracking list.

1.5.0- 2021-12-22

  • Fix bug where two-way bindings did not work on other input element types (type file: can only be set by a user)

1.4.7- 2021-12-15

  • Bug fix, where variable was undefined
  • Rewrite using less inline functions
  • Add two pseudo-boolean attributes: draggable and spellcheck

1.4.6- 2021-09-22

  • Update deps

1.4.5- 2021-07-27

  • Change checkbox two-way behavior: this will now take a boolean instead of an array associated with the name on the element.
  • Fix ternary bug in combination with two-way logic

1.4.4- 2021-06-18

  • Pass children in h function as prop

1.4.3- 2021-05-28

  • Use another parser revert

1.4.2- 2021-05-27

  • Use another parser
  • Use cache for setReactivity
  • Add minor tweaks

1.4.1- 2021-05-21

  • Use better RegExp
  • Little performance gain

1.4.0- 2021-05-06

  • Refactor h function. Breaking Change: Does not really support SVG anymore. Use html function for this case.
  • Performance improvements
  • Add non-keyed solution and default to it

1.3.5- 2021-05-03

  • Refactor Memory Cleanup
  • Add functionality for keyed solutions

1.3.4- 2021-04-28

  • Run build

1.3.3- 2021-04-28

  • Add template function
  • Revert using setTimeout for setReactivity

1.3.2- 2021-04-28

  • Fix critical bug

1.3.1- 2021-04-28

  • Update docs with bind
  • Schedule reactivity better

1.3.0- 2021-04-14

  • Fix props bug with h function
  • Refactor scheduler

1.2.14- 2021-03-15

  • Update deps
  • Fix bug regarding scope issues

1.2.13- 2021-02-20

  • Fix bug where a passed in function would not work in JSX

1.2.12- 2021-01-30

  • Add experimental h function

1.2.11- 2021-01-28

  • Fix bugs where diffing did not work well with document fragments

1.2.10- 2021-01-25

  • Add new function setShouldSetReactivity

1.2.9- 2021-01-24

  • Fix bug where html was not diffable

1.2.8- 2021-01-24

  • Add support for html, head and body element. The html function can create every element now

1.2.7- 2021-01-17

  • Add test and update README.md
  • Refactor HTML function to use String.raw
  • Add new function watchEffect

1.2.6- 2021-01-01

  • Add examples for README.md
  • Move test file to dist
  • Fix newValue bug in 'observe'

1.2.5- 2020-12-22

  • Add feat: display empty string instead of undefined for undefined reactive values
  • Bump deps

1.2.4- 2020-12-17

  • Export setReactivity

1.2.3- 2020-12-04

  • Remove Web Worker goal - tried options:
    • @ampproject/worker-dom does not cover enough APIs to make it possible
    • via.js does not really make it possible to use the windows object
    • DOM-Proxy made it possible to run ~50% of the code but was not efficient enough to make it worthwhile
  • Remove Broadcast Channel goal, because this is something that the App Developer has to take care of. Structured cloning will fail for the most important calls
  • Add MIT License

1.2.2- 2020-11-29

  • Improve performance

1.2.1- 2020-11-29

  • Add deleted .cjs file
  • Add CHANGELOG.md
  • Refactor schedule logic

1.2.0 - 2020-11-24

  • Add Code Coverage for 100%
  • Improve types
  • Fix bind bug
  • Improve prformance when comparing elements
  • Add functions asyncUpdate and unset for reactive Proxy (this functionality already existed on hydro)

1.1.1 - 2020-11-24

  • Improve performance
  • Add support for custom bind attribute, that will remove a DOM Element when the Proxy will be removed

1.1.0 - 2020-11-22

  • Replace internal testing tool with @web/test-runner
  • Fix tests

1.0.9 - 2020-11-22

  • Add npx support to bootstrap starter project

1.0.8 - 2020-11-21

  • Update README.md and code comments

1.0.7 - 2020-11-20

  • Add GC Test
  • Add support for boolean attributes
  • Support export dual module
  • Update README.md

1.0.6 - 2020-11-16

  • Change window.$ to exported value

1.0.5 - 2020-11-16

  • Update README.md and code comments
  • Add test

1.0.4 - 2020-11-15

  • Add better Support for Promises and DOM Nodes
  • Update README.md

1.0.3 - 2020-11-15

  • No relevant changes

1.0.2 - 2020-11-15

  • Add Code examples
  • Update README.md
  • Fix test

1.0.1 - 2020-11-15

  • Update README.md