Mithril.js
A Javascript Framework for Building Brilliant Applications
Me
Stephan Hoyer
from Leipzig
running Leipzigjs
Javascript
(since 2010)
Freelancing
Running a Start-up
tutory.de

Yet another JS Framework?
What's the difference?
Filesize
AngularJS
Ember.js
jQuery
40kb
100kb
35kb
mithril
7kb
react
35kb
Why Filesize mAtters
- Faster download (Mobile friendly)
- Faster parsing and compiling (also Mobile friendly)
- Less code to reason about
- Less code with bugs
- More space for own code :)
API Surface
AngularJS
Ember.js
jQuery
200+
100+
200+
mithril
20
react
~ 50
???
Why API Surface Matters?
- fewer to learn
- fewer to reason about
- fewer to memoize
- more freedom for integration
- smaller lock-in-factor
Performance*
AngularJS
Ember.js
Knockout
800ms
1000ms
300ms
mithril
80ms
react
600ms
http://matt-esch.github.io/mercury-perf/
Why Performance matters
- for reasons
- mobile friendly
- UX
Embraces Functional Programming
- Easy to test
- Easy to extend
- Less bugs because of side effects
Uber-Simple Setup
- no precompiler needed
- no special cli tool
- compatible most bundlers (browserify, webpack, ...)
Features
React-Like VDOM
Small Routes Layer
Modules
Ajax-Helper
FAst Auto-Redraw
- simple to build complex UI
- fast!
- even medium-experienced UX/UI-Dev can change stuff
- Nothing More -
NO GOD Class
No Inheritance Tree
No Template Langugage
No Module Loader
No Crap you probably don't need
Example
var todos = {
controller: function() {
},
view: function() {
return m('h1.random-css-class#some-id', 'huhu');
}
};
m.route(document.body , '/', {
'/': todos
});
basic Setup
<!doctype html>
<html lang="en">
<head>
<title>Mithril Example</title>
<script src="mithril.js"></script>
<script src="index.js"></script>
</head>
<body></body>
</html>
function headerView() {}
function footerView() {}
function mainView() {
return m('main', [
m('ul', [
m('li', [
m('input[type=checkbox]'),
m('input', {
value: 'learn mithril'
}),
m('button.destroy', 'remove')
])
])
]);
}
var todos = {
controller: function() {},
view: function() {
return [
headerView(),
mainView(),
footerView()
]
}
};
Function Composition
function todoView(todo) {
return m('li', [
m('.view', [
m('input[type=checkbox]),
m('input.edit', {
value: 'learn mithril'
}),
m('button.destroy', 'remove')
]),
]);
}
function mainView(scope) {
return m('main', [
m('ul', scope.todos.map(todoView)),
m('.countUndone', scope.countUndone())
]);
}
Turing Complete Templates
function undone(todo) {
return !todo.done;
}
var todos = {
controller: function() {
var scope = {
todos: [{
label: 'learn mithril!',
done: false
}, {
label: 'relax!',
done: false
}],
countUndone: function() {
return scope.todos.filter(undone).length;
}
};
return scope;
},
view: function(scope) {
return [
headerView(scope),
mainView(scope),
footerView(scope),
];
}
};
function todoView(scope) {
return function(todo) {
return m('li', [
m('.view', [
m('input[type=checkbox], {
onchange: scope.markDone(todo)
}),
m('input.edit', {
value: 'learn mithril'
}),
m('button.destroy', 'remove')
]),
]);
}
}
function mainView(scope) {
return m('main', [
m('button', {
onclick: function() {
scope.addTodo()
}
}, 'New todo'),
m('ul', scope.todos.map(todoView(scope)))
m('.countUndone', scope.countUndone())
]);
}
Eventbinding
var todos = {
controller: function() {
var scope = {
todos: [],
countUndone: function() {
return scope.todos.filter(undone).length;
}
addTodo: function(label) {
scope.todos.push({
label: label || 'new todo',
done: false
};
},
markDone: function(todo) {
return function(event) {
todo.done = event.target.checked;
}
}
};
return scope;
},
view: function(scope) {
return [
headerView(scope),
mainView(scope),
footerView(scope),
];
}
};
Routing
var todoComponent = {
controller: function() {
var todos = [];
var scope = {
get todos() {
return todos.filter(function(todo) {
return (m.route() == '/completed' && todo.done) ||
(m.route() == '/active' && !todo.done) ||
(m.route() === '/');
});
}),
// ...
};
return scope;
},
view: function(scope) {
return [
headerView(scope),
mainView(scope),
footerView(scope),
];
}
};
m.route.mode = 'hash';
m.route(document.body , '/', {
'/': todoComponent,
'/active': todoComponent,
'/completed': todoComponent
});
Components
var todoComponent = {
controller: function(todos) {
var scope = {
get todos() {
return todos.filter(function(todo) {
return (m.route() == '/completed' && todo.done) ||
(m.route() == '/active' && !todo.done) ||
(m.route() === '/');
});
}),
// ...
};
return scope;
},
view: function(scope) {
return [
headerView(scope),
mainView(scope),
footerView(scope),
];
}
};
function someOtherAppsView() {
return m('.random-container', [
m.component(todoComponent, somePredefinedTodos)
]);
}
universal Apps
with Node rendering
possible
tutory.de has integration in express webserver
with the help of mithril-node-render
Do you really need it for a SPA?
It's only relevant for search engines
so only pre-render indexed pages
Prerender does not improve render performace
since mithril rerenders everything on startup
Testing
Is powerful!
Is fast!
Is easy!
Basic Example
mithril component
test for component
var out = mithrilQuery(nameComponent, 'Catwoman');
out.should.have('.name');
out.should.not.have('.address');
out.should.have(1, '.name');
out.should.contain('Catwomen');
nameComponent = {
controller: function(initialName) {
return {
name: initialName || 'Batman'
};
},
view: function(scope) {
return m('.name', scope.name);
}
};
ADVANCED Example
mithril component
test for component
var out = mithrilQuery(nameComponent, 'Catwoman');
out.should.contain('Catwomen');
out.click('.clear');
out.should.not.contain('Catwomen');
nameComponent = {
controller: function(initialName) {
var scope = {
name: initialName || 'Batman'
clearName: function() {
scope.name = '';
}
};
return scope;
},
view: function(scope) {
return m('.name', scope.name);
return m('.clear', {
onclick: scope.clearName
}, 'clear');
}
};
Handling Real DOM-Stuff
It's easier than you might think
exaMple
Canvas integration
function drawCanvas(element, isInitialized) {
//don't redraw if we did once already
if (isInitialized) return;
var ctx = element.getContext("2d");
/* draws stuff */
}
var app = {
controller: function() {
// ...
},
view: {
return m("canvas", {
config: drawCanvas
})
}
]
m.mount(document.body, app);
Integration of 3rd-Party code is easy
var Sortable = require('sortablejs');
var app = {
controller: function() {
return {
onSort: function(sortEvent) {
// ....
}
}
},
view: {
return m("ul", {
config: function(el, isInitialized) {
if (isInitialized) return;
new Sortable(el, {
animation: 150,
onSort: scope.onSort
});
}
}, [
m('li', 'one'),
m('li', 'two'),
m('li', 'three')
])
}
]
m.mount(document.body, app);
Conclusion
Still Love it
After 1.5 years of usage
It's my golden hammer
All other stuff* seems so odd compared to mithril
*except other VDOM libs, most are quite good ;)
Questions
thanks
mithril.js
By Stephan Hoyer
mithril.js
- 3,803