miso

Platform

JavaScript EDSL & FFI

Miso.DSL provides a JavaScript DSL inspired by jsaddle for interacting with the browser from Haskell. It works identically on the WASM and JS backends.

Key operators

(!)
property access: obj ! "key" reads obj.key
(#)
method call: obj # "method" $ args calls obj.method(args)
jsg
access a global JS variable by name
jsgf
call a global JS function by name with arguments
-- Read document.body.children.length
document <- jsg "document"
len :: Int <- fromJSValUnchecked =<< (document ! "body" ! "children" ! "length")

-- Call console.log("hello")
console <- jsg "console"
console # "log" $ [ "hello" :: MisoString ]

Marshalling

ToJSVal converts Haskell values to JSVal for passing into JavaScript; FromJSVal converts back. fromJSValUnchecked throws on failure; fromJSVal is the safe Maybe variant.

Inline JavaScript (QuasiQuotation)

Miso.FFI.QQ provides the js quasi-quoter for embedding JavaScript directly in Haskell source. Any Haskell binding in scope can be interpolated with ${varName} — miso uses the binding's ToJSVal instance to marshal it at runtime.

{-# LANGUAGE QuasiQuotes #-}
import Miso.FFI.QQ (js)

update :: Action -> Effect context props model Action
update = \case
  Log msg -> io_ [js| console.log(${msg}) |]

data Action = Log MisoString

Returning values from JavaScript

The return type is inferred from the call site via FromJSVal. Use an explicit annotation or a do-binding to drive inference:

factorial :: Int -> IO Int
factorial n = [js|
  let x = 1;
  for (let i = 1; i <= ${n}; i++) { x *= i; }
  return x;
|]

Haskell variables referenced inside the quoter must be in scope at the splice site; the compiler reports an error if a ${name} has no binding.

Miso.FFI

Miso.FFI collects typed wrappers for common browser calls: consoleLog, getElementById, focus, windowInnerWidth, addEventListener, callbacks (asyncCallback), fetch, scrollIntoView, requestFullscreen and more. Mutable JS collections live in Miso.Data.Array, Miso.Data.Map and Miso.Data.Set.