miso

repository·master·Indexed 25 days ago

https://github.com/dmjio/miso

A 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.

Tokens
7K
Snippets
15
Records
37
Agent score
81%

What's inside miso

  1. What is miso.js?

    master
    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.
  2. What is miso?

    master
    miso 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.
  3. Explore Miso examples

    master

    A wide variety of Miso examples are hosted under the haskell-miso GitHub 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.

  4. How the Model-View-Update (MVU) architecture works in miso

    master

    miso follows the Model-View-Update pattern.

    • Component: Parameterized by a model type and an action type.
    • Update: The update function maps Actions to Effect values. Effect is a monad over a Reader/Writer/State stack that allows modifying the model and scheduling IO operations.
    • 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 a Sink.
  5. Set up a Docker-based development workflow for miso

    master

    You 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:

    1. Clone the repository and navigate to the docker directory:
      git clone https://github.com/dmjio/miso
      cd miso/docker
    2. Build the environment (this step may take a significant amount of time):
      docker-compose build
    3. Start the containers:
      docker-compose up
    4. 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
    5. View the app: Open index.html on your host machine. Any changes made to files within miso/sample-app will trigger an automatic rebuild by Docker.
    git clone https://github.com/dmjio/miso
    cd miso/docker
    docker-compose build
    docker-compose up
  6. Prepare WASM artifacts for browser hosting

    master

    After 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 node version (tested with v24.2.0).

    1. Create a hosting directory (e.g., app.wasmexe).
    2. Generate ghc_wasm_jsffi.js using the post-link.mjs script.
    3. Copy the app.wasm payload into the directory.
    4. Include an index.html and index.js to 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.wasmexe

    Required 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-server to 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
  7. Compile miso to WebAssembly (WASM)

    master

    The miso team recommends using the WASM backend as the default for compilation. You can acquire the GHC WASM compiler via GHCup or Nix.

    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.project to use the WASM compiler and include the miso repository:

    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: True

    Build the application

    Run the following commands to build the WASM payload:

    $ wasm32-wasi-cabal update
    $ wasm32-wasi-cabal build --allow-newer

    The resulting payload will be located in the dist-newstyle/ directory.

  8. Interacting with HTTP APIs

    master

    Miso supports two primary approaches for interacting with HTTP APIs:

    1. Simple JSON APIs: Use the Fetch module directly for straightforward requests.
    2. Complex APIs: Define a Servant API and use servant-miso-client to derive client functions automatically.

    To use servant-miso-client, add the following to your cabal.project file:

    source-repository-package
      type: git
      location: https://github.com/haskell-miso/servant-miso-client
      tag: master
  9. Use the Miso binary cache with Cachix

    master

    To avoid rebuilding shared dependencies when using Nix, you can use the haskell-miso-cachix binary cache.

    Local setup:

    cachix use haskell-miso-cachix

    GitHub Actions CI setup: Use the cachix/cachix-action@v16 action in your workflow:

    - name: Install cachix
      uses: cachix/cachix-action@v16
      with:
        name: haskell-miso-cachix
    cachix use haskell-miso-cachix
  10. Compile miso to JavaScript (GHCJS)

    master

    You can compile miso applications to JavaScript using the GHC JS-backend.

    Using Nix

    You can acquire the JS backend via Nix. It is recommended to use cachix to 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.gz

    Configure cabal.project

    Update your cabal.project to 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-pkg

    Build and Run

    Build the application using cabal:

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

    To view the resulting JavaScript in your browser, use cabal list-bin with http-server:

    $ http-server $(cabal list-bin app --allow-newer).jsexe
  11. Run TypeScript unit tests

    master

    The TypeScript runtime (virtual DOM, diffing, event delegation) is tested using [bun]. To run these tests, you must first install bun via one of the following methods:

    Using curl:

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

    Using nix:

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

    Once bun is installed, run the tests with:

    bun install && bun run test
    curl -fsSL https://bun.sh/install | bash