Introduction

LiteralJS is a JavaScript library for building user interfaces.

Build "literaljs" to build:
       1856 B: index.js.gz
       1611 B: index.js.br
       1859 B: index.m.js.gz
       1620 B: index.m.js.br
       1919 B: index.umd.js.gz
       1680 B: index.umd.js.br

Features

  • Small: Under 2kb in size (using microbundle).
  • Startup:Amazing startup metrics; Great for weaker devices and weaker connections.
  • Simple:LiteralJS uses regular functions and applies structure to building components for easier development.
  • Fast: Current and previous vDOM data is diffed instead of the actual DOM for performant updates and rendering.
  • Virtual DOM: Diffing occurs only on state update for more efficient DOM updates.
  • Flexible Syntax: Freedom to use JSX, Hyperscript, or Object syntax.
  • Lifecycle Methods: All components have the ability to trigger mounted, updated, and unmounted lifecycle methods.
  • Global Application State: One source of truth which makes other state management libraries (like Redux) less of a need.
  • Component State: Manage state individually per component.
  • Event Delegation: Uses event delegation for most events in your application for increased performance.

Getting Started

The easiest way of getting started is by cloning the LiteralJS Starter Application.


Once you clone the project, you cd to the directory and run the following commands:

$ npm install
$ npm run dev
// OR
$ yarn
$ yarn dev

This command is setup to launch a browser window and you should see a landing page with the LiteralJS logo.

Installation

To install literaljs using npm you can type the following in the terminal:

$ npm install literaljs

To install literaljs using yarn you can type the following in the terminal:

$ yarn add literaljs

Once literaljs is installed you can import or require it into your project:

const Literal = require('literaljs');
// OR
import Literal from 'literaljs';
// OR
import { component, render, h } from 'literaljs';

Example Applications

Markup Syntax

With literaljs, you can use three different types of markup syntax:

JSX

How do I use JSX in LiteralJS?
const Foo = component({
  render() {
    const { count } = this.state;
    return (
      <div class='container'>
        This is the Count: {count}
        <button events={{ click: () => this.setStore(({ count }) => ({ count: count + 1 })) }}>
          Click Me!
        </button>
      </div>
    );
  }
});

render(foo, 'app', { count: 1 });

Hyper Script

const Foo = component({
  render() {
    const { count } = this.state;
    return h('div', {
        class: 'container'
      },
      `This is the Count: ${count}`,
      h('button', {
        events: { click: () => this.setStore(({ count }) => ({ count: count + 1 })) }
      })
    );
  }
});

render(foo, 'app', { count: 1 });

Object

const Foo = component({
  render() {
    const { count } = this.state;
    return ({
      el: 'div',
      attr: {
        class: 'container'
      },
      children: [
        `This is the Count: ${count}`,
        {
          el: 'button',
          attr: {
            events: { click: () => this.setStore(({ count }) => ({ count: count + 1 })) }
          }
        },
        'Click Me!'
      ]
    });
  }
});

render(foo, 'app', { count: 1 });

Using JSX With LiteralJS

If you want to use JSX, you need to import the Literal.h function, install the JSX transform plugin , and add the pragma option to your .babelrc or package.json file.

In .babelrc:

{
  "plugins": ["@babel/plugin-transform-react-jsx"]
}

Any file which contains JSX will also need the following at the top:

/** @jsx h */

Considerations On JSX In LiteralJS

JSX in LiteralJS has some differentiation when it comes to other implementations like JSX in React.


The biggest differentiator in syntax from React JSX is that the classattribute is acceptable on JSX elements, props are passed to components as an object, and events are passed as an object.


Event Objects need a key (which is a DOM event name) and a function as a value. Also, any name is considered acceptable as an attribute and any keys that work with LiteralJS Object Markup Syntax also work in LiteralJS JSX markup.

Render Function

