miso
repository·master·Indexed 25 days ago
https://github.com/dmjio/misoA high-performance Haskell framework for building web and mobile applications using the Model-View-Update (MVU) architecture. It provides a type-safe, pure functional approach to UI development with a Virtual DOM, supporting SVG, 2D Canvas, and WebGL. miso targets both JavaScript and WebAssembly via GHC and includes a companion JavaScript package, miso.js, for rendering and event delegation.
What's inside miso
- miso.js is a JavaScript companion package for the Haskell miso project. It provides the JavaScript API necessary for rendering page elements, facilitating prerendering, and handling event delegation within a Miso application.
What is miso?
mastermiso is a Haskell library for building web and mobile applications. It is inspired by Elm and React and implements the Model-View-Update (MVU) paradigm. It uses a Virtual DOM with recursive diffing and patching, supports SVG, 2D Canvas, and WebGL, and provides type-safe client-side routing. It can target both JavaScript and WebAssembly via GHC.Explore Miso examples
masterA wide variety of Miso examples are hosted under the
haskell-misoGitHub organization. These include implementations for:- Games: 2048, Flatris, Plane, Snake, Mario, Space Invaders, Chess
- Rendering: SVG, Canvas 2D, MathML, Three.js, WebVR (A-Frame), Audio, Video
- Web APIs: Fetch (HTTP), File Reader, WebSocket, Router (Client-side routing), SSE (Server-Sent Events), Reactivity
- Reference: TodoMVC, Simple (Counter)
Each repository contains its own build instructions, and the recommended approach is to build via
nix.How the Model-View-Update (MVU) architecture works in miso
mastermiso follows the Model-View-Update pattern.
- Component: Parameterized by a
modeltype and anactiontype. - Update: The
updatefunction mapsActions toEffectvalues.Effectis a monad over a Reader/Writer/State stack that allows modifying the model and schedulingIOoperations. - View: A function that constructs a Virtual DOM from the current model.
- Subscriptions: Long-running effects are expressed as
Subscriptions that push actions into the component via aSink.
- Component: Parameterized by a
Set up a Docker-based development workflow for miso
masterYou can develop and run miso applications using only Docker and Docker Compose. This workflow allows you to edit code on your host machine while the build and execution happen within a containerized environment.
Important for Windows and Mac users: Before starting, increase your Docker settings for Memory and Swap (recommended: Memory=6GB, Swap=2GB). Failure to do so may cause the container to be killed when it exceeds the default resource limits during the build process.
Workflow Steps:
- Clone the repository and navigate to the docker directory:
git clone https://github.com/dmjio/miso cd miso/docker - Build the environment (this step may take a significant amount of time):
docker-compose build - Start the containers:
docker-compose up - Locate the build output: The sample application build is located at:
miso/sample-app/dist-newstyle/build/x86_64-linux/ghcjs-8.6.0.1/app-0.1.0.0/x/app/build/app/app.jsexe - View the app: Open
index.htmlon your host machine. Any changes made to files withinmiso/sample-appwill trigger an automatic rebuild by Docker.
git clone https://github.com/dmjio/miso cd miso/docker docker-compose build docker-compose up- Clone the repository and navigate to the docker directory:
Prepare WASM artifacts for browser hosting
masterAfter compiling to WASM, you must prepare additional artifacts to emulate the WASI interface in the browser. It is recommended to use an up-to-date
nodeversion (tested withv24.2.0).- Create a hosting directory (e.g.,
app.wasmexe). - Generate
ghc_wasm_jsffi.jsusing thepost-link.mjsscript. - Copy the
app.wasmpayload into the directory. - Include an
index.htmlandindex.jsto load the payload.
Step-by-step commands
# 1. Create directory $ mkdir -v app.wasmexe # 2. Generate FFI glue code $ $(wasm32-wasi-ghc --print-libdir)/post-link.mjs \ --input $(wasm32-wasi-cabal list-bin app --allow-newer) \ --output app.wasmexe/ghc_wasm_jsffi.js # 3. Copy WASM payload $ cp -v $(wasm32-wasi-cabal list-bin app --allow-newer) app.wasmexeRequired HTML/JS files
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/browser_wasi_shim@0.3.0/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);Serving the app
Use
http-serverto view the application:$ http-server app.wasmexe# Creates the directory for hosting $ mkdir -v 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- Create a hosting directory (e.g.,
Compile miso to WebAssembly (WASM)
masterThe
misoteam recommends using the WASM backend as the default for compilation. You can acquire the GHC WASM compiler viaGHCuporNix.Using Nix
You can use a Nix shell or install the GHC WASM Flake into your environment.
# Use a temporary nix shell $ nix shell 'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org' # Or install it to your profile $ nix profile install 'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org'Configure cabal.project
Update your
cabal.projectto use the WASM compiler and include themisorepository: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) shared: TrueBuild the application
Run the following commands to build the WASM payload:
$ wasm32-wasi-cabal update $ wasm32-wasi-cabal build --allow-newerThe resulting payload will be located in the
dist-newstyle/directory.Interacting with HTTP APIs
masterMiso supports two primary approaches for interacting with HTTP APIs:
- Simple JSON APIs: Use the
Fetchmodule directly for straightforward requests. - Complex APIs: Define a Servant API and use
servant-miso-clientto derive client functions automatically.
To use
servant-miso-client, add the following to yourcabal.projectfile:source-repository-package type: git location: https://github.com/haskell-miso/servant-miso-client tag: master- Simple JSON APIs: Use the
Use the Miso binary cache with Cachix
masterTo avoid rebuilding shared dependencies when using Nix, you can use the
haskell-miso-cachixbinary cache.Local setup:
cachix use haskell-miso-cachixGitHub Actions CI setup: Use the
cachix/cachix-action@v16action in your workflow:- name: Install cachix uses: cachix/cachix-action@v16 with: name: haskell-miso-cachixcachix use haskell-miso-cachixCompile miso to JavaScript (GHCJS)
masterYou can compile
misoapplications to JavaScript using the GHC JS-backend.Using Nix
You can acquire the JS backend via Nix. It is recommended to use
cachixto avoid unnecessary builds.# Use nix-shell with a specific nixpkgs archive $ nix-shell -p pkgs.pkgsCross.ghcjs.haskell.packages.ghc9122.ghc -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/65f179f903e8bbeff3215cd613bdc570940c0eab.tar.gz # Or install it globally $ nix-env -iA pkgs.pkgsCross.ghcjs.haskell.packages.ghc9122.ghc -f https://github.com/NixOS/nixpkgs/archive/65f179f903e8bbeff3215cd613bdc570940c0eab.tar.gzConfigure cabal.project
Update your
cabal.projectto use the JS compiler: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-pkgBuild and Run
Build the application using
cabal:$ cabal update && cabal build --allow-newerTo view the resulting JavaScript in your browser, use
cabal list-binwithhttp-server:$ http-server $(cabal list-bin app --allow-newer).jsexeRun TypeScript unit tests
masterThe TypeScript runtime (virtual DOM, diffing, event delegation) is tested using [bun]. To run these tests, you must first install
bunvia one of the following methods:Using curl:
curl -fsSL https://bun.sh/install | bashUsing nix:
$ nix-env -iA bun -f '<nixpkgs>'Once
bunis installed, run the tests with:bun install && bun run testcurl -fsSL https://bun.sh/install | bashManual Setup with GHCup and Cabal
master