Coder Social home page Coder Social logo

cona's Introduction

Cona

npm version npm downloads

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

local 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 ๐Ÿ’›

cona's People

Contributors

pin705 avatar

Stargazers

 avatar

Watchers

 avatar

Forkers

fidian

cona's Issues

Remove styles or make them optional

To be fair, I do not know if you'd want to take this on because it seems to be going in a different direction than where you intended for your library. Feel free to tell me that this niche isn't important to you.

When developing for a strict "Content Security Policy", using inline styles, eval, and other techniques are prohibited by the browser. In Cona's case, the problem comes in with the global styles being added. You can see the problems reported by adding a meta tag to `playground/index.html'.

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

Also, you will need to download the list of albums and change fetch() to load the local file. When you do this, the browser will report an error.

image

To avoid this error, I would need to do the following:

  1. Move the styles to embedded CSS or a local CSS file.
  2. Stop Cona from using a global style.
  3. Stop Cona from using a shadow root so the external styles can be applied, unfortunately breaking <slot>.

Item 1 is easy enough. I have edited Cona and added an if to avoid item 2. I've also removed the shadow root, but this isn't a complete solution because the playground doesn't work. Additional effort would be needed to ensure everything works as expected.

Is Content Security Policy a concern for your lightweight framework?

Memory leak in Cona._c

Values are constantly added to Cona._c but are never expired. The longer one uses Cona, the more memory is consumed. You can replicate this with the playground by simply checking the used memory via performance.memory and then go into and out of an album repeatedly. Viewing performance.memory will show an increase in memory. The more you toggle back and forth to the albums and back to the index, the more memory is consumed.

I attempted to change this to use an instance-level this._c but I don't understand the code well enough to make that work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.