{"id":26025415,"url":"https://github.com/lagunoff/htmlt","last_synced_at":"2025-03-06T13:39:37.910Z","repository":{"id":39656325,"uuid":"209617140","full_name":"lagunoff/htmlt","owner":"lagunoff","description":"Lightweight frontend library for GHC with JavaScript Backend ","archived":false,"fork":false,"pushed_at":"2024-04-13T23:53:21.000Z","size":3708,"stargazers_count":18,"open_issues_count":4,"forks_count":0,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-04-14T06:55:36.213Z","etag":null,"topics":["ghc","ghcjs","gui","haskell","react","virtual-dom"],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lagunoff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-09-19T17:58:58.000Z","updated_at":"2024-04-15T22:47:26.833Z","dependencies_parsed_at":"2023-02-19T05:30:29.538Z","dependency_job_id":"731e62cb-763b-4543-83fe-fa9dece878e6","html_url":"https://github.com/lagunoff/htmlt","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lagunoff%2Fhtmlt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lagunoff%2Fhtmlt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lagunoff%2Fhtmlt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lagunoff%2Fhtmlt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lagunoff","download_url":"https://codeload.github.com/lagunoff/htmlt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242220400,"owners_count":20091773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ghc","ghcjs","gui","haskell","react","virtual-dom"],"created_at":"2025-03-06T13:39:37.202Z","updated_at":"2025-03-06T13:39:37.903Z","avatar_url":"https://github.com/lagunoff.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\nLightweight frontend library for GHC with JavaScript backend with\nfocus on minimalism and simplicity\n\n## Getting started\n\nTo follow the instructions, you would need to have [nix](https://nixos.org/download.html)\ninstalled in your system. Alternatively,\nyou can choose to install manually [GHC with JavaScript\nBackend](https://engineering.iog.io/2023-01-24-javascript-browser-tutorial/),\nand `cabal` (comes with every [GHC](https://www.haskell.org/ghc/) installation)\n\nHow to build library and the examples:\n```sh\n# Clone the repository\ngit clone https://github.com/lagunoff/htmlt.git\ncd htmlt\n# Enter the nix-shell\nnix-shell\n# Build examples with cabal\ncabal --with-ghc=javascript-unknown-ghcjs-ghc --with-ghc-pkg=javascript-unknown-ghcjs-ghc-pkg build -f examples\n```\nOnce `cabal build` is successful, you can find js executables in\n`./dist-newstyle/build/javascript-ghcjs/ghc-9.7.20230527/htmlt-0.1.0.0/x` and run them by opening `index.html` in browser\n\n### Minimal example\n\n```haskell\n-- Example featuring \u003cinput\u003e element and two buttons. The input value\n-- is synchronized with 'DynRef's state and can be modified by either entering a\n-- number into the input or by clicking one of the two buttons\napp :: Html ()\napp = do\n  -- First create a 'DynRef\n  counterRef \u003c- newRef @Int 0\n  div_ do\n    input_ [type_ \"number\"] do\n      -- Show the value inside \u003cinput\u003e\n      dynProp \"value\" $ JSS.pack . show \u003c$\u003e fromRef counterRef\n      -- Parse and update the value on each InputEvent\n      on \"input\" $ decodeEvent intDecoder $ writeRef counterRef\n    br_\n    -- Decrease the value on each click\n    button_ do\n      on_ \"click\" $ modifyRef counterRef pred\n      text \"-\"\n    -- Increase the value on each click\n    button_ do\n      on_ \"click\" $ modifyRef counterRef succ\n      text \"+\"\n  where\n    intDecoder =\n      valueDecoder \u003e=\u003e MaybeT . pure . readMaybe . JSS.unpack\n\nmain :: IO ()\nmain =\n  void $ attachToBody app\n\n```\n[Open the demo](https://lagunoff.github.io/htmlt/js-backend/htmlt-counter.jsexe/)\n\n## Quick API summary\n\u003cdetails\u003e\n  \u003csummary\u003eExpand to see simplified definitions\u003c/summary\u003e\n  \n```hs\n-- Constructing DOM\nel :: JSString -\u003e Html a -\u003e Html a\nelns :: JSString -\u003e JSString -\u003e Html a -\u003e Html a\ntext :: JSString -\u003e Html ()\ndynText :: Dynamic JSString -\u003e Html ()\n\n-- Applying attributes and properties\nprop :: JSString -\u003e v -\u003e Html ()\ndynProp :: JSString -\u003e Dynamic v -\u003e Html ()\nattr :: JSString -\u003e JSString -\u003e Html ()\ndynAttr :: JSString -\u003e JSString -\u003e Html ()\ntoggleClass :: JSString -\u003e Dynamic Bool -\u003e Html ()\ntoggleAttr :: JSString -\u003e Dynamic Bool -\u003e Html ()\ndynStyle :: JSString -\u003e Dynamic JSString -\u003e Html ()\ndynStyles :: Dynamic JSString -\u003e Html ()\ndynValue :: Dynamic JSString -\u003e Html ()\ndynClass :: Dynamic JSString -\u003e Html ()\ndynChecked :: Dynamic Bool -\u003e Html ()\ndynDisabled :: Dynamic Bool -\u003e Html ()\n\n-- Handling DOM events\non :: EventName -\u003e (DOMEvent -\u003e Step ()) -\u003e Html ()\non_ :: EventName -\u003e Step () -\u003e Html ()\nonOptions :: EventName -\u003e ListenerOpts -\u003e (DOMEvent -\u003e Step ()) -\u003e Html ()\nonGlobalEvent :: ListenerOpts -\u003e DOMNode -\u003e EventName -\u003e (DOMEvent -\u003e Step ()) -\u003e Html ()\n\n-- Decoding data from DOM Events\nmouseDeltaDecoder :: JSVal -\u003e MaybeT m MouseDelta\nclientXYDecoder :: JSVal -\u003e MaybeT m (Point Int)\noffsetXYDecoder :: JSVal -\u003e MaybeT m (Point Int)\npageXYDecoder :: JSVal -\u003e MaybeT m (Point Int)\nkeyModifiersDecoder :: JSVal -\u003e MaybeT m KeyModifiers\nkeyCodeDecoder :: JSVal -\u003e MaybeT m Int\nkeyboardEventDecoder :: JSVal -\u003e MaybeT m KeyboardEvent\nvalueDecoder :: JSVal -\u003e MaybeT m JSString\ncheckedDecoder :: JSVal -\u003e MaybeT m Bool\n\n-- DOM extras, useful helpers\nunsafeHtml :: MonadIO m =\u003e JSString -\u003e HtmlT m ()\nportal :: Monad m =\u003e DOMElement -\u003e HtmlT m a -\u003e HtmlT m a\ninstallFinalizer :: MonadReactive m =\u003e IO () -\u003e m ()\n\n-- Dynamic collections\nsimpleList :: Dynamic [a] -\u003e (Int -\u003e DynRef a -\u003e Html ()) -\u003e Html ()\n\n-- Arbitrary dynamic content\ndyn :: Dynamic (Html ()) -\u003e Html ()\n\n-- Contructing Events\nnewEvent :: MonadReactive m =\u003e m (Event a, Trigger a)\nfmap :: (a -\u003e b) -\u003e Event a -\u003e Event a\nnever :: Event a\nupdates :: Dynamic a -\u003e Event a\n\n-- Constructing Dynamics\nconstDyn :: a -\u003e Dynamic a\nfromRef :: DynRef a -\u003e Dynamic a\nfmap :: (a -\u003e b) -\u003e Dynamic a -\u003e Dynamic b\n(\u003c*\u003e) :: Dynamic (a -\u003e b) -\u003e Dynamic a -\u003e Dynamic b\nmapDyn :: MonadReactive m =\u003e Dynamic a -\u003e (a -\u003e b)-\u003e m (Dynamic b)\nmapDyn2 :: MonadReactive m =\u003e Dynamic a -\u003e Dynamic b -\u003e (a -\u003e b -\u003e c) -\u003e m (Dynamic c)\nmapDyn3 :: MonadReactive m =\u003e Dynamic a -\u003e Dynamic b -\u003e Dynamic c -\u003e (a -\u003e b -\u003e c -\u003e d) -\u003e m (Dynamic d)\nholdUniqDyn :: Eq a =\u003e Dynamic a -\u003e Dynamic a\nholdUniqDynBy :: (a -\u003e a -\u003e Bool) -\u003e Dynamic a -\u003e Dynamic a\n\n-- Constructing DynRefs\nnewRef :: MonadReactive m =\u003e a -\u003e m (DynRef a)\nlensMap :: Lens' s a -\u003e DynRef s -\u003e DynRef a\n\n-- Read and write DynRefs, Dynamics\nreadDyn :: MonadIO m =\u003e Dynamic a -\u003e m a\nreadRef :: MonadIO m =\u003e DynRef a -\u003e m a\nwriteRef :: DynRef a -\u003e a -\u003e Step ()\nmodifyRef :: DynRef a -\u003e (a -\u003e a) -\u003e Step ()\natomicModifyRef :: DynRef a -\u003e (a -\u003e (a, r)) -\u003e Step r\n\n-- Starting and shutting down the application\natatchOptions :: StartOpts -\u003e Html a -\u003e IO (a, RunningApp)\nattachTo :: DOMElement -\u003e Html a -\u003e IO (a, RunningApp)\nattachToBody :: Html a -\u003e IO (a, RunningApp)\ndetach :: RunningApp -\u003e IO ()\n```\n\n\u003c/details\u003e\n\n## Other examples\n\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eCounter\u003c/td\u003e\n      \u003ctd\u003e5.6M all.js, 3.7M all.min.js\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=./examples/counter/counter.hs target=_blank\u003esource\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\n        \u003ca href=https://lagunoff.github.io/htmlt/js-backend/htmlt-counter.jsexe/ target=_blank\u003eopen\u003ca\u003e |\n        \u003ca href=https://lagunoff.github.io/htmlt/js-backend/htmlt-counter.jsexe/min.html target=_blank\u003eopen minified\u003ca\u003e\n      \u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eTodoMVC\u003c/td\u003e\n      \u003ctd\u003e3.1M all.js, 773K all.min.js\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=./examples/todomvc/todomvc.hs target=_blank\u003esource\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\n        \u003ca href=https://lagunoff.github.io/htmlt/js-backend/htmlt-todomvc.jsexe/ target=_blank\u003eopen\u003ca\u003e |\n        \u003ca href=https://lagunoff.github.io/htmlt/js-backend/htmlt-todomvc.jsexe/min.html target=_blank\u003eopen minified\u003ca\u003e\n      \u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eSimple Routing\u003c/td\u003e\n      \u003ctd\u003e11M all.js, 7.6M all.min.js\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=./examples/simple-routing/simple-routing.hs target=_blank\u003esource\u003c/a\u003e\u003c/td\u003e\n      \u003ctd\u003e\n        \u003ca href=https://lagunoff.github.io/htmlt/js-backend/htmlt-simple-routing.jsexe/ target=_blank\u003eopen\u003ca\u003e |\n        \u003ca href=https://lagunoff.github.io/htmlt/js-backend/htmlt-simple-routing.jsexe/min.html target=_blank\u003eopen minified\u003ca\u003e\n      \u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\nFor comparison, here are the sizes of all.js files build with GHCJS 8.6\n— 1.5M\n[htmlt-counter](https://lagunoff.github.io/htmlt-counter/),\n1.4M\n[htmlt-todomvc](https://lagunoff.github.io/htmlt-todomvc/),\n3.3M\n[htmlt-simple-routing](https://lagunoff.github.io/htmlt-simple-routing/)\n\n## Todos\n - [x] Migrate to GHC with JavaScript backend\n - [ ] More examples and documentation\n - [ ] Similar library for ReactNative\n\n## Legacy GHCJS version\nThe legacy version for GHCJS 8.6 and GHCJS 8.10 can still be found in\nthe [ghcjs](https://github.com/lagunoff/htmlt/tree/ghcjs) branch\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flagunoff%2Fhtmlt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flagunoff%2Fhtmlt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flagunoff%2Fhtmlt/lists"}