cona

Web Component Nano Simple API inspired from Vue typescript friendly

MIT License

Stars
1
Committers
2

Cona

Web Component Nano Simple API inspired from Vue typescript friendly

Table of Contents

Why Cona?

  • Writing a Web Component (WC) using vanilla JavaScript can be tedious. Alternatively, popular WC libraries can be overkill and overweight (4KB+) for creating small components like a "Buy now" button or a cart listing.
  • Cona simplifies the process by staying lightweight, removing unnecessary APIs, and using a simple DOM diffing algorithm.

Features

  • No dependencies
  • 1.3KB gzipped.
  • Simple API inspired by Vue.

Usage

Installation

# ✨ Auto-detect
npx nypm install @pinjs/cona

# npm
npm install @pinjs/cona

# yarn
yarn add @pinjs/cona

# pnpm
pnpm install @pinjs/cona

# bun
bun install @pinjs/cona

Using CDN

First, add the script to the HTML file: <script src="https://unpkg.com/@pinjs/cona"></script> Then, add your component script:

<script>
  let Cona = cona.Cona;
  class MyCounterChild extends Cona {}
</script>
<script>
  let Cona = cona.Cona;
  class MyCounterChild extends Cona {}
</script>

Example

/* main.js */

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

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

class MyCounter extends Cona {
  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)
      }
    )

    /* Watch */
    this.watch(
      () => this.state.count,
      (newValue, oldValue) => {
        console.log(`Count changed from ${oldValue} to ${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 />

Computed

class CounterComponent extends Cona {
  private state: { count: number; doubleCount: () => number };

  constructor() {
    super();
    this.state = this.reactive({ count: 0 });
    this.state.doubleCount = this.computed(() => this.state.count * 2);
  }

  setup() {
    // Watching the state.count property
    this.watch(
      () => this.state.count,
      (newValue, oldValue) => {
        console.log(`Count changed from ${oldValue} to ${newValue}`);
      }
    );

    // Watching the computed doubleCount property
    this.watch(
      this.state.doubleCount,
      (newValue, oldValue) => {
        console.log(`DoubleCount changed from ${oldValue} to ${newValue}`);
      }
    );
  }

  render() {
    return this._render`
      <div>
        <button p:onclick=${this.increment}>Increment</button>
        <p>Count: ${this.state.count}</p>
        <p>Double Count: ${this.state.doubleCount()}</p>
      </div>
    `;
  }

  private increment() {
    this.state.count++;
  }
}

// Define the custom element
customElements.define('counter-component', CounterComponent);

Development

  • Clone this repository
  • Install latest LTS version of Node.js
  • Enable Corepack using corepack enable
  • Install dependencies using pnpm install
  • Run interactive tests using pnpm dev

License

Published under the MIT license. Made by community 💛