vanillapod

0.11.2 • Public • Published

vanillapod

Lightweight library for building vanilla JavaScript components...

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

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, rollup or vite), 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.

Here are the methods available for working with explicit components:

// 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);

// ...

It's possible to pass props/data to components when mounting.

function Header({ text }) {
    return {
        element: 'header',
        text,
    };
}

mount(
    document.body,
    [
        Header, 
        { text: 'Hello World!' }
    ]
);

It's not necessary to mount a functional component, you can mount objects directly instead.

mount(
    document.querySelector('#app'),
    { element: 'h1', text: 'This is not a functional component!' },
    { element: 'p', text: 'Lorem, ipsum dolor sit amet consectetur adipisicing elit. Natus quaerat magnam ratione consequuntur, tempora ipsa sapiente reiciendis eligendi maiores cum blanditiis odit architecto dolorem exercitationem facere. Tempore pariatur magni nemo.' }
);

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

import { mount, createDocumentFragment } from 'vanillapod/vanillapod';

function List({ tasks }) {
    return {
        element: 'ul',
        children: tasks.map(item => () => ({
            element: 'li',
            text: item
        }))
    };
}

function Header({ text }) {
    return {
        element: 'header',
        text,
    };
}

function App() {
    const tasks = [
        'first',
        'second',
        'third'
    ];

    const children = [
        [
            Header,
            { text: 'hello world!' }
        ],
        [
            List,
            { tasks }
        ]
    ];

    const [ el ] = createDocumentFragment();

    return {
        el,
        children
    };
}

mount(
    document.querySelector('#app'), 
    App
);

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.

Potential Pitfalls

There are a few things that might create unexpected results...

Events on document fragments

Since document fragments do not behave like a usual element (doesn't render any element in the DOM), events attached to it will not work. You can work around this by attaching events on another element or on the window object.

import { mount } from 'vanillapod/vanillapod';

function Fragment() {
    const [el] = createDocumentFragment();

    window.addEventListener('click', () => console.log('clicked!'));

    return {
        el
    };
}

mount(null, Fragment);

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

  • [x] lifecycle events/hooks

  • [x] 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...

  • [x] only output console.log if debug is true

  • [x] refactor createElement.js if necessary...

  • [x] write some initial tests!

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

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

  • [x] showcase how to set properties

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

  • [ ] router component would be nice!

  • [x] write documentation for implicit vs explicit approach

Readme

Keywords

none

Package Sidebar

Install

npm i vanillapod

Weekly Downloads

1

Version

0.11.2

License

MIT

Unpacked Size

79 kB

Total Files

30

Last publish

Collaborators

  • carlrafting