The Literal.render function is responsible for parsing the AST that is provided with components, injecting the generated markup into the specific DOM node, diffing any new changes based on store or state changes, and setting the default application store.


The Literal.render function can accept 3 arguments, one of which is optional.

1) The root component.
2) String id of the DOM node to inject generated markup into.
3) (Optional) The default store (global application state) as an Object.

Example:

Literal.render(component, 'app', { count: 0 });
// or
render(component, 'app');

Component Function

The component function accepts objects with the following keys:

props

The props of a component is an object which can be passed with anything that is normally valid in a JavaScript Object. This can include any data or functions.


The only component that can not recieve props is the root componentthat is passed to the Literal.render function.

Example:

const Bar = component({
  render() {
    const { total } = this.props;
    return <p>Total: {total}</p>
  }
});

const Foo = component({
  render() {
    return <div>
      <Bar props={{ total: 9 }} />
    </div>
  }
});

methods

The methods key of a component will accept a function which returns an object of other functions. Within the methods, you have access tothis.getStore, this.setStore,this.getState, and this.setState, functions which can be used accordingly. By defining methods, you can use them directly within the components render function or within other methods by referencing the method name in this.

Methods and a method should never be an arrow function.

getState

getState is a function which is accessible anywhere in a component by referencing this.getState.


Using the getState function will pull a copy of the latest component state object. Once used, the specific state keys are accessible via dot notation or object notation.

setState

The setState function accepts an object or a function which returns an object. Using a function also gives you access to the most current state as an argument of the function. When calling setState, you can functionally modify the current component state by key value.setState is accessible anywhere in a component by referencing this.setStatemethods function.


Using the setState function will cause the component state to update and diffing to occur.

Example:

component({
  state: {
	  count: 0
  },
  methods() {
    return {
      increase() {
        return this.setState(({ count }) => ({ count: count + 1 }));
      }
    };
  },
  render() {
    const { count } = this.getState();
    return (
      <button events={{ click: this.increase }}>
        Current Count: {count}
      </button>
    );
  }
});

Literal.render(component, 'app', {});

getStore

getStore is a function which is accessible anywhere in a component by referencing this.getStore.


Using the getStore function will pull a copy of the latest state object of your application. Once used, the specific state keys are accessible via dot notation or object notation.

setStore

The setStore function accepts an object or a function which returns an object. Using a function also gives you access to the most current store as an argument of the function. When calling setStore, you can functionally modify the current global application store by key value.setStore is accessible anywhere in a component by referencing this.setStoremethods function.


Using the setStore function will cause the application store to update and diffing to occur.

Example:

component({
  methods() {
    const { count } = this.getStore();
    return {
      increase() {
        return this.setStore({ count: count + 1 });
      }
    };
  },
  render() {
    const { count } = this.getStore();
    return (
      <button events={{ click: this.increase }}>
        Current Count: {count}
      </button>
    );
  }
});

Literal.render(component, 'app', { count: 0 });

lifecycle methods

Similar to other front-end frameworks, there are three lifecycle events that LiteralJS includes that can be attached to any component.

To do so, just define a method call unmounted, updated, or mounted in a component to trigger functions when a component is first placed in the DOM, updated, or removed from the DOM.

Example:

component({
	mounted() {
		console.log('I am on the page!');
	},
	updated() {
		console.log('I am updated!');
	},
	unmounted() {
		console.log('I am off the page!');
	},
  render() {
    return (
      <div>
        I have a life!
      </div>
    );
  }
});

Iterating Components

Similar to other front-end frameworks, when iterating over a component, we need to supply a unique key. The key should be a unique and not change, like an id from a database, and not be the index of the iteration. The key is used in generation of a component state object and is used in tracking the individual component state object.

Example:

var TodoCard = component({
  render() {
    const { text } = this.props;
    return (
	  <div>
		{text}
	  </div>
	)
  }
});

