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.br2kb in size (using microbundle).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 devThis command is setup to launch a browser window and you should see a landing page with the LiteralJS logo.
To install literaljs using npm you can type the following in the terminal:
$ npm install literaljsTo install literaljs using yarn you can type the following in the terminal:
$ yarn add literaljsOnce 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';With literaljs, you can use three different types of markup syntax:
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 });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 });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 });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.
.babelrc:{
"plugins": ["@babel/plugin-transform-react-jsx"]
}Any file which contains JSX will also need the following at the top:
/** @jsx h */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.
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.
component.id of the DOM node to inject generated markup into.Literal.render(component, 'app', { count: 0 });
// or
render(component, 'app');The component function accepts objects with the following keys:
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.
const Bar = component({
render() {
const { total } = this.props;
return <p>Total: {total}</p>
}
});
const Foo = component({
render() {
return <div>
<Bar props={{ total: 9 }} />
</div>
}
});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 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.
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.
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 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.
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.
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 });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.
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>
);
}
});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.
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>
);
}
});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:
The state object is defined directly on a component. This is accessible anywhere in the component by referencing this.getState.
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', {});The getState function returns a new object of the current component state. This function is accessible anywhere in the component by referencing this.getState.
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', {});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.
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', {});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.
const Foo = component({
render() {
const { count } = this.getStore();
return (
<button events={{
click: () => console.log(count)
}}>
Current Count: {count}
</button>
);
}
});
render(Foo, 'app', { count: 1 });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.
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 });The methods property is a function which returns an object of functions which are defined on the component instance.
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.
The props property is an object or function which is defined when the component is instantiated within another component.
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.
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:
const Foo = component({
render() {
return (
<button events={{ click: () => console.log('Hello World!') }}>
Click Me!
</button>
);
}
});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.
To install literaljs-router using npm you can type the following in the terminal:
$ npm install literaljs-routerTo install literaljs-router using yarn you can type the following in the terminal:
$ yarn add literaljs-routerOnce literaljs-router is installed you can import or require it into your project:
import { Router } from 'literaljs-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.
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.
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>
);
}
});The getParams function can be used to easily grab the current URL query params or search parameters as an object.
import { getParams } from 'literaljs-router';Deploying a LiteralJS application is very straight forward!
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