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"readsobj.key (#)- method call:
obj # "method" $ argscallsobj.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 MisoStringReturning 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.