var App = component({
  state: {
	  todos: []
  },
  render() {
    return (
      <div>
        {todos.map((todo, index) => (
		  <TodoCard
		    props={{
			  key: todo.id,
			  text: todo.text,
			}}
		  />
		))}
      </div>
    );
  }
});

render

The render key of a component accepts a function that returns an Object. The render function of a component is where all markup (like JSX) should be placed, events and methods used, and be primarily used to organize any other visual aspects of your application.

Render should never be an arrow function but events can use arrow functions


You have access to several functions and objects within the properties of the render key:

state (Object)

The state object is defined directly on a component. This is accessible anywhere in the component by referencing this.getState.

Example:

const Foo = component({
  state: {
    count: 1
  },
  render() {
    const { count } = this.getState();
    return (
      <button events={{
        click: () => console.log(count)
      }}>
        Current Count: {count}
      </button>
    );
  }
});

render(Foo, 'app', {});

getState (Function -> Object)

The getState function returns a new object of the current component state. This function is accessible anywhere in the component by referencing this.getState.

Example:

const Foo = component({
  state: {
    count: 1
  },
  render() {
    const { count } = this.getState();
    return (
      <button events={{
        click: () => console.log(count)
      }}>
        Current Count: {count}
      </button>
    );
  }
});

render(Foo, 'app', {});

setState (Function)

The setState function accepts an object or a function which returns an object. Using a function also gives you access to the most current state as an argument of the function. When calling setState, you can functionally modify the current component state by key value. This function is accessible anywhere in a component by referencing this.setState.

Example:

const Foo = component({
	state: {
		count: 1
	},
	render() {
		const { count } = this.getState();
		return (
			<div>
				<p>Current Count: {count}</p>
				<button events={{
					click: () => this.setState(({ count }) => ({ count: count + 1 }))
				}}>
					Add One To Count
				</button>
				<button events={{
					click: () =>
						this.setState(
							(state) => ({ count: state.count - 1 }),
							() => console.log('Callback: Minus 1 From Count')
						)
				}}>
					Minus Count
				</button>
			</div>
		);
	}
});

render(Foo, 'app', {});

getStore (Function -> Object)

The getStore function returns a new object of the current global application store. This function is accessible anywhere in the component by referencing this.getStore.

Example:

const Foo = component({
  render() {
    const { count } = this.getStore();
    return (
      <button events={{
        click: () => console.log(count)
      }}>
        Current Count: {count}
      </button>
    );
  }
});

render(Foo, 'app', { count: 1 });

setStore (Function)

The setStore function accepts an object or a function which returns an object. Using a function also gives you access to the most current store as an argument of the function. When calling setStore, you can functionally modify the current global application store by key value. This function is accessible anywhere in a component by referencing this.setStore.

Example:

const Foo = component({
	render() {
		const { count } = this.getStore();
		return (
			<div>
				<p>Current Count: {count}</p>
				<button events={{
					click: () => this.setStore({ count: count + 1 })
				}}>
					Add One To Count
				</button>
				<button events={{
					click: () =>
						this.setStore(
							(state) => ({ count: state.count - 1 }),
							() => console.log('Callback: Minus 1 From Count')
						)
				}}>
					Minus Count
				</button>
			</div>
		);
	}
});

render(Foo, 'app', { count: 1 });

methods (Function -> Object -> Functions)

The methods property is a function which returns an object of functions which are defined on the component instance.

Example:

const Foo = component({
  methods() {
    const { count } = this.getStore();
    return {
      increase() {
        return this.setStore({ count: count + 1 });
      }
    }
  },
  render() {
    const { count } = this.getStore();
    return (
      <button events={{ click: this.increase }}>
        Current Count: {count}
      </button>
    );
  }
});

Important: Methods can be used to clean up markup and organize logic.

props (Object, Function)

The props property is an object or function which is defined when the component is instantiated within another component.

Examples:

const Bar = component({
  render() {
    const { total } = this.props;
    return <p>Total: {total}</p>
  }
});

