Skip to content

dmjio/miso

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

miso

A tasty Haskell front-end web framework 🍜

Matrix #haskell-miso:matrix.org Haskell Cachix Build Status Discord Hackage LICENSE

Miso is a small, production-ready, component-oriented, isomorphic Haskell front-end framework for quickly building highly interactive single-page web applications. It features a virtual-dom, recursive diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, Server-sent events (SSE), Websockets, type-safe servant-style routing and an extensible Subscription-based subsystem. Inspired by Elm and React. Miso is pure by default, but side effects can be introduced into the system via the Effect data type. Miso makes heavy use of the GHC Javascript FFI and therefore has minimal dependencies. Miso can be considered a shallow embedded domain-specific language for modern web programming.

Miso supports compilation to both JavaScript and WebAssembly using GHC. For hot-reload, miso uses the jsaddle library. When used with ghcid and ghciwatch this enables a rapid development workflow.

Warning

React-style Components are now added to miso as of version 1.9. This has not yet been released, we recommend developing against master if you'd like to use latest features.

Table of Contents

Quick start

To start developing applications with miso you will need to acquire GHC and cabal. This can be done via GHCup or Nix.

Tip

For new Haskell users we recommend using GHCup to acquire both GHC and cabal

Setup 🏗️

To develop and build your first miso application you will need 3 files:

  • cabal.project
  • app.cabal
  • Main.hs

cabal.project

packages:
  .

source-repository-package
  type: git
  location: https://github.com/dmjio/miso
  branch: master

app.cabal

We recommend using at least cabal-version: 2.2, this will give you the common sections feature which we will use later to allow multiple compilers to build our project (so we can target WASM and JS backends)

cabal-version: 2.2
name: app
version: 0.1.0.0
synopsis: Sample miso app
category: Web

common wasm
  if arch(wasm32)
    ghc-options:
      -no-hs-main
      -optl-mexec-model=reactor
      "-optl-Wl,--export=hs_start"
    cpp-options:
      -DWASM

executable app
  import:
    wasm
  main-is:
    Main.hs
  build-depends:
    base, miso
  default-language:
    Haskell2010

Main.hs

This file contains a simple miso counter application.

----------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE CPP               #-}
----------------------------------------------------------------------------
module Main where
----------------------------------------------------------------------------
import Miso
import Miso.String
import Miso.Lens
----------------------------------------------------------------------------
-- | Application model state
newtype Model = Model
  { _counter :: Int
  } deriving (Show, Eq)
----------------------------------------------------------------------------
counter :: Lens Model Int
counter = lens _counter $ \record field -> record { _counter = field }
----------------------------------------------------------------------------
-- | Sum type for App events
data Action
  = AddOne
  | SubtractOne
  | SayHelloWorld
  deriving (Show, Eq)
----------------------------------------------------------------------------
-- | Entry point for a miso application
main :: IO ()
main = run (startApp app)
----------------------------------------------------------------------------
-- | WASM export, required when compiling w/ the WASM backend.
#ifdef WASM
foreign export javascript "hs_start" main :: IO ()
#endif
----------------------------------------------------------------------------
-- | `defaultApp` takes as arguments the initial model, update function, view function
app :: App Model Action
app = defaultApp emptyModel updateModel viewModel
----------------------------------------------------------------------------
-- | Empty application state
emptyModel :: Model
emptyModel = Model 0
----------------------------------------------------------------------------
-- | Updates model, optionally introduces side effects
updateModel :: Action -> Effect Model Action
updateModel = \case
  AddOne        -> counter += 1
  SubtractOne   -> counter -= 1
  SayHelloWorld -> io $ do
    consoleLog "Hello World"
    alert "Hello World"
----------------------------------------------------------------------------
-- | Constructs a virtual DOM from a model
viewModel :: Model -> View Action
viewModel x = div_ []
  [ button_ [ onClick AddOne ] [ text "+" ]
  , text $ ms (x ^. counter)
  , button_ [ onClick SubtractOne ] [ text "-" ]
  , button_ [ onClick SayHelloWorld ] [ text "Alert Hello World!" ]
  ]
----------------------------------------------------------------------------

Now that your project files are populated, development can begin.

Hot Reload

With GHC and cabal on $PATH, call cabal repl

$ cabal repl

You should see the following output in your terminal.

