Skip to content

anh-ld/nho

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

33 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“Œ Nho

Nho (nhỏ | small in Vietnamese) is a tiny library designed for easy Web Component development.

Why Nho?

  • Writing a Web Component using vanilla JavaScript can be such tedious. Alternatively, popular libraries can be overkill and overweighted (4KB+) for creating small components like a "Buy now" button or a cart listing drawer

  • Nho simplifies the process by staying lightweight, removing unnecessary APIs, and using a simple DOM diffing algorithm

Features

  • 1.2KB gzipped (1210 bytes for esm and 1443 bytes for umd)
  • Simple API inspired from Vue
  • 100% test coverage

Example

Limitation

  • In order to stay small, Nho skips few advanced features found in popular front-end frameworks like key, Fragments, memo. The DOM diffing algorithm is somewhat basic, but it is fast enough for small projects. If your components become too complex, consider other options

Installation

using npm

First, run

npm install nho

The package is published on npm, so other package managers (e.g. yarn, pnpm, bun) still work

then

import { Nho } from 'nho';
class MyCounterChild extends Nho {}

using CDN

First, add script to the html file

<script src="https://unpkg.com/nho"></script>

then, add script to the html file

<script>
  let Nho = nho.Nho;
  class MyCounterChild extends Nho {}
</script>

Usage

/* main.js */

/* declare global style. Styles will be injected to all Nho Elements */
Nho.style = `
  .box {
    background: blue;
    color: yellow;
  }
`

class MyCounterChild extends Nho {
  render(h) {
    /* bind value from props */
    return h`<div>Child: ${this.props.count}</div>`
  }
}

class MyCounter extends Nho {
  setup() {
    /* this method runs before mount */

    /* create component state using "this.reactive", state must be an object */
    this.state = this.reactive({ count: 1 });

    /* only use ref for storing DOM reference */
    this.pRef = this.ref();

    /* effect */
    this.effect(
      // effect value: fn -> value
      () => this.state.count,
      // effect callback: fn(old value, new value)
      (oldValue, newValue) => {
        console.log(oldValue, newValue)
      }
    )
  }

  onMounted() {
    /* this method runs after mount */
    console.log('Mounted');
  }

  onUpdated() {
    /* this method runs after each update. */
    console.log('Updated');

    /* P tag ref */
    console.log('P Ref', this.pRef?.current);
  }

  onUnmounted() {
    /* this method runs before unmount */
    console.log('Before unmount');
  }

  addCount() {
    /* update state by redeclaring its key-value. Avoid updating the whole state. */
    this.state.count += 1;
  }

  render(h) {
    /* this method is used to render */

    /*
      JSX template alike
      - Must have only 1 root element
      - Bind state / event using value in literal string
      - Pass state to child element using props with 'p:' prefix
     */
    return h`
      <div class="box">
        <p ref=${this.pRef}>Name: ${this.state.count}</p>
        <button onclick=${this.addCount}>Add count</button>
        <my-counter-child p:count=${this.state.count + 5}></my-counter-child>
      </div>
    `
  }
}

customElements.define("my-counter", MyCounter);
customElements.define("my-counter-child", MyCounterChild);
/* index.html */
<my-counter></my-counter>

Development (Bun)

  • Install dependencies: bun install
  • Build the library bundles: bun run build
  • Build/watch the example app: bun run dev (outputs to example/dist, open example/index.html)
  • Serve the example folder: bun run serve (default http://localhost:3000)
  • Run tests: bun test

Notice

  • Avoid using these below properties inside Nho Component since they are reversed Nho's properties
setup, onMounted, onUnmounted, onUpdated, effect, ref, reactive, render, style
any property that starts with `_`

How it works

  • It's better to dive into the code, but here is a diagram about how Nho works
flowchart LR
  subgraph Main
    direction LR
    A[State change] --> B[Proxy set trap]
    B --> C[Batched via requestAnimationFrame]
    C --> D[Render template]
    D --> E[Parse to DOM]
    E --> F[Diff & patch current DOM]
    F --> G[Bind props, events, refs]
    G --> H[Run lifecycle + effects]
  end

  subgraph Cache[Cache + bind]
    direction TB
    N1[/Collect p: props and on* handlers while rendering/]
    N2[/Attach cached handlers and refs/]
    N1 --> N2
  end
  D -. cache .-> N1
  N2 -. apply .-> G

  R["Diff steps (order):<br>1. Trim extra children<br>2. Compare each new child by index<br>3. Clone if missing<br>4. Replace if tag/text differs<br>5. Sync attributes in place"]
  F -. uses .-> R
Loading

Mentions

About

πŸ“Œ 1KB Web Component Abstraction

Topics

Resources

Stars

Watchers

Forks