vanillapod

Lightweight library for building vanilla JavaScript components...

Usage no npm install needed!

<script type="module">
  import vanillapod from 'https://cdn.skypack.dev/vanillapod';
</script>

README

vanillapod

Lightweight library for building vanilla JavaScript components...

NOTE: ⚠ vanillapod is not used in production at the moment, and is missing some essential features.

Table of Contents

Install

# install with npm
$ npm install vanillapod

# or if you prefer using yarn
$ yarn add vanillapod

Background

Vanillapods main goal is to be a learning experience and to produce something useful, if not for anyone else, at least for me. It's inspired to attempt creating a UI library, by Juha Lindstedts RE:DOM and later on Chris Ferdinandis Reef.js. Hopefully it will not be too similair to those libraries (why should it exist otherwise?), but rather use those libraries to learn different approaches to solve common problems. Vanillapod is not used in production as of this time, so if you're looking for something more battle-tested, check out the libraries i mentioned above.

Goal: Enhanche Vanilla JavaScript

The goal of vanillapod is to enhance vanilla JavaScript with a component based architecture. Vanillapod provides several helpers to make the interaction with the DOM a bit more pleasant (hopefully).

Getting Started

You can use vanillapod regardless of using a bundler (Webpack, parcel or rollup), or using ES2015 modules in browsers that support it.

To import vanillapod methods without a bundler, you need to point to the script file inside the dist directory.

// import without bundler

import { mount } from './node_modules/vanillapod/dist/vanillapod.js';

Implicit Components

The first step to get started is defining your component.

// script.js

function myComponent() {
    return {
        element: 'h1',
        text: 'This is my first vanillapod component!'
    };
}

The implicit approach of defining components is useful in the beginning of development of a component.

Explicit Components

While this works perfectly fine for rendering something to the screen. it's not very flexible. That's why vanillapod provides a helper for rendering elements inside a component.

// script.js

import { elementHelper } from 'vanillapod';

function myComponent() {
    const props = {
        element: 'h1',
        text: 'This is my first vanillapod component!'
    }

    const element = elementHelper(props);

    return {
        element
    };
}

The explicit approach gives you more flexibility of what a component can do. The only requirement of a component using the explicit approach is that is should return an object with a element property. You can create elements using the elementHelper method.

Mounting

After you've defined your component it's time to mount it to the DOM, in other words render the html. You can specify a root element you want to mount inside, and pass it to mount as the first argument.

// script.js 

import { elementHelper, mount } from 'vanillapod';

// myComponent
// ...

const root = document.getElementById('root');

mount(root, myComponent);

It's also possible to mount multiple components at the same time.

// script.js 

// ...

function anotherComponent() {
    return {
        element: 'div',
        classList: ['another-component'],
        text: 'this is a second component'
    };
}

function thirdComponent() {
    return {
        element: 'div',
        classList: ['third-component'],
        text: 'this is a third component'
    };
}

// ...

// you can specify multiple components to mount simultaneously
mount(
    root,
    myComponent,
    anotherComponent,
    thirdComponent
);

You can pass null as the first argument to mount inside the documents body element.

// ...

mount(null, container);

// ...

Children

You can specify children of your component by specifying a children array key in the component properties. You can use the setElementChildren method when using the explicit approach.

// script.js

import { elementHelper, mount, setElementChildren } from 'vanillapod';

const heading = () => ({
    element: 'h1',
    text: 'This is my first vanillapod component!'
});

function myComponent() {
    return {
        element: 'div',
        children: [
            heading
        ]
    };
}

// mounting
// ...

Attributes


// ...

function myComponent() {
    return {
        // ...
        attributes: {
            hidden: ''
        },
        // ...
    };
}

Use setElementAttributes for explicit components.

Properties


// ...

function myComponent() {
    return {
        // ...
        properties: {
            id: 'myComponent'
        },
        // ...
    };
}

Use setElementProperties for explicit components.

Events

It's possible to attach event handlers to your component, by defining event handler functions in a events key on the props object that gets passed to elementHelper in your component. The event name is the same as what you'd normally pass to element.addEventListener.

// script.js

// ...

function myComponent() {
    const onClick = (e) => {
        console.log('Click event fired!')
    }

    return {
        // ...
        events: {
            click: onClick
        },
        // ...
    };
}

// ...

Passing data to child components

...

Lifecycle Methods (Hooks)

function myComponent() {
    // ...

    return {
        // ...
        hooks: {
            before() {
                console.log('myComponent before mount hook');
            },
            mount() {
                console.log('myComponent on mount hook');
            }
        },
        // ...
    };
}

Use registerHooks method for explicit component approach.

ES5

There is a ES5 bundle available for targeting older browsers that doesn't have support for ES2015 modules. All vanillapod methods are namespaced under vanillapod. Here is how you use the ES5 bundle.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vanillapod ES5</title>
</head>
<body>

<div id="root">
    <h1>vanillapod ES5 Example</h1>
</div>

<script src="./node_modules/vanillapod/dist/vanillapod.es5.min.js"></script>
<script src="script.js"></script>

</body>
</html>
// script.js

function myComponent() {
    return {
        element: 'div',
        text: 'Hello World'
    };
}

if (window.vanillapod) {
    vanillapod.mount(null, myComponent);
} else {
    console.error('Something went wrong!');
}

Debugging

If anything is not going as expected, you can always turn on debugging. vanillapod.js will then output what it's doing along the way. To turn on debugging, do the following:


// yourscript.js

import { debug } from 'vanillapod';

debug(true);

// to read debug value, call debug().

debug() // returns true

Example App

You can check out an example of how to build a ToDo app with vanillapod here.

ToDo

  • lifecycle events/hooks

  • complete name variable in doRegister function (function removed)

  • make it possible to unmount component

  • message/pubsub component

  • make it nicer to check value of classList from another component. stopButton().classList[1] is not so nice...

  • only output console.log if debug is true

  • refactor createElement.js if necessary...

  • write some initial tests!

  • make it possible to attach multiple elements at once... ie:

    mount(root, [
        element1,
        element2,
        element3
    ]);
    
  • only throw errors when debug is true

  • showcase how to set properties

  • make it possible to debug components visually with debug method?

  • router component would be nice!

  • write documentation for implicit vs explicit approach