[1 of 2] Compiling Main             ( Main.hs, interpreted )
Ok, one module loaded.
ghci> 

Now call the main function in the GHCi REPL.

ghci> main
Running on port 8008...
<a href="http://localhost:8008">run</a>
ghci>

Note

The code running in this example is not compiled to JavaScript or WebAssembly, rather it is running the client side application on the server. It works by sending commands to a small javascript interpreter over a websocket to render elements on the page. This is provided by the jsaddle library.

If you visit http://localhost:8008, the application will be live. You can now edit Main.hs, call :r and main in GHCi, and the application will update on the screen.

Note

Instead of typing :r and main manually inside of GHCi on every file change, you can use ghcid or ghciwatch tools to do it automatically.

Tip

For users accustomed to a react.js worfklow, we highly recommend using either ghcid or ghciwatch.

Below is an example of usage with ghcid

$ ghcid -c 'cabal repl app' -T=Main.main

This screenshot shows the hot-reload functionality in action. This is using ghcid, jsaddle and miso.

Image

Compilation

When done developing, we can compile to Web Assembly or JavaScript for distribution. This is done by acquiring a GHC that supports WebAssembly or JavaScript. We recommend acquiring these backends using GHCUp or Nix.

Tip

For new Haskell users we recommend using GHCup to acquire the WASM and JS backends.

Image Web Assembly

Warning

The Haskell miso team currently recommends using the WASM backend as the default backend for compilation.

Using GHCup you should be able to acquire the GHC WASM compiler.

For instructions on how to add a third-party channel with GHCup, please see their official README.md

Tip

For Nix users it is possible to acquire the WASM backend via a Nix flake

$ nix shell 'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org'

Note

This will put wasm32-wasi-cabal in your $PATH, along with wasm32-wasi-ghc. Since the WASM backend is relatively new, the ecosystem is not entirely patched to support it. Therefore, we will need to use patched packages from time to time.

Update your cabal.project to the following

  • cabal.project
packages:
  .

with-compiler:
  wasm32-wasi-ghc

with-hc-pkg:
  wasm32-wasi-ghc-pkg

source-repository-package
  type: git
  location: https://github.com/dmjio/miso
  branch: master

if arch(wasm32)
  -- Required for TemplateHaskell. When using wasm32-wasi-cabal from
  -- ghc-wasm-meta, this is superseded by the global cabal.config.
  shared: True

  -- https://github.com/haskellari/time-compat/issues/37
  -- Older versions of time don't build on WASM.
  constraints: time installed
  allow-newer: time

  -- https://github.com/haskellari/splitmix/pull/73
  source-repository-package
    type: git
    location: https://github.com/amesgen/splitmix
    tag: 5f5b766d97dc735ac228215d240a3bb90bc2ff75

Call wasm32-wasi-cabal build --allow-newer and a WASM payload should be created in dist-newstyle/ directory.

$ wasm32-wasi-cabal build --allow-newer
Configuration is affected by the following files:
- cabal.project
Resolving dependencies...
Build profile: -w ghc-9.12.2.20250327 -O1
In order, the following will be built (use -v for more details):
 - app-0.1.0.0 (exe:app) (configuration changed)
Configuring executable 'app' for app-0.1.0.0...
Preprocessing executable 'app' for app-0.1.0.0...
Building executable 'app' for app-0.1.0.0...
[1 of 1] Compiling Main             ( Main.hs, dist-newstyle/build/wasm32-wasi/ghc-9.12.2.20250327/app-0.1.0.0/x/app/build/app/app-tmp/Main.o )
[2 of 2] Linking dist-newstyle/build/wasm32-wasi/ghc-9.12.2.20250327/app-0.1.0.0/x/app/build/app/app.wasm

You have now successfully compiled a Haskell miso application to WebAssembly 🔥


But, we're not done yet. In order to view this in the browser there are still a few more steps. We need to add some additional files that emulate the WASI interface in the browser (A browser WASI shim).

Note

The GHC WASM backend can execute any Haskell program in a WASI-compliant runtime (e.g. wasmtime) See the official documentation for more information.

To start, we recommend creating an app.wasmexe folder to store the additional artifacts required.

# Creates the directory for hosting
$ mkdir -v app.wasmexe
mkdir: created directory 'app.wasmexe'

