Mithril vs Marko: A Comprehensive Comparison

This website is powered by ItGalaxy.io

In the world of frontend development, Mithril and Marko represent two distinct approaches to building web applications. While Mithril offers a lightweight, hyperscript-based approach with functional components, Marko introduces a unique template-first approach with powerful streaming capabilities and compile-time optimizations. Let’s explore their differences and use cases.

Table of Contents

  1. Core Concepts
  2. Reactivity and State Management
  3. Templating and Components
  4. DOM Manipulation
  5. Event Handling
  6. Component Composition
  7. Form Handling
  8. Lifecycle Management
  9. Web App Features
  10. Performance and Bundle Size
  11. Learning Curve
  12. Conclusion

Core Concepts

Mithril and Marko take fundamentally different approaches to building web applications:

  • Mithril uses a lightweight hyperscript-based approach with functional components and manual redraw triggers
  • Marko employs a template-first approach with powerful streaming capabilities and compile-time optimizations

Reactivity and State Management

Declare State

Mithril

import m from "mithril";

export default function Name() {
  let name = "John";

  return {
    view: () => m("h1", `Hello ${name}`),
  };
}

Marko

<let/name = "John"/>
<h1>Hello ${name}</h1>

Update State

Mithril

import m from "mithril";

export default function Name() {
  let name = "John";
  name = "Jane";
  return {
    view: () => m("h1", `Hello ${name}`),
  };
}

Marko

<let/name = "John"/>
<effect() { name = "Jane" }/>
<h1>Hello ${name}</h1>

Computed State

Mithril

import m from "mithril";

export default function DoubleCount() {
  let count = 10;
  let doubleCount = count * 2;
  return {
    view: () => m("div", doubleCount),
  };
}

Marko

<let/count = 10/>
<const/doubleCount = count * 2/>
<div>${doubleCount}</div>

Templating

Minimal Template

Mithril

import m from "mithril";

export default function HelloWorld() {
  return {
    view: () => m("h1", "Hello World"),
  };
}

Marko

<h1>Hello world</h1>

Styling

Mithril

import "./style.css";
import m from "mithril";

export default function CssStyle() {
  return {
    view: () =>
      m(
        "div",
        m("h1.title", "I am red"),
        m("button", { style: { fontSize: "10rem" } }, "I am a button")
      ),
  };
}

Marko

<h1.title>I am red</h1>
<button style={ fontSize: "10rem" }>I am a button</button>
<button class=scopedButton>I am a style-scoped button</button>

<style>
  .title {
    color: red;
  }
</style>
<style/{ scopedButton }>
  .scopedButton {
    font-size: 10rem;
  }
</style>

Loop

Mithril

import m from "mithril";

export default function Colors() {
  const colors = ["red", "green", "blue"];
  return {
    view: () =>
      m(
        "ul",
        colors.map((color, idx) => m("li", { key: idx }, color))
      ),
  };
}

Marko

<ul>
  <for|color| of=["red", "green", "blue"]>
    <li>${color}</li>
  </for>
</ul>

Event Click

Mithril

import m from "mithril";

export default function Counter() {
  let count = 0;
  const incrementCount = () => (count = count + 1);
  return {
    view: () =>
      m(
        "div",
        m("p", `Counter: ${count}`),
        m("button", { onclick: incrementCount }, "+1")
      ),
  };
}

Marko

<let/count = 0/>
<p>Counter: ${count}</p>
<button onClick() { count++ }>+1</button>

DOM Reference

Mithril

import m from "mithril";

export default function InputFocused() {
  let value = "";
  return {
    view: () =>
      m("input", {
        oncreate: ({ dom }) => dom.focus(),
        type: "text",
        value,
        oninput: (e) => (value = e.target.value),
      }),
  };
}

Marko

<input/inputElement>
<effect() { inputElement().focus() }/>

Conditional

Mithril

import m from "mithril";
const TRAFFIC_LIGHTS = ["red", "orange", "green"];

export default function TrafficLight() {
  let lightIndex = 0;
  let currentLight = () => TRAFFIC_LIGHTS[lightIndex];

  const nextLight = () => (lightIndex + 1) % TRAFFIC_LIGHTS.length;

  const instructions = () => {
    switch (currentLight()) {
      case "red":
        return "STOP";
      case "orange":
        return "SLOW DOWN";
      case "green":
        return "GO";
    }
  };

  return {
    view: () =>
      m(
        "div",
        m("button", { onclick: nextLight }, "Next light"),
        m("p", `Light is: ${currentLight()}`),
        m("p", "You must ", m("span", instructions()))
      ),
  };
}

Marko

static const TRAFFIC_LIGHTS = ["red", "orange", "green"];
<let/lightIndex = 0/>
<const/light = TRAFFIC_LIGHTS[lightIndex]/>

