npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

dommie

v3.0.9

Published

A lightweight library for building reactive UIs pure JavaScript.

Downloads

43

Readme

Dommie

  • Dynamic creation of HTML elements
  • Component management with lifecycle hooks
  • State and subscription handling
  • Efficient DOM updates using DiffDOM

With Dommie, you can create snappy, performant UIs without the overhead of larger frameworks.

Documentation

Please refer to the Dommie Tutorial for usage.

Why Dommie?

Dommie is designed for developers who need a simple yet powerful way to build reactive UIs. It's a great fit for small to medium projects where simplicity, flexibility, and performance are key.

Key Benefits:

  • Simple API: Minimalistic and intuitive for quick integration.
  • Flexible: Works seamlessly with modern build tools like Vite, Webpack, and Rollup.
  • Performant: Dommie's state subscription model ensures only necessary updates are made to the DOM.

Installation

npm install dommie

-- or --

yarn add dommie

-- or --

import app from "https://unpkg.com/[email protected]/build/min/app.js"
// Or use a script tag in your HTML file. Check the latest version on unpkg.com

etc.

Quick Start

Here’s a simple example of how to use Dommie to build a component:

import app from "dommie";
import type { Template } from "dommie";

const myComponent = (h: Template) => {
  const { component, div, h1, text, a, br } = h;

  return component(() => {
    div({ style: { backgroundColor: "pink" }, text: "I am a div" }, () => {
      h1({ text: "I am an h1. I am a child of the div above." });
      text("I am a text node!");
      br();
      a({ href: "https://google.com", text: "I am a link to google" });
    });
  });
};

app(myComponent, "#app");

Example Apps

The easiest way to learn Dommie is to look at some example apps. Here are a few examples to get you started:

Subscriptions

Dommie enables reactive UIs through a simple state and subscription model. When the state updates, subscribed elements re-render automatically.

import app from "dommie";
import type { Template } from "dommie";

const hello = (h: Template) => {
  const { div, button, component } = h;

  return component(({ state }) => {
    const count = state(0);
    const updateCount = () => count.value++;

    div(() => {
      div({ subscribe: count, text: () => count.value });
      button({ text: "Update Count", click: updateCount });
    });
  });
};
app(hello, "#app");

You can also subscribe multiple elements to different states, ensuring your UI stays in sync.

import app from "dommie";
import type { Template } from "dommie";

const hello: Component = (h: Template) => {
  const { div, button, component } = h;
  return component(({ state }) => {
    const count = state(0);
    const title = state("Hello World!");

    const updateCount = () => count.value ++;
    const updateTitle = () => (title.value = "Hello Dommie!");

    div({ subscribe: [count, title] }, () => {
      div({ text: () => count.value });
      div({ text: () => title.value });
    });

    button({ text: "Update Count", click: updateCount });
    button({ text: "Update Title", click: updateTitle });
  });
};

app(hello, "#app");

The first argument of an event listener function is always an event object. If you want to pass additional arguments, you can pass them as an array.

import app from "dommie";
import type { Template } from "dommie";

const hello = (h: Template) => {
  const { div, button, component } = h;

  return component(({ state }) => {
    const count = state(0);
    const updateCount = (_: Event, newNumber: number) => {
      count.value += newNumber;
    };

    div(() => {
      div({ subscribe: count, text: () => count.value });
      button({ text: "Increment", click: [updateCount, [1]] });
      button({ text: "Decrement", click: [updateCount, [-1]] });
    });
  });
};

app(hello, "#app");

You can pass state to a child component, and unlike many other libraries, the child component is allowed to update the state of the parent component.

Loops

Dommie supports loops with any iterable, making it easy to dynamically generate lists of elements.

import app from "dommie";
import type { Template } from "dommie";

const hello = (h: Template) => {
  const { div, component } = h;
  const names = ["Alice", "Bob", "Charlie"];

  return component(() => {
    div(() => {
      for (const name of names) {
        div({ text: name });
      }
    });
  });
};

app(hello, "#app");

When using loops with dynamic state, don’t forget to assign unique IDs for efficient updates.

import app from "dommie";
import type { Template } from "dommie";

const hello = (h: Template) => {
  const { div, button, component } = h;
  const names = ["Alice", "Bob", "Charlie"];

  return component(({ state }) => {
    const count = state(0);
    const updateCount = () => count.value++;

    div(() => {
      div({ subscribe: count }, () => {
        for (const name of names) {
          div({ id: name, text: `${name}-${count.value}` });
        }
      });
      button({ text: "Update Count", click: updateCount });
    });
  });
};

app(hello, "#app");

Lifecycle Hooks

afterMounted

The afterMounted lifecycle hook is called after the component has been mounted to the DOM. This is useful for running code that requires the component to be mounted, such as fetching data from an API.

import app from "dommie";
import type { Template } from "dommie";

const hello = (h: Template) => {
  const { div, component } = h;

  return component(({ afterMounted, state }) => {
    const catData = state<null | string>(null);
    const updateCatData = (data: string) => catData.value = data;

    afterMounted(async () => {
      const res = await fetch("https://meowfacts.herokuapp.com/");
      const data = await res.json();
      updateCatData(data.data[0]);
    });
    div({ text: () => catData.value || "Loading...", subscribe: catData });
  });
};

app(hello, "#app");

afterDestroyed