# This command produces `ghc_wasm_jsffi.js`, which ensures our FFI works properly.
$ $(wasm32-wasi-ghc --print-libdir)/post-link.mjs \
   --input $(wasm32-wasi-cabal list-bin app --allow-newer) \
   --output app.wasmexe/ghc_wasm_jsffi.js

# This copies the `app.wasm` payload into `app.wasmexe`
$ cp -v $(wasm32-wasi-cabal list-bin app --allow-newer) app.wasmexe
Configuration is affected by the following files:
- cabal.project
'/home/dmjio/Desktop/miso/sample-app/dist-newstyle/build/wasm32-wasi/ghc-9.12.2.20250327/app-0.1.0.0/x/app/build/app/app.wasm' -> 'app.wasmexe'

Note

Along with the above ghc_wasm_jsffi.js and app.wasm artifacts, we also need to include an index.html and an index.js for loading the WASM payload into the browser.

  • index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sample miso WASM counter app</title>
  </head>
  <body>
    <script src="index.js" type="module"></script>
  </body>
</html>
  • index.js
import { WASI, OpenFile, File, ConsoleStdout } from "https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/dist/index.js";
import ghc_wasm_jsffi from "./ghc_wasm_jsffi.js";

const args = [];
const env = ["GHCRTS=-H64m"];
const fds = [
  new OpenFile(new File([])), // stdin
  ConsoleStdout.lineBuffered((msg) => console.log(`[WASI stdout] ''${msg}`)),
  ConsoleStdout.lineBuffered((msg) => console.warn(`[WASI stderr] ''${msg}`)),
];
const options = { debug: false };
const wasi = new WASI(args, env, fds, options);

const instance_exports = {};
const { instance } = await WebAssembly.instantiateStreaming(fetch("app.wasm"), {
  wasi_snapshot_preview1: wasi.wasiImport,
  ghc_wasm_jsffi: ghc_wasm_jsffi(instance_exports),
});
Object.assign(instance_exports, instance.exports);

wasi.initialize(instance);
await instance.exports.hs_start(globalThis.example);

The app.wasmexe folder will now look like:

❯ ls app.wasmexe
 app.wasm
 ghc_wasm_jsffi.js
 index.html
 index.js

Now you can host and view the app.wasm payload in a web browser.

$ http-server app.wasmexe

Tip

You can inspect the WASM payload in the Sources tab of your browser by right-clicking and then clicking Inspect.

Image

JavaScript

Using GHCup you should be able to acquire the latest GHC JS-backend compiler.

Tip

For Nix users it is possible to acquire the latest JS backend via Nix

❯ nix-shell -p pkgs.pkgsCross.ghcjs.haskell.packages.ghc9121.ghc '<nixpkgs>'

Note

This will put javascript-unknown-ghcjs-ghc in your $PATH, along with javascript-unknown-ghcjs-ghc-pkg. You might also need to specify in your cabal.project file that you are using the JS backend.

  • cabal.project
packages:
  .

source-repository-package
  type: git
  location: https://github.com/dmjio/miso
  branch: master

with-compiler:
  javascript-unknown-ghcjs-ghc

with-hc-pkg:
  javascript-unknown-ghcjs-ghc-pkg

Note

cabal will use the ghc specified above in with-compiler

$ cabal update && cabal build --allow-newer

Haddocks

Offical Haskell documentation of the Miso web framework.

Platform URL
GHCJS Link
GHC Link

Architecture

For constructing client and server applications, we recommend using one cabal file with two executable sections, where the buildable attribute set is contingent on the compiler. An example of this layout is here.

Tip

For more information on how to use nix with a client/server setup, see the nix scripts for https://haskell-miso.org.

Internals ⚙️

For some details of the internals and general overview of how miso works, see the Internals.

Examples

For real-world examples of Haskell miso applications, see below.

Name Description Source Link Live Demo Link Author
TodoMVC A classic TodoMVC implementation Source Demo @dmjio
2048 A clone of the 2048 game Source Demo @ptigwe
Flatris A Tetris-like game Source Demo @ptigwe
Plane A flappy-birds-like game Source Demo @Lermex
Snake The classic Snake game Source Demo @lbonn
SVG An example showcasing SVG rendering Source Demo @dmjio
Fetch An example demonstrating AJAX requests Source Demo @dmjio
File Reader A FileReader API example Source Demo @dmjio
WebGL A 3D rendering example using Three.JS Source Demo @dmjio
Mario A Super Mario physics example Source Demo @dmjio
WebSocket A simple WebSocket example Source Demo @dmjio
Router A client-side routing example Source Demo @dmjio
Canvas 2D A 2D Canvas rendering example Source Demo @dmjio