<button onClick() { lightIndex = (lightIndex + 1) % TRAFFIC_LIGHTS.length }>
  Next light
</button>
<p>Light is: ${light}</p>
<p>
  You must
  <if=light === "red">STOP</if>
  <else-if=light === "orange">SLOW DOWN</else-if>
  <else>GO</else>
</p>

Lifecycle

On Mount

Mithril

import m from "mithril";

export default function PageTitle() {
  return {
    view: () => m("p", `Page title: ${document.title}`),
  };
}

Marko

<let/pageTitle = ""/>
<effect() { pageTitle = document.title }/>
<p>Page title: ${pageTitle}</p>

On Unmount

Mithril

import m from "mithril";

export default function Time() {
  let time = new Date().toLocaleTimeString();

  const timer = setInterval(() => {
    time = new Date().toLocaleTimeString();
    m.redraw();
  }, 1000);

  return {
    view: () => m("p", `Current time: ${time}`),
    onremove: () => clearInterval(timer),
  };
}

Marko

<let/time = new Date()/>
<lifecycle
  onMount() { this.timer = setInterval(_ => time = new Date(), 1000) }
  onDestroy() { clearInterval(this.timer) }
/>
<p>Current time: ${time.toLocaleTimeString()}</p>

Component Composition

Props

Mithril

import m from "mithril";
import UserProfile from "./UserProfile.js";

export default function App() {
  return {
    view: () =>
      m(UserProfile, {
        name: "john",
        age: 20,
        favouriteColors: ["green", "blue", "red"],
        isAvailable: true,
      }),
  };
}

Marko

<UserProfile
  name="John"
  age=20
  favouriteColors=["green", "blue", "red"]
  isAvailable
/>

Emit to Parent

Mithril

import m from "mithril";
export const AnswerButton = ({ attrs: { onYes, onNo } }) => ({
  view: () =>
    m(
      "div",
      m("button", { onclick: onYes }, "YES"),
      m("button", { onclick: onNo }, "NO")
    ),
});

Marko

<let/isHappy = true/>
<p>Are you happy?</p>
<AnswerButton
  onYes() { isHappy = true }
  onNo() { isHappy = false }
/>
<p style={ fontSize: 50 }>${isHappy ? "😀" : "😥"}</p>

Slot

Mithril

import m from "mithril";
import { FunnyButton } from "./FunnyButton.jsx";

export default function App() {
  return {
    view: () => m(FunnyButton, "Click me!"),
  };
}

Marko

<FunnyButton>Click me!</FunnyButton>

Slot Fallback

Mithril

import m from "mithril";
import FunnyButton from "./FunnyButton.jsx";

export default function App() {
  return {
    view: () => m("", m(FunnyButton), m(FunnyButton, "I got Content")),
  };
}

Marko

<FunnyButton/>
<FunnyButton>I got content!</FunnyButton>

Form Input

Input Text

Mithril

import m from "mithril";

export default function InputHello() {
  let text = "Hello world";
  const handleChange = ({ target: { value } }) => (text = value);

  return {
    view: () =>
      m("", m("p", text), m("input", { value: text, onchange: handleChange })),
  };
}

Marko

<let/text = "Hello world"/>
<p>${text}</p>
<input value:=text/>

Checkbox

Mithril

import m from "mithril";

export default function IsAvailable() {
  let isAvailable = false;
  const onUpdate = () => (isAvailable = !isAvailable);

  return {
    view: () =>
      m(
        "",
        m("input", {
          id: "is-available",
          type: "checkbox",
          checked: isAvailable,
          onchange: onUpdate,
        }),
        m("label", { for: "is-available" }, "Is available")
      ),
  };
}

Marko

<input#is-available
  type="checkbox"
  checked:=input.isAvailable
/>
<label for="is-available">Is available</label>

Radio

Mithril

import m from "mithril";

export default function PickPill() {
  let picked = "red";
  let pills = ["red", "green", "blue"];
  const handleChange = ({ target: { value } }) => (picked = value);

  return {
    view: () =>
      m(
        "",
        m("", `Picked: ${picked}`),
        pills.map((pill) =>
          m(
            ".",
            m("input", {
              id: pill,
              checked: picked == pill,
              type: "radio",
              value: pill,
              onchange: handleChange,
            }),
            m("label", { for: pill }, pill)
          )
        )
      ),
  };
}

Marko

<let/picked = "red"/>
<const/handleChange(event) {
  picked = event.target.value;
}/>

<div>Picked: ${picked}</div>
<input#blue-pill
  type="radio"
  checked=picked === "blue"
  value="blue"
  onChange=handleChange
/>
<label for="blue-pill">Blue pill</label>

