Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/Krutsch/hydro-js

A lightweight (5K compressed) reactive UI library via template literal tags.
https://github.com/Krutsch/hydro-js

dom hydro-js javascript library reactive

Last synced: 15 days ago
JSON representation

A lightweight (5K compressed) reactive UI library via template literal tags.

Lists

README

        

100% Coverage

# hydro-js

> A lightweight (below 5K compressed) reactive UI library via template literal tags.
Support in all modern Browsers.

## Installation

To bootstrap a new app:

```properties
$ npm init hydro-app@latest // or npx create-hydro-app@latest
```

or integrate in an existing app:

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

Alternatively you can use a CDN:

```html

import { render, html } from "https://unpkg.com/hydro-js";

```

## Examples

- [Simple Counter](https://codesandbox.io/s/hydro-js-counter-mwpf4?file=/index.js)
- [Reactive CSS](https://codesandbox.io/s/reactive-styles-916pr?file=/index.js)
- [Two Way Data Binding](https://codesandbox.io/s/hydro-js-two-way-data-binding-observe-extpq?file=/index.js)
- [Show](https://codesandbox.io/s/show-widzf?file=/index.js)
- [Destructure Attributes](https://codesandbox.io/s/hydro-js-destructure-attributes-zhcx7?file=/index.js)
- [Ternary](https://codesandbox.io/s/hydro-js-ternary-c01h2?file=/index.js)
- [Promise Handling](https://codesandbox.io/s/hydro-js-promise-handling-eo90f?file=/index.js)
- [Nested Reactivity](https://codesandbox.io/s/hydro-js-nested-reactivity-myjpt?file=/index.js)
- [Nested Reactivity 2](https://codesandbox.io/s/hydro-js-nested-reactivity-2-6xy42?file=/index.js)

## 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 ``, ``, ``, `` and ``. 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

```js
html`

Text

`;
```

### render

args:

- new Element (`ReturnType | reactive Proxy`)
- old Element (`ReturnType | 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

```js
render(html`

Text

`);
```

### 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. `` 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

```js
//

{{value}}

in HTML
const template = $("#value");
hydro.value = "Hello World";
setReactivity(template);
```

#### Example 2 (with event)

```js
//

{{value}}

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

### onRender

args:

- `function`
- elem (`ReturnType`)
- ...args for passed `function`

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

#### Example

```js
const elem = html`

Hello World

`;
onRender(() => console.log("rendered elem"), elem);
render(elem);
```

### onCleanup

args:

- `function`
- elem (`ReturnType`)
- ...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

```js
const elem = html`

Hello World

`;
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

```js
const data = reactive({ value: 42 });
render(html`

${data.value} €

`);
data((prev) => (prev.value = 21)); // Change the value
```

### observe

args:

- `ReturnType`

- `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:

```js
observe(person.name, ...)
```

#### Example

```js
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`

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

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

count(1);
// -> logs 1
```

### getValue

args: `ReturnType`

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

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

### setAsyncUpdate

args:

- `ReturnType`

- `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

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`
- trueVal: `any`
- falseVal: `any`
- proxy?: `ReturnType`

returns: `ReturnType`

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

```js
const toggleValue = reactive(true);
render(html` ${ternary(toggleValue, "ON", "OFF")} `);
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

```js
hydro.fruit = "Banana";
render(html`{{ fruit }}`);
```

### 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`

- renderFunction: `function`, args: item: `any`, i: `number`

#### Example

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

### 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

```js
render(
html`

console.log(cake)}>

emit("fav", "Cheesecake", target)}>
Click to emit your favorite cake 🍰


`
);
```

#### Example 2

```js
// With event options
render(
html`

console.log(cake)}>

emit("fav", "Strawberry Cake", target),
}}
>
Click to emit your favorite cake 🍰


`
);
```

### \$

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

```js
const data = reactive({ name: "Pet" });
render(html`

${data.name}

`);
setTimeout(() => unset(data), 1000); // will remove the element
```

## Further

To enable HTML highlighting in your files, you could use [leet-html](https://marketplace.visualstudio.com/items?itemName=EldarGerfanov.leet-html) in VS Code.