Lightweight frontend library for GHC with JavaScript backend with focus on minimalism and simplicity
To follow the instructions, you would need to have nix
installed in your system. Alternatively,
you can choose to install manually GHC with JavaScript
Backend,
and cabal
(comes with every GHC installation)
How to build library and the examples:
# Clone the repository
git clone https://github.com/lagunoff/htmlt.git
cd htmlt
# Enter the nix-shell
nix-shell
# Build examples with cabal
cabal --with-ghc=javascript-unknown-ghcjs-ghc --with-ghc-pkg=javascript-unknown-ghcjs-ghc-pkg build -f examples
Once cabal build
is successful, you can find js executables in
./dist-newstyle/build/javascript-ghcjs/ghc-9.7.20230527/htmlt-0.1.0.0/x
and run them by opening index.html
in browser
-- Example featuring <input> element and two buttons. The input value
-- is synchronized with 'DynRef's state and can be modified by either entering a
-- number into the input or by clicking one of the two buttons
app :: Html ()
app = do
-- First create a 'DynRef
counterRef <- newRef @Int 0
div_ do
input_ [type_ "number"] do
-- Show the value inside <input>
dynProp "value" $ JSS.pack . show <$> fromRef counterRef
-- Parse and update the value on each InputEvent
on "input" $ decodeEvent intDecoder $ writeRef counterRef
br_
-- Decrease the value on each click
button_ do
on_ "click" $ modifyRef counterRef pred
text "-"
-- Increase the value on each click
button_ do
on_ "click" $ modifyRef counterRef succ
text "+"
where
intDecoder =
valueDecoder >=> MaybeT . pure . readMaybe . JSS.unpack
main :: IO ()
main =
void $ attachToBody app
Expand to see simplified definitions
-- Constructing DOM
el :: JSString -> Html a -> Html a
elns :: JSString -> JSString -> Html a -> Html a
text :: JSString -> Html ()
dynText :: Dynamic JSString -> Html ()
-- Applying attributes and properties
prop :: JSString -> v -> Html ()
dynProp :: JSString -> Dynamic v -> Html ()
attr :: JSString -> JSString -> Html ()
dynAttr :: JSString -> JSString -> Html ()
toggleClass :: JSString -> Dynamic Bool -> Html ()
toggleAttr :: JSString -> Dynamic Bool -> Html ()
dynStyle :: JSString -> Dynamic JSString -> Html ()
dynStyles :: Dynamic JSString -> Html ()
dynValue :: Dynamic JSString -> Html ()
dynClass :: Dynamic JSString -> Html ()
dynChecked :: Dynamic Bool -> Html ()
dynDisabled :: Dynamic Bool -> Html ()
-- Handling DOM events
on :: EventName -> (DOMEvent -> Step ()) -> Html ()
on_ :: EventName -> Step () -> Html ()
onOptions :: EventName -> ListenerOpts -> (DOMEvent -> Step ()) -> Html ()
onGlobalEvent :: ListenerOpts -> DOMNode -> EventName -> (DOMEvent -> Step ()) -> Html ()
-- Decoding data from DOM Events
mouseDeltaDecoder :: JSVal -> MaybeT m MouseDelta
clientXYDecoder :: JSVal -> MaybeT m (Point Int)
offsetXYDecoder :: JSVal -> MaybeT m (Point Int)
pageXYDecoder :: JSVal -> MaybeT m (Point Int)
keyModifiersDecoder :: JSVal -> MaybeT m KeyModifiers
keyCodeDecoder :: JSVal -> MaybeT m Int
keyboardEventDecoder :: JSVal -> MaybeT m KeyboardEvent
valueDecoder :: JSVal -> MaybeT m JSString
checkedDecoder :: JSVal -> MaybeT m Bool
-- DOM extras, useful helpers
unsafeHtml :: MonadIO m => JSString -> HtmlT m ()
portal :: Monad m => DOMElement -> HtmlT m a -> HtmlT m a
installFinalizer :: MonadReactive m => IO () -> m ()
-- Dynamic collections
simpleList :: Dynamic [a] -> (Int -> DynRef a -> Html ()) -> Html ()
-- Arbitrary dynamic content
dyn :: Dynamic (Html ()) -> Html ()
-- Contructing Events
newEvent :: MonadReactive m => m (Event a, Trigger a)
fmap :: (a -> b) -> Event a -> Event a
never :: Event a
updates :: Dynamic a -> Event a
-- Constructing Dynamics
constDyn :: a -> Dynamic a
fromRef :: DynRef a -> Dynamic a
fmap :: (a -> b) -> Dynamic a -> Dynamic b
(<*>) :: Dynamic (a -> b) -> Dynamic a -> Dynamic b
mapDyn :: MonadReactive m => Dynamic a -> (a -> b)-> m (Dynamic b)
mapDyn2 :: MonadReactive m => Dynamic a -> Dynamic b -> (a -> b -> c) -> m (Dynamic c)
mapDyn3 :: MonadReactive m => Dynamic a -> Dynamic b -> Dynamic c -> (a -> b -> c -> d) -> m (Dynamic d)
holdUniqDyn :: Eq a => Dynamic a -> Dynamic a
holdUniqDynBy :: (a -> a -> Bool) -> Dynamic a -> Dynamic a
-- Constructing DynRefs
newRef :: MonadReactive m => a -> m (DynRef a)
lensMap :: Lens' s a -> DynRef s -> DynRef a
-- Read and write DynRefs, Dynamics
readDyn :: MonadIO m => Dynamic a -> m a
readRef :: MonadIO m => DynRef a -> m a
writeRef :: DynRef a -> a -> Step ()
modifyRef :: DynRef a -> (a -> a) -> Step ()
atomicModifyRef :: DynRef a -> (a -> (a, r)) -> Step r
-- Starting and shutting down the application
atatchOptions :: StartOpts -> Html a -> IO (a, RunningApp)
attachTo :: DOMElement -> Html a -> IO (a, RunningApp)
attachToBody :: Html a -> IO (a, RunningApp)
detach :: RunningApp -> IO ()
Counter | 5.6M all.js, 3.7M all.min.js | source | open | open minified |
TodoMVC | 3.1M all.js, 773K all.min.js | source | open | open minified |
Simple Routing | 11M all.js, 7.6M all.min.js | source | open | open minified |
For comparison, here are the sizes of all.js files build with GHCJS 8.6 — 1.5M htmlt-counter, 1.4M htmlt-todomvc, 3.3M htmlt-simple-routing
- Migrate to GHC with JavaScript backend
- More examples and documentation
- Similar library for ReactNative
The legacy version for GHCJS 8.6 and GHCJS 8.10 can still be found in the ghcjs branch