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

turbopug

v1.0.5

Published

No-junk JS component library with insignificant weight. All in one mush: Web-Components, Localization, Routing, Reactive binding, Debounce, Unique IDs, PSW hashing

Downloads

9

Readme

TURBOPUG

No-junk JS component library with insignificant weight. If you're 17.5% too weak or 11.4% too unwilling to use heavy-weight component frameworks, but still want all the stuff in one mush:

  • Web-Components
  • Localization
  • Templates
  • Routing
  • Reactive binding
  • Debounce
  • Unique IDs
  • PSW hashing

Plus 133.7% of all the quickility.

KEEPING IT SIMPLE SINCE 1903

Handmade master craftsmanship gives you the full-bodied low-fat experience you expect from a component framework:

  • No inheritance
  • No bundler
  • No build-step
  • No TypeScript
  • No junk

All the folding chair comfort you ever hoped for.

REQUIREMENTS

Because TURBOPUG is so bare bones, your customer's browser almost needs to be from the JavaScript-future:

  • modules & imports
  • template literals
  • Web Components
  • All the words: let, const, ...

Yes: That's all the browsers, since a couple of years.

GET OVER YOUR SPEED-ANGST

Running in an up-to-date browsers your SPA doesn't need all the vanity junk. Just use a server that doesn't crack on HTML/2 push and gzipped response compression.

Real quickility comes with not doing stuff. And thats what TURBOPUG does: not doing stuff. If you want wing chair comfort and a nurse giving you a hand, don't use TURBOPUG. If you know your JavaScript, get cracking.

FRIENDLY TO CARBONS OUT OF THE BOX

TURBOPUG is hertz-saving for computers. So somewhere someone doesn't need to burn fatty non-renewables. But keep in mind: what you do is up to you: Run your stuff on a Windows-11-VM through 5 VPNs and 6 SSH-tunnels if you like to roll like a coaler.

Docs

TURBOPUG happened while writing an SPA in vanilla JavaScript in a reactive, event-driven design by factoring out the accidental complexity.

Component Example

import Comp from '../turbo/comp.js';
import Store from '../turbo/store.js';

export class MyCounter extends Comp {
    #store;

    static get observedAttributes() {
        return ['label'];
    }

    constructor() {
        super();

        this.#store = new Store({
            label: (value = '', event) => {
                switch (event.type) {
                    case 'set':
                        return event.value;
                    default:
                        return value;
                }
            },
            counter: (value = 0, event) => {
                switch (event) {
                    case 'incremented':
                        return value + 1;
                    case 'decremented':
                        return value - 1;
                    default:
                        return value;
                }
            },
        });

        this.#store.merge({
            label: null,
            counter: 1,
        });
    }

    attributeChangedCallback(name, _, value) {
        switch (name) {
            case 'label':
                this.#store.send.label({type: 'set', value});
                break;
            default:
                break;
        }
    }

    get label() {
        return this.#store.state.label;
    }
    set label(value) {
        this.#store.send.label({type: 'set', value});
    }

    render() {
        return /*html*/`
            <div>
                <span class="label">${this.#store.state.label}</span>
                <span class="counter">${this.#store.state.counter}</span>
            </div>
            <button class="decrement">Decrement</button>
            <button class="increment">Increment</button>
        `;
    }

    bind(valElem, decBtn, incBtn) {
        const labelElem = valElem.querySelector('.label');
        this.#store.on.label((_, value) => {
            labelElem.innerText = value;
        });
        const counterElem = valElem.querySelector('.counter');
        this.#store.on.counter((_, value) => {
            counterElem.innerText = value;
        });
        decBtn.addEventListener('click', () => {
            this.#store.send.counter('decremented');
        });
        incBtn.addEventListener('click', () => {
            this.#store.send.counter('incremented');
        });
    }
}

customElements.define('my-counter', MyCounter);
  • An TURBOPUG component is defined by extending the class Comp.
  • For state management a Store is initialized and assigned onto a private variable #store. Stores are inspired by redux und made of variables that can be changed by sending events and reacting to them via reducers.
  • The 2 methods static get observedAttributes() and attributeChangedCallback(name, _, value) are part of the web-components specification.
  • A component can have a render-method which must return a rendered HTML-string. JS template strings are used for that.
  • After such a HTML-string is turned into DOM elements the bind-method is called. In which event-listeners from the DOM are attached and changes coming from the store are inserted into the DOM.

Components are state machines

How to program with TURBOPUG

An TURBOPUG comp is a state machine. Programming is done via variable changes over time. I.e. setting one variable through user-input, reacting to a change, setting another variable and reacting to the change, until one change is reflected in HTML via a binding or triggers an outside variable-change-handler.

Localization/i18n bindings

lcz-prop and lcz-attr are used to bind to localization keys from the translations file. Syntax is:

[<attrib-or-prop-name>=]<LOCALIZATION_KEY>

Example:

<p lcz-prop="textContent=LOCALIZABLE_KEY:Fallback Translation"></p>
<input lcz-attr="value=LOCALIZABLE_KEY:Fallback Translation" type="text">

The name of the prop or attrib to set the localization on. Can be omitted, defaults to textContent.

<LOCALIZATION_KEY>

The localization key to use to retrieve the value from the translations file. Fallback is value of <attrib-or-prop-name> if not found, default value textContent included.

Localization/i18n setup

insig must rely on script loading order to make translations available ASAP during page-loading. Translations file and LCZ-module must be setup as follows:

<!doctype html>
<html lang="en">

<head>
    <script src="/translations.js"></script> <!-- TRANSLATIONS FILE IS FIRST JS-FILE LOADED -->
    <!-- OTHER SCRIPT AFTERWARDS ... -->
</head>

<body>
    <!-- CONTENT COMES HERE -->
    <script src="/insig/lcz.js" type="module"></script> <!-- LCZ MODULE LOADED DIRECTLY AFTER CONTENT -->
</body>

</html>

The localizations file must set a the property localizations on the window object. If you only need one central localizations file, use the simple object notation in which each language locale is a key, i.e. de for german or "de-CH" for swiss german:

window.localizations = {
    de: {
        DOGS: 'Hunde',
        CATS: 'Katzen',
        DOG: 'Hund',
        CAT: 'Katze',
        DONE: 'Fertig'
    }
};

If you want to use several localization files, use the array notation in which each entry is one of the above. Use the following terse notation per file to automatically extend a previously loaded definition:

window.localizations = (l => [...l, {
    de: {
        DOGS: 'Hunde',
        CATS: 'Katzen',
        DOG: 'Hund',
        CAT: 'Katze',
        DONE: 'Fertig'
    }
}])(window.localizations || []);

The actual translations objects are merged in this case. The order in which the files are loaded in the HTML is significant: Files loaded later overwrite translation-keys from a file loaded earlier, so you can extend on base localizations.