The afterDestroyed lifecycle hook is called after the component has been removed from the DOM. This is useful for cleaning up resources, such as event listeners.

import app from "dommie";
import type { Template} from "dommie";

const hello = (h: Template) => {
  const { div, component } = h;

  return component(({ state, afterDestroyed }) => {
    const count = state(0);
    const updateCount = () => count.value ++;
    const interval = setInterval(updateCount, 1000);

    afterDestroyed(() => {
      clearInterval(interval);
    });

    div({ text: () => count.value, subscribe: count });
  });
};

app(hello, "#app");

Side Effects

Use the subscribe function provided in your component to run side effects when a state changes. subscribe takes a callback function and an array of states. The callback function is called whenever the state changes.

import app from "dommie";
import type { Template } from "dommie";

const hello = (h: Template) => {
  const { div, button, component } = h;

  return component(({ state, subscribe }) => {
    const count = state(0);
    const updateCount = () => count.value++;
    subscribe(() => {
      // This will run whenever the count changes
      console.log("Count has changed to", count.value);
    }, [count]);

    div({ subscribe: count, text: () => count.value });
    button({ text: "Increment", click: updateCount });
  });
};

app(hello, "#app");

Refs

Refs are a way to access the underlying DOM element of a component. You can create a ref using the ref function and pass it to the ref property of an element. You can then access the DOM element using the ref function.

import app from "dommie";
import type { Template } from "dommie";

const hello = (h: Template) => {
  return h.component(({ ref, afterMounted }) => {
    const inputRef = ref();

    // Focus on the input after it is mounted
    afterMounted(() => {
      inputRef()?.focus();
    });

    h.input({ ref: inputRef, type: "text" });
  });
};

app(hello, "#app");

Child Components

Dommie allows you to break your UI into smaller components, which can pass and update state between parent and child components.

// components/Hello.ts
import type { Template } from "dommie";

const Hello = (h: Template) => {
  const { div } = h;
  return div({ text: "Hello World!" });
};

export default Hello;
// components/App.ts
import app from "dommie";
import type { Template } from "dommie";
import Hello from "./Hello";

const App = (h: Template) => {
  const { div, component } = h;
  return component(() => {
    div(() => {
      Hello(h);
    });
  });
};

app(App, "#app");

Step-by-Step Guide to SPA with Dommie

1. Setting up Routes

Dommie allows you to define multiple routes in your SPA using the router function. Routes are paths in the URL that map to specific components. The router takes an object where each key is a path and the value is a component to render.

Here’s an example:

import app, { router } from "dommie";
import type { Template } from "dommie";

const Home = (h: Template) => {
  const { div, component } = h;
  return component(() => {
    div({ text: "Welcome to the Home page!" });
  });
};

const About = (h: Template) => {
  const { div, component } = h;
  return component(() => {
    div({ text: "This is the About page." });
  });
};

2. Integrating the Router into the Main Component In your main app component, you call the router() function inside a component block. The router takes care of switching between components based on the current URL path.

const App = (h: Template) => {
  const { div, component } = h;
  return component(() => {
    div(() => {
      router({
        "/": Home,    // When the path is '/', render the Home component
        "/about": About, // When the path is '/about', render the About component
      });
    });
  });
};

app(App, "#app", { spa: true });

3. Navigating Between Pages

To navigate between routes, Dommie provides the r object with a go function. You can use this to programmatically navigate between different pages without reloading the page.

Here’s an example of how you could add buttons to navigate between pages:

const Home = (h: Template) => {
  const { div, button, component } = h;
  return component(({ r }) => {
    div({ text: "Welcome to the Home page!" });
    button({ text: "Go to About", click: () => r.go("/about") }); // Navigate to the About page
  });
};

const About = (h: Template) => {
  const { div, button, component } = h;
  return component(({ r }) => {
    div({ text: "This is the About page." });
    button({ text: "Go to Home", click: () => r.go("/") }); // Navigate back to the Home page
  });
};

4. Enabling SPA Mode

In the call to app(), the third argument is an options object where you can enable the SPA mode by setting spa: true. This tells Dommie to manage the URL changes internally without reloading the page.

app(App, "#app", { spa: true });

5. Wildcard Routes (Optional)

You can define routes with dynamic segments using the * wildcard character. This allows matching paths like /blog/123/comments/456 where the numbers (or other path parts) can vary.

Here’s an example of using a wildcard route:

const Blog = (h: Template) => {
  return h.component(({ r }) => {
    console.log(r.path.value);  // Full path, e.g. "/blog/123/comments/456"
    console.log(r.pathVariables.value); // Array of variables from the path, e.g. ["123", "456"]
    console.log(r.pathVariablesMap.value); // Object with named variables, e.g. { blog: "123", comment: "456" }
  });
};

router({
  "/": Home,
  "/about": About,
  "/blog/*": Blog, // This will match any path that starts with /blog/
});

Key Points Recap:

  • Router Setup: Use router() to define the mapping of URL paths to components.
  • Navigation: Use the r.go(path) function to change routes dynamically.
  • SPA Mode: Enable SPA by passing { spa: true } to app(), which prevents full page reloads.
  • Wildcard Routes: Use * in paths to capture dynamic parts of the URL.

Get Involved

Dommie is open-source and welcomes contributions. If you find bugs or have ideas for improvements, feel free to open an issue or submit a pull request.