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
2kb
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 dev
This 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 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';
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 class
attribute 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 component
s, 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 component
that 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 component
s 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.setState
methods
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.setStore
methods
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-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';
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