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

picoapp

v3.6.2

Published

🐣 Tiny no-framework component toolkit. **900b gzipped.**

Downloads

2,408

Readme

picoapp

🐣 Tiny no-framework component toolkit. 900b gzipped.

tl;dr - this library automatically instantiates JavaScript modules on specific DOM elements in a website if they exist on the page. This is helpful for projects like Shopify or Wordpress that aren't using a framework like React or Vue. picoapp also contains functionality that make it a great companion to any PJAX library – like operator – where page transitions can make conventional JS patterns cumbersome.

Install

npm i picoapp --save

Usage

Define data attributes on the DOM nodes you need to bind to:

<button data-component="button">I've been clicked 0 times</button>

Create a corresponding component:

// button.js
import { component } from "picoapp";

export default component((node, ctx) => {
  let count = 0;

  node.onclick = () => {
    node.innerHTML = `I've been clicked ${++count} times`;
  };
});

Import your component and create a picoapp instance:

import { picoapp } from "picoapp";
import button from "./button.js";

const app = picoapp({ button });

To bind your component to the DOM node, call mount():

app.mount();

State & Events

picoapp uses a very simple concept of state, which is shared and updated using events or hydrate helpers. Internally, picoapp uses evx, so check that library out for more info.

You can define initial state:

const app = picoapp({ button }, { count: 0 });

And consume it on the context object passed to your component:

export default component((node, ctx) => {
  // ctx.getState().count
});

To interact with state, you will primarily use events. Passing an object when emitting an event will merge that object into the global state. Event listeners are then passed the entire state object for consumption.

export default component((node, ctx) => {
  ctx.on("incremenent", state => {
    node.innerHTML = `I've been clicked ${state.count} times`;
  });

  node.onclick = () => {
    ctx.emit("increment", { count: ctx.getState().count + 1 });
  };
});

You can also pass a function to an emitter in order to reference the previous state:

ctx.emit("increment", state => {
  return {
    count: state.count + 1
  };
});

Just like evx, picoapp supports multi-subscribe, wildcard, and property keyed events as well:

ctx.on(["count", "otherProp"], state => {}); // fires on `count` & `otherProp`
ctx.on("*", state => {}); // fires on all state updates
ctx.on("someProp", ({ someProp }) => {}); // fires on all someProp updates

If you need to update state, but don't need to fire an event, you can use ctx.hydrate:

export default component((node, ctx) => {
  ctx.hydrate({ count: 12 });
});

Other Events

picoapp has a few protected events:

  • mount - called after all components have mounted
  • unmount - called after all unmountable components have unmounted
  • error - called if a component throws an error

Errors

If an instance throws an error while mounting, it will be caught by picoapp. To listen and process errors, subscribe to the error event:

app.on("error", ({ error }) => {
  // do something with error
});

Un-mounting

picoapp components are instantiated as soon as they're found in the DOM after calling mount(). Sometimes you'll also need to un-mount a component, say to destroy a slideshow or global event listener after an AJAX page transition.

To do so, return a function from your component:

import { component } from "picoapp";

export default component((node, ctx) => {
  ctx.on("incremenent", state => {
    node.innerHTML = `I've been clicked ${state.count} times`;
  });

  function handler(e) {
    ctx.emit("increment", { count: ctx.getState().count + 1 });
  }

  node.addEventListener("click", handler);

  return node => {
    node.removeEventListener("click", handler);
  };
});

And then, call unmount(). All evx event subscriptions within your component will be destroyed automatically.

app.unmount();

unmount() is also synchronous, so given a PJAX library like operator, you can do this after every route transition:

router.on("after", state => {
  app.unmount(); // cleanup
  app.mount(); // init new components
});

If your component does not define an unmount handler, the component will remain mounted after calling unmount (including all evx event subscriptions within the component). This is useful for components that persist across AJAX page transitions such as global navigation or even a WebGL canvas.

Other Stuff

The picoapp instance also has access to state and the event bus:

app.emit("event", { data: "global" });
app.on("event", state => {});

So you can add arbitrary state to the global state object directly:

app.hydrate({ count: 5 });

And then access it from anywhere:

app.getState(); // { count: 5 }

If you need to add components – maybe asynchronously – you can use add:

app.add({
  lazyImage: component(context => {})
});

If data-component isn't your style, or you'd like to use different types of "components", pass your attributes to mount():

Given the below, picoapp will scan the DOM for both data-component and data-util attributes and init their corresponding JS modules:

app.mount(["data-component", "data-util"]);

Plugins

The picoapp instance allows you to extend the component API through plugins. Plugins are functions that return objects, which then get merged into the context object passed to your component. Note that name conflicts with plugin properties will always be overriden by picoapp's context, and that plugins are evaluated for every component.

To define plugins, pass a function to the use method. The example below adds a props object extracted from the component node's data-props attribute:

app.use(node => {
  const props = JSON.parse(node.dataset.props || "{}");
  return { props };
});

And then acccess plugin extensions from your component:

const foo = component(node, ctx) => {
  const { images = [] } = ctx.props
  console.log(`start preloading ${images.length} images...`)
})

Have an idea for a plugin? Open an issue and we can discuss! :)

License

MIT License © Eric Bailey