Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork148
🍜 A tasty Haskell web framework
License
dmjio/miso
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
AtastyHaskell web and mobile framework 🍜
Miso is a small, production-ready, component-oriented,isomorphicHaskell front-end framework for quickly building highly interactive single-page web andmobile applications. It features a virtual-dom, recursive diffing / patching algorithm, attribute and property normalization, event delegation, event batching, SVG, Server-sent events (SSE),Websockets, type-safeservant-style routing and an extensible Subscription-based subsystem. Inspired byElm andReact.Miso is pure by default, but side effects can be introduced into the system via theEffect
data type.Miso makes heavy use of theGHC Javascript FFI and therefore has minimal dependencies.Miso can be considered a shallowembedded domain-specific language for modern web programming.
Miso supports compilation to bothJavaScript andWebAssembly usingGHC. For hot-reload,miso
uses thejsaddle library. When used withghcid andghciwatch this enables a rapid development workflow.
Tip
React-style Components are now added tomiso
as of version1.9
. This has not yet been released, we recommend developing againstmaster
if you'd like to use latest features.
- History
- Quick Start
- Setup
- Hot Reload
- Compilation
- WebAssembly
- JavaScript
- Haddocks
- Architecture
- Internals
- Examples
- Building examples
- HTTP
- Coverage
- Isomorphic
- Native
- Benchmarks
- Nix
- Community
- Maintainers
- Contributing
- Contributors
- Partnerships
- Backers
- Organizations
- License
miso is a play on the wordsmicro andisomorphic.
miso began in 2016 as research in:
- Expressing theElm architecture inGHCJS as anembedded domain-specific language.
- Implementing modern JavaScript frontend techniques (e.g.reconciliation,isomorphic)
Miso aims tobridge the gap between modern JavaScript techniques (as found inReact,Vue.js, etc.) and functional programming inHaskell.
It has since grown to encompass more techniques from the JavaScript community likeComponents andRenderers. It alsonow supports native development to iOS / Android viaLynxJS and additional backends likeWeb Assembly.
To start developing applications withmiso
you will need to acquireGHC andcabal. This can be done viaGHCup orNix.
To develop and build your firstmiso
application you will need 3 files:
cabal.project
app.cabal
Main.hs
packages:.source-repository-packagetype:gitlocation:https://github.com/dmjio/misobranch:master
We recommend using at leastcabal-version: 2.2
, this will give you thecommon sections feature which we will use later to allow multiple compilers to build our project (so we can targetWASM
andJS
backends)
cabal-version:2.2name:appversion:0.1.0.0synopsis:Sample miso appcategory:Webcommon wasmif arch(wasm32)ghc-options:-no-hs-main-optl-mexec-model=reactor"-optl-Wl,--export=hs_start"cpp-options:-DWASMexecutable appimport:wasmmain-is:Main.hsbuild-depends:base, misodefault-language:Haskell2010
This file contains a simplemiso
counter application.
----------------------------------------------------------------------------{-#LANGUAGE OverloadedStrings #-}{-#LANGUAGE RecordWildCards #-}{-#LANGUAGE LambdaCase #-}{-#LANGUAGE CPP #-}----------------------------------------------------------------------------moduleMainwhere----------------------------------------------------------------------------importMisoimportMiso.StringimportMiso.Lens------------------------------------------------------------------------------| Component model statenewtypeModel=Model{_counter::Int}deriving (Show,Eq)----------------------------------------------------------------------------counter::LensModelIntcounter= lens _counter$\record field-> record { _counter= field }------------------------------------------------------------------------------| Sum type for Component eventsdataAction=AddOne |SubtractOne |SayHelloWorldderiving (Show,Eq)------------------------------------------------------------------------------| Entry point for a miso applicationmain::IO()main= run (startComponent app)------------------------------------------------------------------------------| WASM export, required when compiling w/ the WASM backend.#ifdef WASMforeignexport javascript"hs_start" main::IO()#endif------------------------------------------------------------------------------| `component` takes as arguments the initial model, update function, view functionapp::ComponentModelActionapp= component emptyModel updateModel viewModel------------------------------------------------------------------------------| Empty application stateemptyModel::ModelemptyModel=Model0------------------------------------------------------------------------------| Updates model, optionally introduces side effectsupdateModel::Action->EffectModelActionupdateModel=\caseAddOne-> counter+=1SubtractOne-> counter-=1SayHelloWorld-> io_$do consoleLog"Hello World" alert"Hello World"------------------------------------------------------------------------------| Constructs a virtual DOM from a modelviewModel::Model->ViewActionviewModel x= div_[] [ button_ [ onClickAddOne ] [ text"+" ] , text$ ms (x^. counter) , button_ [ onClickSubtractOne ] [ text"-" ] , button_ [ onClickSayHelloWorld ] [ text"Alert Hello World!" ] ]----------------------------------------------------------------------------
Now that your project files are populated, development can begin.
WithGHC
andcabal
on$PATH
, callcabal repl
$ cabal repl
You should see the following output in your terminal.
[1 of 2] Compiling Main ( Main.hs, interpreted )Ok, one module loaded.ghci>
Now call themain
function in theGHCi
REPL.
ghci> mainRunning on port 8008...<a href="http://localhost:8008">run</a>ghci>
Note
The code running in this example is not compiled to JavaScript or WebAssembly, rather it is running the client side application on the server. It works by sending commands to a small javascript interpreter over a websocket to render elements on the page. This is provided by thejsaddle library.
If you visithttp://localhost:8008, the application will be live. You can now editMain.hs
, call:r
andmain
inGHCi
, and the application will update on the screen.
Note
Instead of typing:r
andmain
manually inside ofGHCi
on every file change, you can useghcid orghciwatch tools to do it automatically.
Tip
For users accustomed to a react.js worfklow, we highly recommend using eitherghcid
orghciwatch
.
Below is an example of usage withghcid
$ ghcid -c'cabal repl app' -T=Main.main
This screenshot shows the hot-reload functionality in action. This is usingghcid
,jsaddle
andmiso
.
When done developing, we can compile to Web Assembly or JavaScript for distribution. This is done by acquiring aGHC
that supports WebAssembly or JavaScript. We recommend acquiring these backends usingGHCUp
orNix
.
Tip
The Haskellmiso
team currently recommends using the WASM backend as the default backend for compilation.
UsingGHCup you should be able to acquire theGHC
WASM
compiler.
For instructions on how to add a third-party channel withGHCup, please see their officialREADME.md
$ nix shell'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org'
Note
This will putwasm32-wasi-cabal
in your$PATH
, along withwasm32-wasi-ghc
. Since the WASM backend is relatively new, the ecosystem is not entirely patched to support it. Therefore, we will need to use patched packages from time to time.
Tip
Instead of using anix shell
, it's possible to install the GHC WASM Flake into your environment so it will always be present on$PATH
$ nix profile install'gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org'
Update yourcabal.project
to the following
cabal.project
packages:.with-compiler:wasm32-wasi-ghcwith-hc-pkg:wasm32-wasi-ghc-pkgsource-repository-packagetype:gitlocation:https://github.com/dmjio/misobranch:masterif arch(wasm32)-- Required for TemplateHaskell. When using wasm32-wasi-cabal from-- ghc-wasm-meta, this is superseded by the global cabal.config.shared:True
Callwasm32-wasi-cabal build --allow-newer
and aWASM
payload should be created indist-newstyle/
directory.
$ wasm32-wasi-cabal update$ wasm32-wasi-cabal build --allow-newer
Configuration is affected by the following files:- cabal.projectResolving dependencies...Build profile: -w ghc-9.12.2.20250327 -O1In order, the following will be built (use -vfor more details): - app-0.1.0.0 (exe:app) (configuration changed)Configuring executable'app'for app-0.1.0.0...Preprocessing executable'app'for app-0.1.0.0...Building executable'app'for app-0.1.0.0...[1 of 1] Compiling Main ( Main.hs, dist-newstyle/build/wasm32-wasi/ghc-9.12.2.20250327/app-0.1.0.0/x/app/build/app/app-tmp/Main.o )[2 of 2] Linking dist-newstyle/build/wasm32-wasi/ghc-9.12.2.20250327/app-0.1.0.0/x/app/build/app/app.wasm
You have now successfully compiled a Haskellmiso
application to WebAssembly 🔥
But, we're not done yet. In order to view this in the browser there are still a few more steps. We need to add some additional files that emulate theWASI interface in the browser (A browser WASI shim).
Note
The GHC WASM backend can execute any Haskell program in a WASI-compliant runtime (e.g.wasmtime)See theofficial documentation for more information.
To start, we recommend creating anapp.wasmexe
folder to store the additional artifacts required.
Tip
We recommend using an up-to-datenode
version (currently tested withv24.2.0
) to ensurepost-link.mjs
works properly.
# Creates the directory for hosting$ mkdir -v app.wasmexemkdir: created directory'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.wasmexeConfiguration is affected by the following files:- cabal.project'/home/dmjio/Desktop/miso/sample-app/dist-newstyle/build/wasm32-wasi/ghc-9.12.2.20250327/app-0.1.0.0/x/app/build/app/app.wasm' ->'app.wasmexe'
Note
Along with the aboveghc_wasm_jsffi.js
andapp.wasm
artifacts, we also need to include anindex.html
and anindex.js
for loading the WASM payload into the browser.
index.html
<!DOCTYPE html><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1"><title>Sample miso WASM counter app</title></head><body><scriptsrc="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";importghc_wasm_jsffifrom"./ghc_wasm_jsffi.js";constargs=[];constenv=["GHCRTS=-H64m"];constfds=[newOpenFile(newFile([])),// stdinConsoleStdout.lineBuffered((msg)=>console.log(`[WASI stdout] ''${msg}`)),ConsoleStdout.lineBuffered((msg)=>console.warn(`[WASI stderr] ''${msg}`)),];constoptions={debug:false};constwasi=newWASI(args,env,fds,options);constinstance_exports={};const{ instance}=awaitWebAssembly.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);awaitinstance.exports.hs_start(globalThis.example);
Theapp.wasmexe
folder will now look like:
❯ ls app.wasmexe app.wasm ghc_wasm_jsffi.js index.html index.js
Now you can host and view theapp.wasm
payload in a web browser.
$ http-server app.wasmexe
Tip
You can inspect the WASM payload in theSources
tab of your browser by right-clicking and then clickingInspect
.
UsingGHCup you should be able to acquire the latest GHC JS-backend compiler.
Tip
ForNix users it is possible to acquire the latest JS backend (thatmiso
uses) via Nix.Usecachix to ensure you're not building dependencies unnecessarilycachix use haskell-miso-cachix
nix-shell -p pkgs.pkgsCross.ghcjs.haskell.packages.ghc9122.ghc -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/65f179f903e8bbeff3215cd613bdc570940c0eab.tar.gz
Note
This will putjavascript-unknown-ghcjs-ghc
in your$PATH
, along withjavascript-unknown-ghcjs-ghc-pkg
. You might also need to specify in yourcabal.project
file that you are using the JS backend.
Tip
Alternatively, if you'd like to install the compiler into your global environment (so you don't need to develop inside abash
shell) you can use the following command.
nix-env -iA pkgs.pkgsCross.ghcjs.haskell.packages.ghc9122.ghc -f https://github.com/NixOS/nixpkgs/archive/65f179f903e8bbeff3215cd613bdc570940c0eab.tar.gz
cabal.project
packages:.source-repository-packagetype:gitlocation:https://github.com/dmjio/misobranch:masterwith-compiler:javascript-unknown-ghcjs-ghcwith-hc-pkg:javascript-unknown-ghcjs-ghc-pkg
Note
cabal
will use theghc
specified above inwith-compiler
$ cabal update&& cabal build --allow-newer
Configuring executable'app'for app-0.1.0.0...Preprocessing executable'app'for app-0.1.0.0...Building executable'app'for app-0.1.0.0...[1 of 1] Compiling Main ( Main.hs, dist-newstyle/build/javascript-ghcjs/ghc-9.12.2/app-0.1.0.0/x/app/build/app/app-tmp/Main.o )[2 of 2] Linking dist-newstyle/build/javascript-ghcjs/ghc-9.12.2/app-0.1.0.0/x/app/build/app/app.jsexe
Tip
To view the JavaScript in your browser, you can usecabal list-bin
andhttp-server
$ http-server$(cabal list-bin app --allow-newer).jsexeConfiguration is affected by the following files:- cabal.projectStarting up http-server, serving /home/dmjio/Desktop/miso/sample-app/dist-newstyle/build/javascript-ghcjs/ghc-9.12.2/app-0.1.0.0/x/app/build/app/app.jsexehttp-server version: 14.1.1http-server settings:CORS: disabledCache: 3600 secondsConnection Timeout: 120 secondsDirectory Listings: visibleAutoIndex: visibleServe GZIP Files:falseServe Brotli Files:falseDefault File Extension: noneAvailable on: http://127.0.0.1:8080 http://192.168.1.114:8080Hit CTRL-C to stop the server
OfficalHaskell documentation of theMiso web framework.
Platform | URL |
---|---|
GHCJS | Link |
GHC | Link |
For constructing client and server applications, we recommend using onecabal
file with two executable sections, where thebuildable
attribute set is contingent on the compiler. An example of this layout ishere.
Tip
For more information on how to usenix
with aclient
/server
setup, see thenix scripts forhttps://haskell-miso.org.
For some details of the internals and general overview of howmiso
works, see theInternals.
For real-world examples of Haskellmiso
applications, see below.
Name | Description | Source Link | Live Demo Link | Author |
---|---|---|---|---|
TodoMVC | A classic TodoMVC implementation | Source | Demo | @dmjio |
2048 | A clone of the 2048 game | Source | Demo | @ptigwe |
Flatris | A Tetris-like game | Source | Demo | @ptigwe |
Plane | A flappy-birds-like game | Source | Demo | @Lermex |
Snake | The classic Snake game | Source | Demo | @lbonn |
SVG | An example showcasing SVG rendering | Source | Demo | @dmjio |
Fetch | An example demonstrating AJAX requests | Source | Demo | @dmjio |
File Reader | A FileReader API example | Source | Demo | @dmjio |
Three.js | A 3D rendering example using Three.JS | Source | Demo | @juliendehos |
Mario | A Super Mario physics example | Source | Demo | @dmjio |
WebSocket | A simple WebSocket example | Source | Demo | @dmjio |
Router | A client-side routing example | Source | Demo | @dmjio |
Canvas 2D | A 2D Canvas rendering example | Source | Demo | @dmjio |
Space Invaders | A Space-Invaders-like game | Source | Demo | @juliendehos |
Audio | Audio examples | Source | Demo | @juliendehos |
Video | Video examples | Source | Demo | @juliendehos |
The easiest way to build the examples is with thenix
package manager
Tip
Usecachix to ensure you're not building dependencies unnecessarilycachix use haskell-miso-cachix
$ git clone https://github.com/dmjio/miso$ cd miso$ nix-build -A miso-examples
This will compile all the examples to JavaScript into a folder namedresult
.
➜ tree -d ./result/bin./result/bin|-- canvas2d.jsexe|-- file-reader.jsexe|-- mario.jsexe| `-- imgs|-- mathml.jsexe|-- router.jsexe|-- simple.jsexe|-- svg.jsexe|-- tests.jsexe|-- threejs.jsexe|-- todo-mvc.jsexe|-- websocket.jsexe`-- fetch.jsexe
Note
To see examples, we recommend hosting them with a web server (we usehttp-server)
cd result/bin/todo-mvc.jsexe && http-severServing HTTP on 0.0.0.0 port 8000 ...
If you want to interact with an HTTP API, we recommend one of the following approaches:
For a simple JSON-based API, you can use Miso's
fetch
function.In more complex cases, you can define aServant API and automatically obtain client functions via
servant-client-js
(or any otherservant-client-core
-based backend).The Fetch example (Source,Demo) demonstrates the necessary ingredients. Make sure to add the following to your
cabal.project
:source-repository-packagetype: gitlocation: https://github.com/amesgen/servant-client-jstag: 2853fb4f26175f51ae7b9aaf0ec683c45070d06e
The core engine ofmiso
is thediff function. It is responsible for all DOM manipulation that occurs in a miso application and has100% code coverage. Tests and coverage made possible usingbun.
Note
To run the tests and build the coverage report ensurebun is installed.
$ curl -fsSL https://bun.sh/install| bash
or
$ nix-env -iA bun -f'<nixpkgs>'
and
$ bun install&& bun runtest
--------------------|---------|---------|-------------------File| % Funcs| % Lines| Uncovered Line#s--------------------|---------|---------|-------------------All files| 92.37| 85.48| ts/happydom.ts| 100.00| 100.00| ts/miso/dom.ts| 100.00| 100.00| ts/miso/event.ts| 90.91| 81.62| ts/miso/hydrate.ts| 80.00| 91.24| ts/miso/smart.ts| 100.00| 100.00| ts/miso/util.ts| 83.33| 40.00|--------------------|---------|---------|------------------- 84 pass 0 fail
Isomorphic javascript is a technique for increased SEO, code-sharing and perceived page load times. It works in two parts. First, the server sends a pre-rendered HTML body to the client's browser. Second, after the client javascript application loads, the pointers of the pre-rendered DOM are copied into the virtual DOM (a process known ashydration), and the application proceeds as normal. All subsequent page navigation is handled locally by the client, while avoiding full-page postbacks.
Note
Themiso function is used to facilitate the pointer-copying behavior client-side.
Miso supports the creation of iOS and Android applications viaLynxJS. See themiso-lynx repository for more information.
According to benchmarks,miso
is among the fastest functional programming web frameworks, second only toElm.
Nix
is a powerful option for building web applications withmiso
since it encompasses development workflow, configuration management, and deployment. The source code forhaskell-miso.org
is an example of this.
Tip
If unfamiliar withnix
, we recommend@Gabriella439's"Nix and Haskell in production" guide.
By defaultmiso
uses a known-to-work, pinned version ofnixpkgs
known aspkgs
.
Note
miso
also maintains a legacy version of nixpkgs known aslegacyPkgs
so we can use tools likenixops
for deployment and to buildmiso
with the originalGHCJS 8.6
backend.
nix
users on a Linux or OSX distros can take advantage of abinary cache for faster builds. To use the binary cache follow the instructions oncachix.
Tip
We highly recommend nix users consume thecachix cache.cachix use haskell-miso-cachix
.
$ cachix use haskell-miso-cachix
Feel free to dive in!Open an issue or a submitPull Request.
SeeCONTRIBUTING for more info.
Note
This project exists thanks to all the people whocontribute
If you'd like to support this project financially, be it through requesting feature development, or a corporate partnership, please drop us a line and we will be in touch shortly.
Become afinancial contributor and help us sustain our project and community. We are very grateful and thankful for our individual sponsors.
- Moses Tschanz
- @MaxGabriel
- @DigitalOcean
- @maybetonyfu
- @jhrcek
- etc.
Support this project with your organization. Your logo will show up here with a link to your website. We are also very grateful and thankful for our corporate sponsors.
BSD3 © dmjio
About
🍜 A tasty Haskell web framework
Topics
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.