haskell-miso
v0.0.5
Published
miso: A tasty Haskell web and mobile framework
Readme
Miso is a small, production-ready, component-oriented, reactive, isomorphic Haskell front-end framework for quickly building highly interactive single-page web and mobile applications. It features a virtual-dom, recursive diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, 2D/3D Canvas (WebGL), 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 WASM browser mode. When used with ghciwatch this enables a rapid development workflow.
[!IMPORTANT] Check out the new Haskell miso Organization 🍜
Table of Contents
- History
- Quick Start
- Getting Started
- Setup
- Hot Reload
- Compilation
- WebAssembly
- JavaScript
- Haddocks
- Wiki
- Architecture
- Internals
- Examples
- Building examples
- HTTP
- Coverage
- Isomorphic
- Native
- Benchmarks
- Nix
- Community
- Maintainers
- Commercial
- Contributing
- Contributors
- Partnerships
- Backers
- Organizations
- License
History 📜
miso is a play on the words micro and isomorphic.
miso began in 2016 as research in:
- Expressing the Elm architecture in GHCJS as an embedded domain-specific language.
- Implementing modern JavaScript frontend techniques as found in react (e.g. Reconciliation, isomorphic)
Miso aims to bridge the gap between modern JavaScript frameworks (such as React, Vue.js, etc.) and functional programming in Haskell. It has since grown to encompass more features from the JavaScript community like Components and Renderers. Miso also now supports native development for iOS, Android and HarmonyOS devices via LynxJS and targets additional backends like Web Assembly.
[!Note] React-style Components are now added to
misoas of version1.9. This has not yet been released, we recommend developing againstmasterif you'd like to use latest features.
Quick start ⚡
[!TIP] We have a template repository that includes a sample counter application and build scripts for all platforms.
# Install nix (flakes enabled by default)
curl -fsSL https://install.determinate.systems/nix | sh -s -- install
# Clone and build
git clone https://github.com/haskell-miso/miso-sampler && cd miso-sampler
nix develop .#wasm --command bash -c 'make && make serve'The fastest way to get started is to clone and build the miso sampler repository 🍱 This has build scripts for Web Assembly, JS and vanilla GHC. This requires Nix Flakes usage. See also the section on using miso's binary cache.
Getting started
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.projectapp.cabalMain.hs
cabal.project
packages:
.
source-repository-package
type: git
location: https://github.com/dmjio/miso
branch: masterapp.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 options
if arch(wasm32)
ghc-options:
-no-hs-main
-optl-mexec-model=reactor
"-optl-Wl,--export=hs_start"
cpp-options:
-DWASM
if arch(javascript)
ld-options:
-sEXPORTED_RUNTIME_METHODS=HEAP8
executable app
import:
options
main-is:
Main.hs
build-depends:
base, miso
default-language:
Haskell2010Main.hs
This file contains a simple miso counter application.
----------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE CPP #-}
----------------------------------------------------------------------------
module Main where
----------------------------------------------------------------------------
import Miso
import qualified Miso.Html as H
import qualified Miso.Html.Property as P
import Miso.Lens
----------------------------------------------------------------------------
-- | Sum type for App events
data Action
= AddOne
| SubtractOne
| SayHelloWorld
deriving (Show, Eq)
----------------------------------------------------------------------------
-- | Entry point for a miso application
main :: IO ()
main = startApp defaultEvents app
----------------------------------------------------------------------------
-- | WASM export, required when compiling w/ the WASM backend.
#ifdef WASM
foreign export javascript "hs_start" main :: IO ()
#endif
----------------------------------------------------------------------------
-- | `component` takes as arguments the initial model, update function, view function
app :: App Int Action
app = component 0 updateModel viewModel
----------------------------------------------------------------------------
-- | Updates model, optionally introduces side effects
updateModel :: Action -> Transition Int Action
updateModel = \case
AddOne -> this += 1
SubtractOne -> this -= 1
SayHelloWorld -> io_ $ do
alert "Hello World"
consoleLog "Hello World"
----------------------------------------------------------------------------
-- | Constructs a virtual DOM from a model
viewModel :: Int -> View Int Action
viewModel x =
H.div_
[ P.className "counter"
]
[ H.button_ [ H.onClick AddOne ] [ text "+" ]
, text $ ms (x ^. counter)
, H.button_ [ H.onClick SubtractOne ] [ text "-" ]
, H.br_ []
, H.button_ [ H.onClick SayHelloWorld ] [ text "Alert Hello World!" ]
]
----------------------------------------------------------------------------Now that your project files are populated, development can begin.
Hot Reload 🔥
See the WASM browser mode section of the miso-sampler repository for usage of hot reload development w/ ghciwatch. Also see this blog post.
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.
Web Assembly
[!TIP] The Haskell
misoteam 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-cabalin your$PATH, along withwasm32-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.
[!TIP] Instead of using a
nix shell, it's possible to install the GHC WASM Flake into your environment so it will always be present on$PATH
$ nix profile install 'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org'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: TrueCall wasm32-wasi-cabal build --allow-newer and a WASM payload should be created in dist-newstyle/ directory.
$ wasm32-wasi-cabal update
$ wasm32-wasi-cabal build --allow-newerConfiguration 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.wasmYou 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.
[!TIP] We recommend using an up-to-date
nodeversion (currently tested withv24.2.0) to ensurepost-link.mjsworks properly.
# 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.jsandapp.wasmartifacts, we also need to include anindex.htmland anindex.jsfor 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.jsNow 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
Sourcestab of your browser by right-clicking and then clickingInspect.
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 (that
misouses) via Nix. Use cachix to ensure you're not building dependencies unnecessarilycachix use haskell-miso-cachix
nix-shell -p pkgs.pkgsCross.ghcjs.haskell.packages.ghc9122.ghc -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/65f179f903e8bbeff3215cd613bdc570940c0eab.tar.gz[!NOTE] This will put
javascript-unknown-ghcjs-ghcin your$PATH, along withjavascript-unknown-ghcjs-ghc-pkg. You might also need to specify in yourcabal.projectfile that you are using the JS backend.
[!TIP] Alternatively, if you'd like to install the compiler into your global environment (so you don't need to develop inside a
bashshell) you can use the following command.nix-env -iA pkgs.pkgsCross.ghcjs.haskell.packages.ghc9122.ghc -f https://github.com/NixOS/nixpkgs/archive/65f179f903e8bbeff3215cd613bdc570940c0eab.tar.gz
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]
cabalwill use theghcspecified above inwith-compiler
$ cabal update && cabal build --allow-newerConfiguring 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/javascript-ghcjs/ghc-9.12.2/app-0.1.0.0/x/app/build/app/app-tmp/Main.o )
[2 of 2] Linking dist-newstyle/build/javascript-ghcjs/ghc-9.12.2/app-0.1.0.0/x/app/build/app/app.jsexe
[!TIP] To view the JavaScript in your browser, you can use
cabal list-binandhttp-server
$ http-server $(cabal list-bin app --allow-newer).jsexe
Configuration is affected by the following files:
- cabal.project
Starting up http-server, serving /home/dmjio/Desktop/miso/sample-app/dist-newstyle/build/javascript-ghcjs/ghc-9.12.2/app-0.1.0.0/x/app/build/app/app.jsexe
http-server version: 14.1.1
http-server settings:
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none
Available on:
http://127.0.0.1:8080
http://192.168.1.114:8080
Hit CTRL-C to stop the serverHaddocks
Offical Haskell documentation of the Miso web framework.
| Platform | URL | |------|-------------| | GHCJS | Link | | GHC | Link |
Wiki
See the DeepWiki entry to explore the source code.
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
nixwith aclient/serversetup, 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 | | 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 | | MathML | A MathML example | Source | Demo | @dmjio | | Simple | A simple counter example | Source | Demo | @dmjio | | SSE | SSE (Server-sent events) Example | Source | Demo | @dmjio | | Three.js | A 3D rendering example using Three.JS | Source | Demo | @juliendehos | | Space Invaders | A Space-Invaders-like game | Source | Demo | @juliendehos | | Audio | Audio examples | Source | Demo | @juliendehos | | Video | Video examples | Source | Demo | @juliendehos | | WebVR | WebVR examples | Source | Demo | @dmjio | | Reactivity | Reactive examples | 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
See the @HaskellMiso organization for all examples, and how to build then.
Interacting with HTTP APIs 🔌
If you want to interact with an HTTP API, we recommend one of the following approaches:
For a simple JSON-based API, you can use Miso's Fetch module.
In more complex cases, you can define a Servant API and automatically obtain client functions via servant-miso-client.
The Fetch example (Source, Demo) demonstrates the necessary ingredients. Make sure to add the following to your
cabal.project:source-repository-package type: git location: https://github.com/haskell-miso/servant-miso-client tag: master
Coverage ✅
The core engine 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 | bashor
$ nix-env -iA bun -f '<nixpkgs>'and
$ bun install && bun run test--------------------------|---------|---------|-------------------
File | % Funcs | % Lines | Uncovered Line #s
--------------------------|---------|---------|-------------------
All files | 93.50 | 92.28 |
ts/happydom.ts | 100.00 | 100.00 |
ts/miso/context/dom.ts | 100.00 | 100.00 |
ts/miso/context/patch.ts | 75.00 | 81.17 | 68-76,79-84,87-93,99-107,193-202
ts/miso/dom.ts | 100.00 | 98.86 | 41,302-303
ts/miso/event.ts | 100.00 | 92.75 | 28-30,52,81,85-90,116,161
ts/miso/hydrate.ts | 100.00 | 98.15 | 11-12
ts/miso/patch.ts | 100.00 | 80.00 | 26-32,36,38,42-43,85-86,91,93
ts/miso/smart.ts | 85.00 | 93.33 | 45-48
ts/miso/types.ts | 100.00 | 100.00 |
ts/miso/util.ts | 75.00 | 78.53 | 79,83,122,137,169,172,175-186,197-202,223-229,233-236,249-254
--------------------------|---------|---------|-------------------
223 pass
0 fail
7598 expect() calls
Ran 223 tests across 9 files. [497.00ms]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.
Native📱
Miso supports the creation of iOS and Android applications via LynxJS. See the miso-lynx repository for more information.
Benchmarks 🏎️
According to benchmarks miso is competitive. miso.js depicted below is how miso performs with its JS engine relative to vanilla JS. The miso column shows performance using the GHC JS backend. We're currently researching staged meta-programming as a way to remove excess allocations in our DSL and to increase performance when compiling from Haskell.
Nix
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]
misoalso maintains a legacy version of nixpkgs known aslegacyPkgsso we can use tools likenixopsfor deployment and to buildmisowith the originalGHCJS 8.6backend.
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-cachixWhen building miso projects w/ GitHub workflow CI, we recommend the Cachix GitHub action
- name: Install cachix
uses: cachix/cachix-action@v16
with:
name: haskell-miso-cachixCommunity :octocat:
Maintainers
Commercial 🚀
Since it's launch, miso has been used in a variety of industries, including but not limited to:
- Quantitative finance
- Network security
- Defense research
- Academia
- SaaS companies
- Public sector
- Non-profit sector
- etc.
The largest miso installation known was ~200,000 lines of miso code with 10,000+ users.
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]
Backers
Become a financial contributor and help us sustain our project and community. We are very grateful and thankful for our individual sponsors.
- Moses Tschanz
- @MaxGabriel
- @maybetonyfu
- @jhrcek
- etc.
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
