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

@asteo/nano-component

v1.0.4

Published

A minimal component library to compose vanilla HTML + JS from multiple files.

Downloads

21

Readme

NanoComponent

NPM Version JSR Version Minimized gzipped size Runtime Dependencies NPM License

NanoComponent is a very lightweight, vanilla TypeScript / JavaScript utility library that allows you to create and manage simple, reusable components in your web applications. This library allows for a straightforward way to split your HTML and JavaScript into small, manageable pieces without a full-fledged framework. It is best used with a bundler like vite.

NanoComponent Logo

Features

  • Simple HTML parsing: A template literal formatter can be used to parse HTML fragments within JS.
  • Component Properties: Re-use component factories with different properties (non-reactive).
  • Component as Property: Use components or HTML fragments as component properties.
  • Element References: Easily get a JS reference to tagged DOM elements (e.g. mount slots).
  • Lightweight: No dependencies, pure vanilla TypeScript, tiny package size.

Not a feature

This library does not provide any reactivity, async components, component lifecycles, template syntax, scoped styles or server-side rendering. It solely provides basics to glue together multiple HTML + JS fragments. This is by design.

This library is intended to help quickly bootstrap simple web pages without the need for heavy frameworks or verbose web components.

Installation

You can install NanoComponent via npm (or any tool of your choice):

npm i @asteo/nano-component

Usage

Example: Counter Component

A simple example would look like this:

// MyCounter.ts

import { component, useMount, useRef, useRefMount, html, Mountable } from "@asteo/nano-component";

interface Props {
  label: Mountable;
  value: number;
}

export default component<Props>(({ props: { label, value } }) => {
  // append a parsed DocuemntFragment to the component DOM
  useMount(html`
    <div class="my-first-component">
      <template class="ref:label"></template>
      is:
      <span class="ref:value">${value}</span>
      <button type="button"" class="ref:btn">Increase!</button>
    </div>
  `);

  // mount label prop to the "ref:label" position marker
  useRefMount("label", label);

  // query by "ref:value" and "ref:btn" classes respectively
  const valueEl = useRef("value");
  const btnEl = useRef("btn");

  // add click listener to button; just some vanilla js
  btnEl.addEventListener("click", () => {
    valueEl.textContent = `${++value}`;
  });
});

Mounting the root component

You'll want your root component to be mounted with the mount function:

// index.ts

import { mount, html } from "@asteo/nano-component";
import MyCounter from "./MyCounter.ts";

mount(
  MyCounter({
    label: html`The <span style="color:red">answer</span>`,
    value: 42,
  }),
  document.body,
);

Nested components

As you can see in the example with the label property, just use Mountable and useRefMount(...) for nested components or HTML fragments. We could've passed in any component(s) to the label property in the example above. The global mount function accepts Mountable as well, so we can mount multiple components at the same time, for example.

mount(
  [
    MyCounter({
      label: html`The <span style="color:red">answer</span>`,
      value: 42,
    }),
    // feature flag use-case
    showMultiple
      ? [
          MyCounter({ label: "First", value: -1 }),
          MyCounter({ label: "Second", value: 0 }),
        ]
      : null,
  ],
  document.body,
);

This simple structure allows a lot of flexibility. Feel free to experiment!

API

Types

Mountable

The Mountable type should primarily be used to define nested content properties. It is accepted by all mount functions below.

type Mountable =
  | null // accept "no content" values
  | Node // accept vanilla DOM Nodes (including html`...` template literals)
  | string // accept text content (**not** HTML)
  | Component // accept components
  | Mountable[]; // accept lists of all the above (including recursive lists of course)

Globals

component<Props>(setup: fn)

Creates a ComponentFactory that uses the passed setup function for component initialization. The setup function receives a single argument of { props: Props, doc: DocumentFragment }. The doc is the root DOM fragment to be filled by the setup function. This is mainly supposed to be done by the component-scoped helper functions below.

mount(content: Mountable, mountTarget: Element | DocumentFragment)

Mounts the passed content onto the passed mount target.

Caution: For <template> mount targets, the content is moved in front of the template element instead (useful for useRefMount; see below).

html template literal

The html template literal formatter parses the string into a DocumentFragment; ready to be used with mount functions.

Component-Scoped helpers

These helper functions must be used synchronously within the setup function of components. The use the components DOM inherently for convenience.

useRef<T>(name: string)

Retrieves an element reference from the component DOM. The element must have the class ref:<name> (e.g. ref:button). The default return type is HTMLElement. This can be further specified by the generic parameter T.

Caution: Refences are simply resolved via querySelector at that moment. If nested content is already mounted, it will pollute the DOM that is searched. Consider collecting references before any nested mounting.

useMount(content: Mountable)

Appends the passed content to the component DOM.

useRefMount(name: string, content: Mountable)

Appends the passed content to the specified reference element within the component DOM.

For <template> mount targets, the content is moved in front of the template element instead. This allows to easily use <template class="ref:slot"> to act as pure reference points in the DOM (no useless container elements).

Recommendations

HTML Files

It is recommended to use vite or a similar bundler. This allows to simply import HTML files as strings (?raw with vite) within your ts/js file. Those can be parsed to Mountables via the html template literal.

import MyFragment from "./MyFragment.html?raw";

// use with any mount function via the template literal parser; e.g.
mount(html`${MyFragment}`, document.body);

It is not recommended to move component templates into dedicated HTML files. But dumb (no js) HTML fragments can easily be split into dedicated files in this manner.

Refs first

Mounting sub-content pollutes the component DOM with arbitrary children. Thus, it is recommended to query useRef first for complex nested components. You can always use the global mount function onto the refs later on.

Singleton Components

If a component is only to exist once, you should export the component instance directly, not the factory.

// TheHeader.ts

export default component<void>(() => {

  // [component logic]

})();
// the trailing `()` immediately creates and exports just an instance

File Naming

It is recommended to name component files in TitleCase (e.g. MyCounter.ts, TheHeader.ts, TheFooter.html). Singleton names are recommended to be prefixed with "The".

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue or submit a pull request.

License

NanoComponent is licensed under the MIT License. See the LICENSE file for more information.