Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

zheksoon/htm-ally

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

htm-ally brotli size

500 bytes web app micro-framework with a syntax you already know

  • Just HTML, nothing new to learn
  • No need for transpiler or compiler
  • Components, mount, unmount, and update hooks
  • Element refs, event handlers, attributes, and styles
  • Class helper
  • Fragments and arrays
  • XSS safe

Install

npm install --save htm-ally

yarn add htm-ally

and then:

import { html } from "htm-ally";

// or directly from unpkg

import { html } from "https://unpkg.com/htm-ally?module";

Usage

The output of html is just an HTMLFragment, so you can use it anywhere you can use a fragment:

const fragment = html`<div>hello</div>`;

document.getElementById("app").append(fragment);

Nesting fragments (or arrays of fragments) are supported:

const makeNested = (number) => html`
  <h1>hello</h1>
  <div>
    <p>${number}</p>
  </div>
`;

const nested = makeNested(100);

const fragment = html`
  <div>
    ${nested}
    ${[1, 2, 3].map(makeNested)}
  </div>
`;

Just insert interpolation with an object to define attributes and event handlers. Other attributes of the element are preserved:

const onclick = () => alert("clicked");

const fragment = html`
  <button id="button" ${{ onclick, class: "btn", disabled: false }}>
    Click me
  </button>
`;

Classes can also be defined as arrays or objects:

const primary = true;
const isDisabled = false;

const classNames = ["btn", primary && "primary", { disabled: isDisabled }];

const fragment = html`
  <button ${{ class: classNames }}>Click</button>
`;

Styles are defined as an object (with camelCase):

const fragment = html`
  <button ${{ style: { color: "red", fontWeight: "bold" } }}>Click</button>
`;

Refs can be defined with a function:

const inputRef = (el) => (inputRef.el = el);

const onChange = () => {
  console.log(inputRef.el.value);
};

const fragment = html`
  <input type="text" ${{ ref: inputRef, onchange: onChange }} />
`;

Components are just functions that return a fragment

const Component = () => html`<div>hello</div>`;

const fragment = html`<div>${Component}</div>`;

Components have access to an element it's mounted to (a slot element). The element contains update, mount, and unmount functions:

let count = 0;

const Counter = (parent) => {
  const onClick = () => {
    count += 1;
    parent.update();
  };

  return html`<button ${{ onclick: onClick }}>Count: ${count}</button>`;
};

document.getElementById("app").append(html`${Counter}`);

For the mount and unmount hooks, see the Timer example.

Note: text input components cannot be updated with the parent.update() as the cursor position will be reset.

Components can have a local state in a closure or on the parent node:

const Counter = (initialValue) => {
  let count = initialValue;

  return (parent) => {
    const onClick = () => {
      count += 1;
      parent.update();
    };

    return html`<button ${{ onclick: onClick }}>Count: ${count}</button>`;
  }
};

document.getElementById("app").append(html`${Counter(100)}`);

The state isn't preserved when the parent component updates (as there is no reconciliation), so it's recommended to use a global state to store states for each component.

Examples

All examples can be found in the CodeSandbox: link

Counter

Shows event handlers, attributes, component update

import { html } from "htm-ally";

let count = 0;

const Counter = (parent) => {
  const inc = () => {
    count += 1;
    parent.update();
  };

  const dec = () => {
    count -= 1;
    parent.update();
  };

  return html`
    <button ${{ onclick: dec, disabled: count <= 0 }}>-</button>
    ${count}
    <button ${{ onclick: inc, disabled: count >= 10 }}>+</button>
  `;
};

document.getElementById("app").append(html`${Counter}`);

Todo list

Shows ref usage, array map, and component update.

let todos = [];

const TodoList = () => {
  let inputRef = (el) => (inputRef.el = el);

  return (parent) => {
    const addTodo = () => {
      const text = inputRef.el.value;
      inputRef.el.value = "";
      todos.push(text);
      parent.update();
    };

    return html`
      <input type="text" ${{ ref: inputRef }} />
      <button ${{ onclick: addTodo }}>Add</button>
      <ul>
        ${todos.map((text) => html`<li>${text}</li>`)}
      </ul>
    `;
  };
};

document.getElementById("app").append(html`${TodoList()}`);

Timer

Shows mount and unmount hooks

let show = false;

const TimerToggle = (parent) => {
  const toggleShow = () => {
    show = !show;
    parent.update();
  };

  return html`
    <button ${{ onclick: toggleShow }}>${show ? "Hide" : "Show"}</button>
    ${showTimer && Timer}
  `;
};

let interval;
let time = 0;

const Timer = (parent) => {
  // start timer on mount
  parent.mount = () => {
    interval = setInterval(() => {
      time += 1;
      parent.update();
    }, 1000);

    time = 0;
    parent.update();
  };

  // stop timer on unmount
  parent.unmount = () => {
    clearInterval(interval);
  };

  return html`Timer: ${time}s`;
};

document.getElementById("app").append(html`${TimerToggle}`);

Advanced Todo list

See the CodeSanbox

Author

Eugene Daragan

License

MIT

About

500 bytes web app micro-framework with a syntax you already know

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.