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,618