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 🙏

© 2026 – Pkg Stats / Ryan Hefner

simplicit

v0.5.0

Published

A truly modest yet powerful JavaScript framework.

Downloads

331

Readme

🧐 What is Simplicit?

Simplicit is a small library for structuring front-end JavaScript around controllers and components.

On the MVC side, it mirrors the “controller/action” convention you may know from frameworks like Ruby on Rails: based on <body> attributes, it finds the corresponding controller and calls its lifecycle hooks and action method.

On the component side, it provides a lightweight runtime (start() + Component) that instantiates and binds components from data-component, builds parent/child relationships, and automatically tears them down when elements are removed from the DOM.

🤝 Dependencies

Simplicit relies only on dompurify for sanitizing HTML.

📲 Installation

$ npm install --save simplicit

🎮 Usage

🖲️ Components

Simplicit ships with a small component runtime built around DOM attributes.

✅ Quick start

import { start, Component } from "simplicit";

class Hello extends Component {
  static name = "hello";

  connect() {
    const { input, button, output } = this.refs();
    this.on(button, "click", () => {
      output.textContent = `Hello ${input.value}!`;
    });
  }
}

document.addEventListener("DOMContentLoaded", () => {
  start({ root: document, components: [Hello] });
});
<div data-component="hello">
  <input data-ref="input" type="text" />
  <button data-ref="button">Greet</button>
  <span data-ref="output"></span>
</div>

DOM conventions

  • data-component="<name>": marks an element as a component root.
    • <name> must match the component class’ static name.
    • <script> tags are never treated as components, even if they have data-component.
  • data-component-id="<id>": set automatically on every element with data-component (each component instance).
    • Also available as instance.componentId.
  • data-ref="<key>": marks ref elements inside a component (see ref() / refs()).

start({ root, components })

start() scans root (defaults to document.body) for elements with data-component, creates and binds component instances for them, and keeps them in sync with DOM changes (new elements get initialized, removed ones get disconnected).

  • Validation
    • Throws if there are no data-component elements within root.
    • Throws if the DOM contains data-component="X" but you didn’t pass a matching class in components.
    • Throws if any provided component class does not define a writable static name.
  • Lifecycle
    • When an instance is created, if it has connect(), it is called after the instance is bound to its root DOM element (available as this.element).
    • When a component element is removed from the DOM, its instance.disconnect() is called automatically.

Return value

start() returns an object:

  • roots: array of root component instances (components whose parent is null) discovered at startup.
  • addComponents(newComponents): registers additional component classes later.
    • Validates the DOM again.
    • Scans the existing DOM for elements with data-component matching the newly added classes and initializes those that weren’t initialized yet.
    • Returns the newly created instances (or null if nothing was added).

Base class: Component

Simplicit exports a Component base class you can extend.

Core properties

  • element: the root DOM element of the component (data-component="...").
  • node: internal node graph { name, element, parent, children, siblings }.
  • componentId: string id mirrored to data-component-id.
  • parent: parent component instance (or null for root components).

Relationships

All relationship helpers filter by component name(s):

  • children(nameOrNames): direct children component instances (DOM order).
  • siblings(nameOrNames): sibling component instances.
  • ancestor(name): nearest matching ancestor component instance (or null).
  • descendants(name): all matching descendants (flat array).

Refs

Refs are scoped to the component’s root element.

  • ref(name): returns null, a single element, or an array of elements (when multiple match).
  • refs(): returns an object mapping each data-ref key to Element | Element[]. Only elements inside the component that have data-ref are included.

Cleanup & lifecycle utilities

disconnect() runs cleanup callbacks once and detaches the instance from its parent/child links.

You can register cleanup manually or use helpers that auto-register cleanup:

  • registerCleanup(fn)
  • on(target, type, listener, options) (auto-removes the listener on disconnect)
  • timeout(fn, delay) (auto-clears on disconnect)
  • interval(fn, delay) (auto-clears on disconnect)

Server-driven templates via <script type="application/json">

