turbopug

1.0.5 • Public • Published

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.

Package Sidebar

Install

npm i turbopug

Weekly Downloads

0

Version

1.0.5

License

LicenseRef-LICENSE

Unpacked Size

35.8 kB

Total Files

17

Last publish

Collaborators

  • jaqmol