Building examples

The easiest way to build the examples is with the nix package manager

Tip

Use cachix to ensure you're not building dependencies unnecessarily cachix use haskell-miso-cachix

$ git clone https://github.com/dmjio/miso
$ cd miso
$ nix-build -A miso-examples

This will compile all the examples to JavaScript into a folder named result.

➜ tree -d ./result/bin
./result/bin
|-- canvas2d.jsexe
|-- file-reader.jsexe
|-- mario.jsexe
|   `-- imgs
|-- mathml.jsexe
|-- router.jsexe
|-- simple.jsexe
|-- svg.jsexe
|-- tests.jsexe
|-- threejs.jsexe
|-- todo-mvc.jsexe
|-- websocket.jsexe
`-- fetch.jsexe

Note

To see examples, we recommend hosting them with a web server (we use http-server)

cd result/bin/todo-mvc.jsexe && http-sever
Serving HTTP on 0.0.0.0 port 8000 ...

Coverage ✅

The core algorithmic component of miso is the diff function. It is responsible for all DOM manipulation that occurs in a miso application and has 100% code coverage. Tests and coverage made possible using bun.

Note

To run the tests and build the coverage report ensure bun is installed.

$ curl -fsSL https://bun.sh/install | bash

or

$ nix-env -iA bun -f '<nixpkgs>'

and

$ bun install && bun run test
--------------------|---------|---------|-------------------
File                | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|---------|-------------------
All files           |   92.37 |   85.48 |
 ts/happydom.ts     |  100.00 |  100.00 |
 ts/miso/dom.ts     |  100.00 |  100.00 |
 ts/miso/event.ts   |   90.91 |   81.62 |
 ts/miso/hydrate.ts |   80.00 |   91.24 |
 ts/miso/smart.ts   |  100.00 |  100.00 |
 ts/miso/util.ts    |   83.33 |   40.00 |
--------------------|---------|---------|-------------------

 84 pass
 0 fail

Isomorphic ☯️

Isomorphic javascript is a technique for increased SEO, code-sharing and perceived page load times. It works in two parts. First, the server sends a pre-rendered HTML body to the client's browser. Second, after the client javascript application loads, the pointers of the pre-rendered DOM are copied into the virtual DOM (a process known as hydration), and the application proceeds as normal. All subsequent page navigation is handled locally by the client, while avoiding full-page postbacks.

Note

The miso function is used to facilitate the pointer-copying behavior client-side.

Benchmarks 🏎️

According to benchmarks, miso is among the fastest functional programming web frameworks, second only to Elm.

Nix nixos-snowflake

Nix is a powerful option for building web applications with miso since it encompasses development workflow, configuration management, and deployment. The source code for haskell-miso.org is an example of this.

Tip

If unfamiliar with nix, we recommend @Gabriella439's "Nix and Haskell in production" guide.

Pinning nixpkgs 📌

By default miso uses a known-to-work, pinned version of nixpkgs known as pkgs.

Note

miso also maintains a legacy version of nixpkgs known as legacyPkgs so we can use tools like nixops for deployment and to build miso with the original GHCJS 8.6 backend.

Binary cache

nix users on a Linux or OSX distros can take advantage of a binary cache for faster builds. To use the binary cache follow the instructions on cachix.

Tip

We highly recommend nix users consume the cachix cache. cachix use haskell-miso-cachix.

$ cachix use haskell-miso-cachix

Maintainers

@dmjio

Contributing

Feel free to dive in! Open an issue or a submit Pull Request.

See CONTRIBUTING for more info.

Contributors 🦾

Note

This project exists thanks to all the people who contribute

Partnerships 🤝

If you'd like to support this project financially, be it through requesting feature development, or a corporate partnership, please drop us a line and we will be in touch shortly.

[email protected]

Financial contributors

Become a financial contributor and help us sustain our project and community. We are very grateful and thankful for our individual sponsors.

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. We are also very grateful and thankful for our corporate sponsors.

License

BSD3 © dmjio