- No building tools. Use a regular html file as a Single File Component.
- Server side rendered by default (templates are valid html).
- Ridiculously small API. After reading this file you will understand
Merlin
better than me. - Ultrafast vDom.
- Built-in Single Page Application Router.
<!DOCTYPE html>
<html>
<head>
<title>Todo - The Merlin JS framework</title>
</head>
<body>
<main>
<h1>To do list</h1>
<input type="text" value:="value" data-oninput="NewValue">
<ul>
<li each:="todos" text:></li>
</ul>
<button data-onclick="AddTodo">New!</button>
</main>
<script type="module">
import merlin from "https://cdn.jsdelivr.net/gh/marcodpt/merlin/index.js"
merlin({
components: {
todo: {
root: document.body.querySelector('main'),
init: () => ({
value: "",
todos: []
}),
AddTodo: ({todos, value}) => ({
todos: todos.concat(value),
value: ''
}),
NewValue: ({todos, value}, ev) => ({
todos,
value: ev.target.value
})
}
}
})
</script>
</body>
</html>
If you are using a template, you can write directly:
<template id="my-view">
<main>
<h1>To do list</h1>
<input type="text" value:="value" oninput="NewValue">
<ul>
<li each:="todos" text:></li>
</ul>
<button onclick="AddTodo">New!</button>
</main>
<template>
Every on{event}
or data-on{event}
attribute will be treated as a string and
converted to the component's associated function.
If you are not using a template
and the element is already SSR in the DOM you
MUST use data-on{event}
because there is no global function NewValue
or
AddTodo
and it will result in errors.
Object containing all components of your application:
root
: The optional root DOM element where the component should be mounted. Rootless components are views in the routing system. Rooted components are permanent elements like navigation bars and footers.template
: The optional template DOM element used to assemble the component. In general, you will use templates associated with views in the routing system and will not use templates on permanent elements. But there are exceptions that will be addressed.- fn
init
(data
,call
) ->state
: An optional function that is always called at component initialization and returns the initialstate
. Ifinit
is not passed whatever is indata
will be the initialstate
. - fn
format
(state
) ->viewState
: An optional function that transforms the state for rendering the view. It is useful when the state must store data that belongs to the component's internal logic and/or data formatting must be applied before rendering. Ifformat
is not passed,state
will be used directly to render the view. - fn
done
(state
,call
) -> (): An optional function that is always called when the component must be stopped, such as when the route is changed. If not passed, a function that does nothing will be used. - fn
method
(state
,data
,call
) ->newState
: All remaining properties of thecomponent
object aremethods
available to be called by the user usingon{event}
ordata-on{event}
in thetemplate
or associated DOM element or to be called internally by anothermethod
orinit
ordone
through the use of thecall
function. - fn
call
(method
,data
) ->newState
: Can call anymethod
in the component. Any DOM event will be acall
with thedata
being the event itself. The router starts the component withcall('init', data)
only then themethods
become available and the router stops the component withcall('done')
and all themethods
stop working. What exactly is insidedata
ininit
will be covered next.
Whenever the page's hash changes, the router resolves to a new state. If you
are not passing routes
the router will not be mounted (and it is absolutely
unnecessary to pass root
), otherwise it will listen for hash changes and
render the associated component in the root
or main
tag if root
is not
is passed or body
if there is no main
tag in the body.
Array of routes
that will be used to render a component
within the root
element on page hash changes. The first route that matches the hash will be
used.
route
: An optional string representing a hash path. It is allowed to use:var
to declare path variables.
Ex: #/counter/:count
will match #/counter/3
with Path
{"count": 3}.
If you don't pass route
, the router will match when no other routes match
(useful for creating 404 views).
component
: An optional string with thename
of a rootlesscomponent
defined in thecomponents
object. If no components are passed, whatever was inside theroot
element before the router started will be re-rendered there, this is useful for going back to an initial view that was SSR inside theroot
before the router started.
Array of functions that add parameters associated with the page hash. It should always return an object with new parameters. If not passed, the default value is an empty array. For example, if you declared the route:
{
route: '#/counter/:count'
}
And we have the hash of the page at that moment equal to:
#/counter/7?x=13&y=bird
We have:
{
url: '#/counter/7?x=13&y=bird',
path: '#/counter/7',
query: 'x=13&y=bird',
route: '#/counter/:count',
Params: {
count: '7'
},
Query: {
x: '13',
y: 'bird'
}
}
New properties can be added to be passed to all rootless components
using
your owns middlewares
.
Any remaining properties present will be treated as userData
. And they will
be passed at component
init
function.
api
: It is a property that brings theuserData
associated to thecomponent
.config
: It is a property that containsuserData
that is not associated with anycomponent
.root
: DOM element that the router is mounted on.refresh
() -> (): A function that tells the router to restart the component.
Rootless components are initialized (when the router matches a hash change)
with an object with properties defined by the result of middleware
(url
, path
, query
, route
, Params
, Query
) and any new properties
that the user-defined middleware
brings.
A function that is called on all rooted components whenever the router matches
a new route, the data
is the result of the middleware.
One use case could be if you want to implement a navigation bar as rooted
component and want to display the active route in links, you have to implement
in methods
a method called hashchange
.
Function that when called stops the router
and all rooted components and
terminates the merlin
application.
Merlin uses Tint as its template engine, you should read the docs for a complete reference.
The index.min.js
file is a minified and bundled version of index.js
built
online with bundle.js.
It's a very simple project. Any contribution, any feedback is greatly appreciated.
This work is hugely influenced by these amazing projects:
A huge thank you to all the people who contributed to these projects.