| Copyright | (C) 2016-2026 David M. Johnson (@dmjio) |
|---|---|
| License | BSD3-style (see the file LICENSE) |
| Maintainer | David M. Johnson <code@dmj.io> |
| Stability | experimental |
| Portability | non-portable |
| Safe Haskell | None |
| Language | Haskell2010 |
Miso
Description
Miso 🍜
miso is a library for building web and native user interface applications in Haskell. See the GitHub group.
It provides a React-like programming experience for a simple Haskell dialect that emphasizes
- performance
- purity
- simplicity
- extensibility
- composability
miso supports common areas that arise naturally in web development:
- DOM manipulation:
misouses a Virtual DOM with diffing algorithm that is responsible for all DOM modification andComponentlifecycle hooks. - Event delegation: All event listeners are attached to a top-level element
(typically
<body>). When raised, events are routed through the virtual DOM to Haskell event handlers which cause application state changes. Internallymisovirtualizes both thecaptureandbubblephases of the browser when it performs event routing. - Prerendering: Prerendering is a process where the server delivers HTML
to the client before the JavaScript (or Web Assembly) application bootstraps.
Instead of performing an initial draw, the application will create and populate the virtual DOM from the actual DOM.
This is a process known as "hydration". This avoids unnecessary page draws on initial page
load and enhances search engine optimization.
misoprovides its own HTML rendering (Miso.Html.Render) to render HTML on the server and themisofunction exists on the client to "hydrate" the virtual DOM with the DOM. - Components: A
Componentcan be considered an instance of amisoapplication. AComponentcontains user-defined state, logic for updating this state, and a function for creating UI templates from this user-defined state.Componentcan nest otherComponentbecausemisois defined recursively. - Custom renderers: The underlying DOM operations are able to be abstracted. This allows a custom rendering engine to be used. This is seen in the miso-lynx project (which allows miso to target mobile phone devices).
- Lifecycle hooks:
Componentexposemountandunmountlifecycle hooks. This allow users to define custom logic that will execute when aComponentmounts or unmounts.onCreatedandonDestroyedareVNodespecific lifecycle hooks. These hooks are commonly used forComponentcommunication and for third-party integration with JavaScript libraries. - State management:
Componentmodelstate can be manipulated using Miso.Lens or Miso.State in response to application events.
The Model-View-Update pattern
The core type of miso is Component. The Component API adheres to the Elm
MVU (model-view-update) interface. This is similar to a left-fold, where the Component model
will be updated via a list of action given a specific update function, and rendered via view.
- model: This can be any user-defined type in Haskell. An
Eqconstraint is required. We recommend using the default derivedEqinstance. - view:
This is the templating function that is used to construct a new virtual DOM (or HTML if rendering on the server).view:: model ->Viewmodel action - update:
Theupdate:: action ->Effectparent model actionupdatefunction handles how themodelevolves over time in response to events that are raised by the application. This function takes anyaction, updating themodeland optionally introducingIOinto the system.
Your first Component
To define a Component the component smart constructor can be used.
Below is an example of a simple counter Component.
-----------------------------------------------------------------------------
module Main where
-----------------------------------------------------------------------------
import Miso
import Miso.Lens
import qualified Miso.Html.Element as H
import qualified Miso.Html.Event as HE
import qualified Miso.Html.Property as HP
-----------------------------------------------------------------------------
* - The type of the parent Component model
| * - The type of the current Component's model
| | * - The type of the action that updates the model
| | |
counter :: Component parent Int Action
counter = component m u v
where
m :: Int
m = 0
u :: Action -> Effect parent Int Action
u = \case
Add -> this += 1
Subtract -> this -= 1
v :: Int -> View Int Action
v x = H.div_
[ H.button_ [ HE.onClick Add, HP.id_ "add" ] [ "+" ]
, text (ms x)
, H.button_ [ HE.onClick Subtract, HP.id_ "subtract" ] [ "-" ]
]
-----------------------------------------------------------------------------
main :: IO ()
main = startApp defaultEvents counter
-----------------------------------------------------------------------------
data Action
= Add
| Subtract
deriving (Eq, Show)
-----------------------------------------------------------------------------
Running your first Component
The startApp (or miso) functions are used to run the above Component.
main :: IO () main =startAppdefaultEventscounter
The startApp function is what we recommend using first. It sets up event listeners and performs the initial page draw.
The startApp function assumes that <body> is empty, and it will begin drawing the Component View from <body>.
The miso function (and also the prerender function) assume that <body> has already been populated by the results of the view function.
Instead of drawing, miso will perform hydration.
If the structures do not match miso will fallback to drawing the page from scratch (clearing the contents of <body> first).
It is possible to execute an initial action when a Component is first mounted. See the mount (and similarly unmount) hooks.
data Action = Init main :: IO () main =startAppdefaultEventscounter {mount= Just Init } update ::Appmodel Action update = \case Init ->io_(consoleLog"hello world!")
Component composition
miso Component can contain other Component. This is
accomplished through the Component mounting combinator (+>). This combinator
is responsible for encoding a typed Component hierarchy, allowing Component
type-safe read-only access to their parent model state.
This combinator unifies the parent model with the child parent, and
subsequently the grandchild parent unifies with the child model. This
gives us a correct-by-construction Component hierarchy.
(+>) :: forall child model action a . Eq child =>MisoString->Componentmodel child action ->Viewmodel a key+>vcomp =VComp[Property"key" (toJSONkey) ] (SomeComponentvcomp)
Practically, using this combinator looks like:
view :: Int ->ViewInt action view x =div_[id_"container" ] [ "counter"+>counter ]
You'll notice the "counter" string was specified. This is a unique Key
to identify a Component at runtime. These keys are very important when
diffing two Component together. When intentionally replacing Component it is important
to specify a new Key, otherwise the Component will not be unmounted.
It is possible to mount a component using the mount_ function, which avoids specifying a key_, but this should only be used
when the user is certain they will not be diffing their Component with another Component. When in doubt, use the (+>) combinator
and key_ your Component.
Lastly, note also the signature of startApp.
startApp::Eqmodel =>Events->Appmodel action -> IO ()
The App type signature is a synonym for Component ROOT
typeAppmodel action =ComponentROOTmodel action
ROOT is a type tag that encodes a Component as top-level. Which means it has no parent, hence we mark parent as ROOT.
data ROOT
startApp and miso will always infer parent as ROOT.
VComp lifecycle hooks
Component are mounted on the fly during diffing. All Component are equipped with mount and unmount hooks. This allows the defining of custom actions that will be processed in response to lifecycle events.
VNode lifecycle hooks
Similar to Component lifecycle hooks, all VNode also expose lifecycle hooks.
This is convenient for initializing and deinitializing third-party libraries (as seen below with highlight.js)
{-# LANGUAGE QuasiQuotes -#}
{-# LANGUAGE MultilineStrings -#}
import Miso
import Miso.FFI.QQ (js)
data Action = Highlight DOMRef
update :: Action -> Effect parent model Action
update = \case
Highlight domRef -> io_ $ do
[js| hljs.highlight(${domRef}) |]
view :: model -> View model Action
view x =
code_
[ onCreatedWith Highlight
]
[ """
function addOne (x) { return x + 1; }
"""
]
Key
A Key is a unique identifier used to optimize diffing.
Virtual DOM nodes can be "keyed" (See key_). Keys have multiple meanings in miso (and react).
- Keys are used to optimize child node list diffing.
When two lists of elements are being diffed, as long as they all have unique keys, diffing large child lists will be much faster. This optimization automatically occurs when all the elements in a VNode child list contain unique keys. Unless all View nodes in a child list are keyed, this optimization will not fire.
- Keys are used to compare two identical nodes.
If two VNode are being compared (or two VComp) and their keys differ, the old node will be destroyed and a new one created. Otherwise, the underlying DOM node won't be removed, but its properties will be diffed. In the case of diffing two Component (the VComp case), if the keys differ, the unmount phase will be triggered for the old VComp and the mount phase will be triggered for the new Component. The underlying DOM reference will be replaced.
See the key_ property for usage (and smart constructors like textKey_ and (+>) as well).
ul_[] [li_[key_"key-1" ] [ "a" ] ,li_[key_"key-2" ] [ "b" ] , "key-3"+>counter ,textKey"key-4" "text here" ]
View DSL
The View type is the core type for templating a web page. It is similar to
a Rose tree data structure. This is how the Virtual DOM is constructed. It is
mutually recursive with the Component type (via the view function), which allows us to embed
Component inside other Component.
dataViewmodel action =VNodeNamespaceTag[Attributeaction] [Viewmodel action] |VText(MaybeKey)MisoString|VComp[Attributeaction] (SomeComponentmodel)
VNode and VText have a one-to-one mapping from the virtual DOM to the physical DOM. The VComp constructor is abstract and does not contain a reference to the physical DOM. The existential type of SomeComponent is defined recursively in terms of View and is what allows us to embed other polymorphic Component.
dataSomeComponentparent = forall model action . Eq model =>SomeComponent(Componentparent model action)
The smart constructors:
are used to build VNode, VText and VComp respectively. A list of all the smart constructors defined in terms of node (e.g. div_) can be found in Miso.Html.Element.
Events
- Event Delegation
By default all events are delegated through <body>. Miso supports both capture and bubble phases of browser events.
Users can handle both phases in their applications.
- Using events
Miso exposes a defaultEvents for convenience, these events are commonly used events and listened for on <body>. They get routed through the View to the virtual DOM node that raised the event. Other Events are exposed as conveniences (e.g. touchEvents). All events required by all Component must be combined together for use when running your application (e.g. keyboardEvents <> touchEvents).
touchEvents::EventstouchEvents= M.fromList [ ("touchstart",BUBBLE) , ("touchcancel",BUBBLE) , ("touchmove",BUBBLE) , ("touchend",BUBBLE) ]
- Defining event handlers
Users can define their own event handlers using the on combinator. By default this will define an event in the BUBBLE phase. See onCapture for handling events during the CAPTURE phase. See the the module Miso.Html.Event for many predefined events.
onChangeWith:: (MisoString->DOMRef-> action) ->AttributeactiononChangeWith=on"change"valueDecoder
- Decoding events
After an event has been raised, one can extract information from the event for use in their application. This is accomplished through a Decoder. Many common decoders are available for use in Miso.Event.Decoder.
dataDecodera =Decoder{decoder::Value->Parsera ,decodeAt::DecodeTarget} -- | Example of a customDecoderfor thevalueproperty of an event target.valueDecoder::DecoderMisoStringvalueDecoder= Decoder {..} where decodeAt =DecodeTarget["target"] decoder =withObject"target" $ \o -> o .: "value"
Attributes / Properties
The Attribute type allows us to define web handlers that map browser events to
Haskell data types (e.g. onClick), along with specifying properties on DOM elements
(like className and id_). See Miso.Property and Miso.Html.Property for more information.
div_[id_"some-id",className"some-class" ] [ ]
Effect
The Effect type is used to mutate the model over time in response to action.
This allows IO to be scheduled for evaluation by the miso scheduler.
Note: IO is never evaluated inside of Effect, it is only scheduled.
There is no MonadIO instance for Effect.
The Effect type is defined as a RWS.
typeEffectparent model action =RWS(ComponentInfoparent) [Scheduleaction] model ()
IO can be performed either synchronoulsy or asynchronously. By default all IO is asynchronous
Asynchronous IO
withSink: The core function (from which most other combinators are defined) that gives users access to the underlying eventSink. This also allows us to introduceIOinto the system. Themisoscheduler attaches exception handlers to allIOactions.- For maximum flexibility, the
MonadWriterinstance (tell) can be used to scheduleIO(see thewithSinkimplementation).
Synchronous IO
sync: Forces the scheduler to evaluateIOsynchronously. It is recommended to use theiofunction by default,sync*will* block the scheduler.
Sink
typeSinka => a ->IO()
The Sink function allows one to write any action to the global event queue.
Managing model state.
Any MonadState function is allowed for use when manipulating model, get, put, etc. See Miso.State.
The MonadReader instances allows the retrieval of ComponentInfo within Effect.
ComponentInfo provides the current ComponentId the parent ComponentId, and the DOMRef (_componentDOMRef) that the Component is mounted on.
Component communication
Asynchronous communication
Component are able to communicate asynchronously via a message-passing system.
The miso runtime exposes a few primitives to allow Component communication.
All Component have a mailbox that can receive messages (as Value) from other Component.
This is meant to be used with the checkMail function. The mail function allows a Component to send a specific message (as Value) to another Component via its ComponentId.
The ComponentId can be found in the Effect monad. Using ask will return a ComponentInfo. The Component receiving
the message will find it in its mailbox.
miso has support for the publisher / subscriber concurrency pattern. See the Miso.PubSub module for more information.
Synchronous communication
Experimental support for data bindings (where Component model can synchronize fields via a Lens in response to model differences along the parent-child relationship). See the Miso.Binding module for more information, and the miso-reactive example. *Warning*: This is still considered experimental.
While not direct communication, a Component can receive read-only access to its parent state via the parent function.
Subscriptions
A Sub is any long-running operation that is external to a Component, but that can write
to a Component Sink. Sub come in two flavors, a dynamic Sub (via startSub / stopSub) and subs.
main :: IO () main =startAppdefaultEventsapp {subs= [ timerSub ] } timerSub ::SubAction timerSub sink =forever$ (threadDelay100000) >> sink Log data Action = Log
The subs field of Component contains Sub that exist for the lifetime of that Component.
When a Component unmounts, these Sub will be stopped, and their resources finalized.
onLineSub:: (Bool -> action) ->SubactiononLineSubf sink =createSubacquire release sink where release (cb1, cb2) = do FFI.windowRemoveEventListener "online" cb1 FFI.windowRemoveEventListener "offline" cb2 acquire = do cb1 <- FFI.windowAddEventListener "online" (const $ sink (f True)) cb2 <- FFI.windowAddEventListener "offline" (const $ sink (f False)) pure (cb1, cb2)
At times its necessary to dynamically generate a Sub in reponse to an event (e.g. starting a Miso.WebSocket connection
when a user logs in). The startSub and stopSub functions facilitate dynamic Sub creation / removal.
update = \case
StartTimer -> startSub ("timer" :: MisoString) timerSub
StopTimer -> stopSub "timer"
Log -> io_ (consoleLog "log")
where
timerSub :: Sub Action
timerSub sink = forever $ (threadDelay 100000) >> sink Log
data Action = Log
createSub is a helper function for creating a Sub using the bracket pattern.
This ensures that event listeners can be unregistered when a Component unmounts. For example usage
please see the Miso.Subscription sub modules. createSub is only meant to be used in scenarios where
custom event listeners are required (shown below).
(2D/3D) Canvas support
Miso has full 2D and 3D canvas support. See the Miso.Canvas module, the miso-canvas example, along with the three-miso package.
State management (Lens)
A simple Lens implementation is included with miso, this was done for convenience, to minimize dependencies, reduce payload size, and provide a simpler interface. See Miso.Lens. This is a simple lens formulation that exposes many common MonadState lenses (e.g. ) that work in the +=Effect monad. Miso.Lens is not required for use, any lens library will also work with miso.
HTML
Miso's virtual DOM DSL (View) type can be repurposed to render HTML. See the Miso.Html.Render module for more information. This uses the ToHtml class.
JavaScript EDSL
Miso provides a Javascript DSL (inspired by jsaddle) via Miso.DSL.
See the ToJSVal / FromJSVal typeclasses when marshaling to and from Haskell to JavaScript. See also the jsg
function for accessing JavaScript objects that exist in the global scope.
document ::JSVal<-jsg"document" :: IOJSVallen ::Int<-fromJSValUnchecked=<< (document ! "body" ! "children" ! "length")
QuasiQuotation (inline-js)
Along with Miso.DSL, a JavaScript QuasiQuoter is now included (See Miso.FFI.QQ). This makes it easy to
integrate miso with any third-party JavaScript library. This bindings in scope can be used inside the QuasiQuoter, which
will utilize their ToJSVal instances. When returning values from the QuasiQuoter, the FromJSVal instance will
be used Haskell.
{-# LANGUAGE QuasiQuotes #-}
import Miso.FFI.QQ (js)
update :: Action -> Effect parent model Action
update = \case
Log msg -> io_ [js| console.log(${msg}) |]
data Action = Log MisoString
Routing
miso exposes its own internal router. See Miso.Router for more information. The router is inspired by both the servant and the web-routes package. The router has its own Sub called routerSub meant for easy integration with the History API.
MisoString
miso includes its own string type named MisoString. This is the preferred string type to use
in order to maximize application performance. Since strings are ubiquitous in applications we
want to minimize the copying of these strings between the JS and Haskell heaps. MisoString accomplishes this.
MisoString is a synonym for JSString when using the JS / WASM backends. When using vanilla GHC
it is Text. See Miso.String for more information.
MisoString is also used in the Miso.Util.Lexer and Miso.Util.Parser modules.
JSON
Miso.JSON is a microaeson
implementation that uses MisoString. This is done for performance reasons and to minimize the dependency burden. Miso.JSON is used
in Miso.Event.Decoder, Miso.Fetch, Miso.WebSocket modules respectively.
Styles
Miso prescribes no exact CSS style usage. It is up to the user's discretion on how best to handle styles in their application. Inline styles, external stylesheets and the Miso.CSS DSL can all be used. See also miso-ui for an example of what is possible.
Development
When developing miso applications interactively it is possible to append styles and scripts to the <head> portion of
the page when the Component mounts. This is a convenience only meant to be used in development. We recommend guarding the usage behind a flag.
main ::IO() main =startAppdefaultEventscounter where app = counter #ifdef INTERACTIVE {scripts= [Src"https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js" ] ,styles= [Href"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" ] } #endif
See the miso-sampler repository for more information.
Debugging
Sometimes things can go wrong. Common errors like using onClick but not listening for the the click event are common.
These are errors that cannot be caught statically. These can be detected by enabling DebugAll. Currently, debugging event delegation
and page hydration is supported.
counter { logLevel = DebugAll }
Internals
Internally miso uses a global event queue and a scheduler to process all events raised by Component throughout the lifetime of
an application. Events are processed in FIFO order, batched by the Component that raised them.
Synopsis
- miso :: Eq model => Events -> (URI -> App model action) -> IO ()
- prerender :: Eq model => Events -> App model action -> IO ()
- (🍜) :: Eq model => Events -> (URI -> App model action) -> IO ()
- type App model action = Component ROOT model action
- startApp :: Eq model => Events -> App model action -> IO ()
- renderApp :: Eq model => Events -> MisoString -> App model action -> IO ()
- data Component parent model action = Component {
- model :: model
- hydrateModel :: Maybe (IO model)
- update :: action -> Effect parent model action
- view :: model -> View model action
- subs :: [Sub action]
- styles :: [CSS]
- scripts :: [JS]
- mountPoint :: Maybe MountPoint
- logLevel :: LogLevel
- mailbox :: Value -> Maybe action
- bindings :: [Binding parent model]
- eventPropagation :: Bool
- mount :: Maybe action
- unmount :: Maybe action
- component :: model -> (action -> Effect parent model action) -> (model -> View model action) -> Component parent model action
- (+>) :: Eq child => MisoString -> Component model child action -> View model a
- mount_ :: Eq child => Component model child a -> View model action
- withSink :: (Sink action -> IO ()) -> Effect parent model action
- type Sink action = action -> IO ()
- mail :: ToJSON message => ComponentId -> message -> IO ()
- checkMail :: FromJSON value => (value -> action) -> (MisoString -> action) -> Value -> Maybe action
- parent :: (parent -> action) -> action -> Effect parent model action
- mailParent :: ToJSON message => message -> Effect parent model action
- broadcast :: (Eq model, ToJSON message) => message -> Effect parent model action
- startSub :: ToMisoString subKey => subKey -> Sub action -> Effect parent model action
- stopSub :: ToMisoString subKey => subKey -> Effect parent model action
- type Sub action = Sink action -> IO ()
- issue :: action -> Effect parent model action
- batch :: [IO action] -> Effect parent model action
- io :: IO action -> Effect parent model action
- io_ :: IO () -> Effect parent model action
- sync :: IO action -> Effect parent model action
- sync_ :: IO () -> Effect parent model action
- for :: Foldable f => IO (f action) -> Effect parent model action
- withJS :: IO a -> IO a
- module Miso.Binding
- module Miso.DSL
- module Miso.Effect
- module Miso.Event
- module Miso.Fetch
- module Miso.PubSub
- module Miso.Property
- module Miso.Reload
- module Miso.Subscription
- module Miso.Storage
- module Miso.Types
- module Miso.Util
- module Miso.FFI
- module Miso.State
API
Miso
Arguments
| :: Eq model | |
| => Events | Globally delegated Events |
| -> (URI -> App model action) | The Component application, with the current URI as an argument |
| -> IO () |
Runs an miso application.
Assumes the pre-rendered DOM is already present. Always mounts to <body>. Copies page into the virtual DOM.
main ::IO() main =misodefaultEvents(\uri -> app uri))
Arguments
| :: Eq model | |
| => Events | Globally delegated |
| -> App model action |
|
| -> IO () |
Like miso, except discards the URI argument.
Use this function if you'd like to prerender, but not use navigation.
main ::IO() main =prerenderdefaultEventsapp
Arguments
| :: Eq model | |
| => Events | Globally delegated |
| -> (URI -> App model action) |
|
| -> IO () |
Alias for miso.
App
Arguments
| :: Eq model | |
| => Events | Globally delegated |
| -> App model action |
|
| -> IO () |
Like miso, except it does not perform page hydration.
This function draws your application on an empty body
You will most likely want to use this function for your application unless you are using prerendering.
main ::IO() main =startAppdefaultEventsapp
Arguments
| :: Eq model | |
| => Events | Globally delegated |
| -> MisoString | Name of the JS object that contains the drawing context |
| -> App model action |
|
| -> IO () |
Runs a miso application, but with a custom rendering engine.
The MisoString specified here is the variable name of a globally-scoped
JS object that implements the context interface per ts/miso/context/dom.ts
This is necessary for native support.
It is expected to be run on an empty <body>
main :: IO () main =renderAppdefaultEvents"my-context" app
Component
data Component parent model action Source #
Application entry point
Constructors
| Component | |
Fields
| |
Arguments
| :: model | model |
| -> (action -> Effect parent model action) | update |
| -> (model -> View model action) | view |
| -> Component parent model action |
Smart constructor for Component with sane defaults.
Component mounting combinator.
Note: only use this if you're certain you won't be diffing two Component
against each other. Otherwise, you will need a key to distinguish between
the two Component, to ensure unmounting and mounting occurs.
mount_ $ component model noop $ \m ->
div_ [ id_ "foo" ] [ text (ms m) ]
Since: 1.9.0.0
Sink
Arguments
| :: (Sink action -> IO ()) | Callback function that provides access to the underlying |
| -> Effect parent model action |
withSink allows users to write to the global event queue. This is useful for introducing IO into the system.
A synonym for tell, specialized to Effect.
A use-case is scheduling an IO computation which creates a 3rd-party JS
widget which has an associated callback. The callback can then call the sink
to turn events into actions.
'update' FetchJSON = 'withSink' $ \sink -> getJSON (sink . ReceivedJSON) (sink . HandleError)
Since: 1.9.0.0
type Sink action = action -> IO () Source #
Function to write to the global event queue for processing by the scheduler.
Arguments
| :: ToJSON message | |
| => ComponentId |
|
| -> message | The message to send |
| -> IO () |
Send any ToJSON message => message to a Component mailbox, by ComponentId
io_ $ mail componentId ("test message" :: MisoString) :: Effect parent model action
Since: 1.9.0.0
Arguments
| :: FromJSON value | |
| => (value -> action) | Successful callback |
| -> (MisoString -> action) | Errorful callback |
| -> Value | The message received to parse. |
| -> Maybe action |
Helper function for processing Mail from mail.
data Action
= ParsedMail Message
| ErrorMail MisoString
main :: IO ()
main = app { mailbox = checkMail ParsedMail ErrorMail }
Since: 1.9.0.0
Arguments
| :: (parent -> action) | Successful callback |
| -> action | Errorful callback |
| -> Effect parent model action |
Send any ToJSON message => message to the parent's Component mailbox
mailParent ("test message" :: MisoString) :: Effect parent model action
Since: 1.9.0.0
Subscriptions
Arguments
| :: ToMisoString subKey | |
| => subKey | The key used to track the |
| -> Sub action | The |
| -> Effect parent model action |
Starts a named Sub dynamically, during the life of a Component.
The Sub can be stopped by calling Ord subKey => stop subKey from the update function.
All Sub started will be stopped if a Component is unmounted.
data SubType = LoggerSub | TimerSub deriving (Eq, Ord) update Action = startSub LoggerSub $ \sink -> forever (threadDelay (secs 1) >> consoleLog "test")
Since: 1.9.0.0
Arguments
| :: ToMisoString subKey | |
| => subKey | The key used to stop the |
| -> Effect parent model action |
type Sub action = Sink action -> IO () Source #
Type synonym for constructing subscriptions.
For example usage see Miso.Subscription
The Sink function is used to write to the global event queue.
Effect
Arguments
| :: action |
|
| -> Effect parent model action |
Issue a new action to be processed by update.
data Action = HelloWorld type Model = Int 'update' :: Action -> 'Effect' parent Model Action 'update' = \\case Click -> 'issue' HelloWorld
Since: 1.9.0.0
Like io but doesn't cause an action to be dispatched to
the update function.
This is handy for scheduling IO computations where you don't care
about their results or when they complete.
Note: The result of IO a is discarded.
Since: 1.9.0.0
Like sync, except discards the result.
Since: 1.9.0.0
JS file embedding
Load miso's javascript.
You don't need to use this function if you're compiling w/ WASM and using miso or startApp.
It's already invoked for you. This is a no-op w/ the JS backend.
If you need access to FFI to call functions from `miso.js`, but you're not
using startApp or miso, you'll need to call this function (w/ WASM only).
Bindings
Primitives for synchronizing parent and child models.
module Miso.Binding
DSL
A JavaScript DSL for easy FFI interoperability
module Miso.DSL
Effect
module Miso.Effect
Event
Functions for specifying component lifecycle events and event handlers.
module Miso.Event
Fetch
Interface to the Fetch API for making HTTP requests.
module Miso.Fetch
PubSub
Publish / Subscribe primitives for communication between components.
module Miso.PubSub
Property
Construct custom properties on DOM elements.
module Miso.Property
Reload
Support for clearing the page during live-reloading w/ WASM browser mode.
module Miso.Reload
Subscriptions
Subscriptions for external events (mouse, keyboard, window, history, etc.).
module Miso.Subscription
Storage
Web Storage API (Local and Session storage) interface.
module Miso.Storage
Types
Core types for Miso applications.
module Miso.Types
Util
Utility functions for views, parsing, and general purpose combinators.
module Miso.Util
FFI
Foreign Function Interface (FFI) utilities for interacting with JavaScript.
module Miso.FFI
State management
State management for Miso applications.
module Miso.State