const Foo = component({
  render() {
    return (
		<div>
			<Bar props={{ total: 9 }} />
		</div>
	);
  }
});

Important: You can use other popular patters like render props or HoC in a similar way to how React using this process.

events (Object)

The events key in markup accepts an Object.

An event object consists of the key of the event name and the value of a function to trigger:

  • Key: Event Name
  • Value: Function To Trigger

Example:

const Foo = component({
  render() {
    return (
      <button events={{ click: () => console.log('Hello World!') }}>
        Click Me!
      </button>
    );
  }
});

LiteralJS-Router

LiteralJS-Router is a small library that can be used to add routing to your application in a similar manner to other routing library found in other frameworks, like React Router.

Installation

To install literaljs-router using npm you can type the following in the terminal:

$ npm install literaljs-router

To install literaljs-router using yarn you can type the following in the terminal:

$ yarn add literaljs-router

Once literaljs-router is installed you can import or require it into your project:

import { Router } from 'literaljs-router';

Router

The Router component contains the logic to display specific components based on the associated paths and requires routes, path, the store key name, and set which is the coresponding getState method to be passed to it as props.

The routes that are passed to the Router as props is an array with multiple objects. These route objects should contain a path (string) and render (component) as demonstrated in the following example.

The path that is passed to the Router as props is matched against the current browser pathname and should come from the application store. This is used to change which associated component the Router displays when the path changes.

Important: The path has the option of being a string which is converted into a regular expression. A regular expression can also be used. Regular expressions should be primarily used when pieces of the path need to be dynamic. Dynamic parts of a path include things like ids and other meta data not included as query params of the URL.

The key props is a string which is the name of the application store key-value that is used for switching the current browser pathname.

The set prop is the corresponding setStore method. This is determined by where and how you wish to change the current route.

Example:


import { Router } from 'literaljs-router';

// Components
import Navbar from './components/Navbar';
import Flash from './components/Flash';
import Footer from './components/Footer';

// Pages
import IndexPage from '../pages/index';
import SignInPage from '../pages/sign-in';
import SignUpPage from '../pages/sign-up';
import ProfilePage from '../pages/profile';
import NotFoundPage from '../pages/notFound';

// Pages
const routes = [
	{
		path: '/',
		render: IndexPage
	},
	{
		path: '/sign-in',
		render: SignInPage
	},
	{
		path: '/sign-up',
		render: SignUpPage
	},
	{
		path: /\/profile\/[a-z|\d]/, // This regular expression is used to match profile ids.
		render: ProfilePage
	},
	{
		path: '404', // Use this to display a component for an unmatched path
		render: NotFoundPage
	}
];

const App = component({
	render() {
		const { location } = this.state;
		return (
			<div class="App">
				<Navbar />
				<Flash />
				<Router
					props={{
						path: location,
						routes: routes,
						key: 'location',
						set: this.setStore
					}}
				/>
				<Footer />
			</div>
		);
	}
});

render(App, 'app', { location: location.pathname });

With the path located in the application state, you can change what the Router displays by changing that specific key value using the corresponding setState.

Example:

const Navbar = component({
	methods() {
		return {
			goToSignUp() {
				this.setStore({ location: '/sign-up' });
			}
		}
	},
	render() {
		return (
			<div class="Navbar">
				<button events={{ click: this.goToSignUp }}>
					Sign Up
				</button>
			</div>
		);
	}
});

getParams (Function -> Object)

The getParams function can be used to easily grab the current URL query params or search parameters as an object.

Example:

import { getParams } from 'literaljs-router';

Deployment

Deploying a LiteralJS application is very straight forward!

Netlify

By default, the LiteralJS Starter application is setup to easily deploy to Netlify.

The only thing needed is to setup the build command in the Netlify dashboard to build-netlify, a .nvmrc file with the latest node version, and the Publish directory set to dist:

Build command: npm run build-netlify

Publish directory: dist

^