If a component class defines static template(data), Simplicit can render HTML from JSON embedded in the page.

import { start, Component } from "simplicit";

class Slide extends Component {
  static name = "slide";
  static template = ({ text }) => `<div data-component="slide">${text}</div>`;
}

start({ root: document, components: [Slide] });
<div id="slideshow"></div>

<script
  type="application/json"
  data-component="slide"
  data-target="slideshow"
  data-position="beforeend"
>
  [{"text":"A"},{"text":"B"}]
</script>

Notes:

  • The JSON payload must be an array; each item is passed to ComponentClass.template(item).
  • The rendered HTML is sanitized with dompurify before being inserted.
  • data-target must match an existing element id, otherwise an error is thrown.
  • Insertion uses targetEl.insertAdjacentHTML(position, html) where position comes from data-position (default: beforeend). Valid values: beforebegin, afterbegin, beforeend, afterend.
  • Inserted component elements are then auto-initialized like any other DOM addition.

🕹️ Controllers

Simplicit must have access to all controllers you want to run. In practice, you build a Controllers object and pass it to init().

Example:

// js/index.js (entry point)

import { init } from 'simplicit';

import Admin from "./controllers/Admin.js"; // namespace controller
import User from "./controllers/User.js";   // namespace controller

import Articles from "./controllers/admin/Articles.js";
import Comments from "./controllers/admin/Comments.js";

Object.assign(Admin, {
  Articles,
  Comments
});

const Controllers = {
  Admin,
  User
};

document.addEventListener("DOMContentLoaded", function() {
  init(Controllers);
});

💀 Anatomy of the controller

Example controller:

// js/controllers/admin/Articles.js

import { helpers } from "simplicit";

import Index from "views/admin/articles/Index.js";
import Show from "views/admin/articles/Show.js";

class Articles {
  // Simplicit supports both static and instance actions
  static index() {
    Index.render();
  }

  show() {
    Show.render({ id: helpers.params.id });
  }
}

export default Articles;

Minimal view example (one possible approach):

// views/admin/articles/Show.js

export default {
  render: ({ id }) => {
    const el = document.getElementById("app");
    el.textContent = `Article ${id}`;
    // If you need data loading, you can fetch here and update the DOM after.
  },
};

👷🏻‍♂️ How does it work?

On DOMContentLoaded, Simplicit reads these <body> attributes:

  • data-namespace (optional): a namespace path like Main or Main/Panel
  • data-controller: controller name (e.g. Pages)
  • data-action: action name (e.g. index)
<body data-namespace="Main/Panel" data-controller="Pages" data-action="index">
</body>

Then it resolves the matching controller(s), runs lifecycle hooks, and calls the action.

Resolution rules (simplified):

  • If data-namespace resolves (e.g. Main/PanelControllers.Main.Panel), Simplicit initializes the namespace controller and resolves the page controller under it (e.g. Controllers.Main.Panel.Pages).
  • Otherwise it skips the namespace controller and falls back to Controllers.Pages.

Call order (per controller):

  • If a method exists as static or instance, Simplicit will call it.
  • On navigation/re-init, previously active controllers receive deinitialize() (if present).
namespaceController = new Controllers.Main.Panel;
Controllers.Main.Panel.initialize();               // if exists
namespaceController.initialize();                  // if exists

controller = new Controllers.Main.Panel.Pages;
Controllers.Main.Panel.Pages.initialize();         // if exists
controller.initialize();                           // if exists
Controllers.Main.Panel.Pages.index();              // if exists
controller.index();                                // if exists

You don’t need controllers for every page; if a controller/method is missing, Simplicit skips it.

The init function returns { namespaceController, controller, action }.

🛠 Helpers

Simplicit exports helpers object that has the following properties:

  • params (getter) - facilitates fetching params from the URL

👩🏽‍🔬 Tests

npx playwright install

npm run test

npx playwright test --headed e2e/slideshow.spec.js

📜 License

Simplicit is released under the MIT License.

👨‍🏭 Author

Zbigniew Humeniuk from Art of Code