<input#red-pill
  type="radio"
  checked=picked === "red"
  value="red"
  onChange=handleChange
/>
<label for="red-pill">Red pill</label>

Select

Mithril

import m from "mithril";
const colors = [
  { id: 1, text: "red" },
  { id: 2, text: "blue" },
  { id: 3, text: "green" },
  { id: 4, text: "gray", isDisabled: true },
];

export default function ColorSelect() {
  let selectedColorId = 2;

  const handleSelect = ({ target: { value } }) => (selectedColorId = value);

  return {
    view: () =>
      m(
        "select",
        { value: selectedColorId, onchange: handleSelect },
        colors.map(({ id, text, isDisabled }) =>
          m("option", { key: id, id, disabled: isDisabled, value: id }, text)
        )
      ),
  };
}

Marko

static const colors = [
  { id: 1, text: "red" },
  { id: 2, text: "blue" },
  { id: 3, text: "green" },
  { id: 4, text: "gray", isDisabled: true },
];
<let/selectedColorId = 2/>

<select onChange(event) { selectedColorId = event.target.value }>
  <for|{ id, isDisabled, text }| of=colors>
    <option value=id disabled=isDisabled selected=id === selectedColorId>
      ${text}
    </option>
  </for>
</select>

Web App Features

Render App

Mithril

<!DOCTYPE html>
<html>
  <body>
    <div id="app"></div>
    <script type="module" src="./main.jsx"></script>
  </body>
</html>

Marko

<!DOCTYPE html>
<html>
  <App/>
</html>

Fetch Data

Mithril

import m from "mithril";

export default function App() {
  let isLoading = false;
  let error = null;
  let users = [];

  async function fetchUsers() {
    isLoading = true;
    try {
      const { results } = await m.request(
        "https://randomuser.me/api/?results=3"
      );
      users = results;
    } catch (err) {
      error = err;
    }
    isLoading = false;
  }

  return {
    oninit: fetchUsers,
    view() {
      if (isLoading) return m("p", "Fetching users...");
      if (error) return m("p", "An error occurred while fetching users");
      return users.map((user) =>
        m(
          "li",
          { key: user.login.uuid },
          m("img", { src: user.picture.thumbnail, alt: "user" }),
          m("p", `${user.name.first} ${user.name.last}`)
        )
      );
    },
  };
}

Marko

<await(fetch("https://randomuser.me/api/?results=3").then(res => res.json()))>
  <@placeholder>
    <p>Fetching users...</p>
  </@placeholder>
  <@catch|error|>
    <p>An error occurred while fetching users</p>
  </@catch>
  <@then|{ results: users }|>
    <ul>
      <for|{ picture, name }| of=users>
        <li>
          <img src=picture.thumbnail alt="user">
          <p>${name.first} ${name.last}</p>
        </li>
      </for>
    </ul>
  </@then>
</await>

Performance and Bundle Size

Mithril

  • Extremely lightweight (~10KB gzipped)
  • Fast virtual DOM diffing
  • Built-in routing and XHR
  • Minimal API surface

Marko

  • Compile-time optimizations
  • Streaming rendering
  • Partial hydration
  • Automatic code splitting
  • Small runtime footprint

Learning Curve

Mithril

  • Simple and straightforward API
  • Minimal concepts to learn
  • Functional programming approach
  • Excellent documentation

Marko

  • Template-first approach
  • Unique syntax to learn
  • Built-in streaming capabilities
  • Growing documentation and community

Conclusion

Choose Mithril if you:

  • Need a minimal, lightweight framework
  • Prefer functional programming
  • Want built-in routing and XHR
  • Value simplicity and performance
  • Are building small to medium applications

Choose Marko if you:

  • Need streaming server rendering
  • Want compile-time optimizations
  • Need partial hydration
  • Value template-first development
  • Are building large, scalable applications

Both frameworks excel in different scenarios:

  • Mithril is perfect for lightweight applications that need performance and simplicity
  • Marko shines in applications that need streaming capabilities and compile-time optimizations

The choice between Mithril and Marko often depends on your specific needs:

  • Use Mithril for small to medium projects that need performance and simplicity
  • Use Marko for applications that need streaming capabilities and compile-time optimizations






Decouvrez plus d’Offres de la plateform ItGalaxy.io :

Découvrez notre gamme complète de services et formations pour accélérer votre carrière.

1. Nous contactez

  • Description: Besoin de Formation et des Solutions cloud complètes pour vos applications
  • Links:

2. Infra as a Service

  • Description: Infrastructure cloud évolutive et sécurisée
  • Links:

3. Projets Développeurs


4. Développeurs


5. Formations Complètes


6. Marketplace

7. Blogs


This website is powered by ItGalaxy.io