Routing using reflex-dom without Obelisk (or how to get Obelisk working)

I have a reflex SPA that’s working fine but now I need to do routing.

I’m using Obelisk but I cannot use their router because while it’s working fine using ob run, after I build my application via nix-build -o frontend-result -A ghcjs.frontend it won’t run - the generated app fails with the following error:

reflex-dom warning: hydration failed: the DOM was not as expected at switchover time. This may be due to invalid HTML which the browser has altered upon parsing, some external JS altering the DOM, or the page being served from an outdated cache.

It says warning but after that it says can't access property parentNode of undefined or something.

This error will even appear on a brand new application using ob init so I’ve completely given up.

Eventually I’ve landed onto the following setup:

  • I have a frontend :: Frontend route in my Frontend.hs only to get ob run to function (it won’t otherwise)
  • My main is using mainWidgetWithHead to get around the hydration error

That has been working fine for a few months but now I’m completely lost with routing.

  • I tried using the router from reflex-dom-contrib but I couldn’t even add the dependency.
  • I tried copying the code from reflex-dom-contrib and I got some parts of it working but while it “works” using ob run I can’t get the application to compile because it says jsaddle doesn’t have a GHCJS.Foreign module (even though HLS with cabal will find it just fine)
  • I tried adapting the 6 years old servant-router to work for my use case without success.
  • I really tried getting the Obelisk setup to run but as mentioned above, even with a brand new application it will throw the hydration error and fail. This user had the same issue 3 years ago but never posted about a resolution.

Routing is such a common thing to do I’m finding it very hard to believe that this isn’t a solved problem, but I’m probably missing something.

Hey, Its probably a year too late, but I find myself facing the same issue.

Ive been trying for a few days now to get a basic routing with auth set up. I avoided Obelisk because it seemed very opinionated towards nixos and aws for deployments so I took a reflex-platform approach. I finally implemented a basic routing with set up, but now im trying to incorporate the router from the reflex-dom-contrib as it looks to have really nice features.

I also couldnt get reflex-dom-contrib to work as a library, I was thinking about trying to fix the nix package but decided to simply import the code I wanted directly. In the Router.hs most of it worked out of the box with the exception of HasJsContent this is from Foreign.JavaScript.TH .

The api of this library and for this type has changed at least twice since the router.hs in contrib was written first to HasJS and now to JSContextSingleton. Unfortunately its not as easy as just changing the name, Im guessing the Singleton was introduced in an effort of efficiency, tbh I need to do some research and understand the implementation a bit better.

Regardless when I find a fix I will make a PR to contrib but at this stage ive had to change so much I might create a new standalone library with a subset of the essential features that will be easier to maintain into the future.

If anyone comes across this and has an interest please let me know.

I don’t actively work on this project anymore but I remember that the solution I ended up with was literally copying the code from arohi-route-client and adapting it to work with my codebase.

Thank you,

in the end my solution was this

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-deprecations #-}

import HomePage (homeWidget)
import LoginPage (loginWidget)
import Reflex.Dom
import Types (Page (..))

main :: IO ()
main = mainWidgetWithHead headElement body

headElement :: MonadWidget t m => m ()
headElement = do
  el "title" $ text "Login Prototype"
  elAttr "link" ("rel" =: "stylesheet" <> "type" =: "text/javascript" <> "href" =: "https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css") blank

-- | Create the HTML body element
body :: MonadWidget t m => m ()
body = do
  rec dynPage <-
        foldDyn ($) LoginPage $
          leftmost
            [ const HomePage <$ evLogin
            , const LoginPage <$ evLogout
            ]
      evLogin <- loginWidget dynPage
      evLogout <- homeWidget dynPage evLogin
  return ()

But I ended up abandoning Reflex and instead started to use the new JS backend for GHC.

Just posting the code here in case anyone comes across this discussion and needs a quick fix.

I created a PR a few weeks ago with an update to reflex-examples and heard nothing back so decided to not make any more PR’s.

What is your experience with the JS backend so far? Do you think it’s usable enough to build a SPA?

2 Likes

Honestly, I’ve been having so much fun using it. It was exactly what I hoped it would be.

I guess for some people getting the compiler set might be a challenge, but I was able to build it from source and set it up with ghcup with no issues and I’m by no means an expert, I also compiled it from source on my nixos desktop with no issues.

Once I had the js backend it was easy to integrate it with cabal etc, HLS doesn’t work directly with it but I just used default hls in my nvim setup, it just flags some of the foreign functions as issues, but everything compiles fine with no issues or warnings.

With regard a SPA I certainly think so and I’ve been doing exactly that to build some friend’s app for their social enterprise. They needed something to help with automation of their operations, I built the first version with Reflex but now I’m doing it all with the ghc js backend.

My project needs were not that demanding I can take my time with this because there is no time pressure, and the users will just be a dozen or so of my friends. I’m not sure how viable it would be for a commercial app, but that opinion isn’t based on anything I’ve seen just a lack of experience using it in production.

There are some things people might be concerned about the js bundle was around 1M for the hello world because it has the runtime in it. However, when I compressed this with Brotli set to 5 it came down to 180KB

I think as the ecosystem evolves around the js backend it will become a lot more viable for anyone to use with confidence. To this end I actually started writing a library, well two actually first a subset of curated bindings for the js DOM functions with helper functions and second a simple library that uses these bindings to create a simple app with basic user management and routing.

I intend these to be a pair of libraries with good documentation, tooling and nix support so anyone can get building for the front end fast.

My vision is something simple with helper functions and syntax for common tasks but still with the option to use custom html/css js when needed. I really want something that can convert a figma design fast. Then I plan to put some focus into the tooling using Nix to make life easier and providing options for Android and IOS webview apps.

With regard state management I’ve just been using a servant backend with some basic monads on the frontend. I intend to ship the library without a state system so the user can choose their own, but long-term I’d like to provide the option of one or two state systems, probably a FRP option and definitely a state machine approach I’ve been working on.

So far, I’m nearly done with the bindings and hope to get it and the other library done this weekend or next, it really depends on other factors in my life, I’m currently finishing up a long full-time contract, so job hunting is taking a priority.

But once it’s in a decent MVP state in which it possible to build SPA’s ill post it here.

6 Likes

This is really exciting! Looking forward to see it :slightly_smiling_face:

I’m still working on releasing my framework. At this rate, I think it will be weeks or months before I can release even an alpha version, mainly due to my current high work and life commitments.

However, I’ve built a supporting library just focused on the DOM. This underlying library just uses FFI and IO for DOM functions, but it’s more than enough to build sites with and is what I’m using to build my framework.

I thought it made sense to wrap it to potentially be used separately, and to that end, I tried to make it generic. You can take a look here:

I intend to keep improving it, so any and all feedback is greatly appreciated. I hope to release its sister repo ‘moka’ in the coming weeks. Moka uses moka-dom and provides a Blaze-like syntax for building a frontend apps. It will be declarative and state system agnostic. I intend to release a third library with a state system I’ve been working on but want to keep the other libraries free of its influence.

There is just so much to do with the GHC JS backend. The hardest part is honestly picking a focus and getting something out the door, but I highly recommend trying it out. Hopefully, the library I shared will help towards that end.

The documentation and presentation needs work, so please just reach out with any and all questions.

To summarize:

  1. There’s the alpha of the DOM bindings I posted here, which will be steadily improved over the coming weeks.
  2. In the very near future, I will release the framework library built on top of it.
  3. Possibly in a month or two, I’ll release the state system I’m building.
1 Like

Interesting work. Can you compare moka-dom vs something like ghcjs-dom and/or ghcjs-base? Those packages also provide low-level javascript FFI. From what I understood, they should also work with the ghc javascript backend.

Yeah definitely I have something to that effect in the TODO.md bench marking and also simply using existing ghcjs libraries. But I think the people behind ghcjs are the same who are working on the ghc js backend. From what I read they were frustrated with trying to keep it in sync with GHC developments, so they decided to just add it to the GHC as a backend and basically sunset ghcjs.

This as far as I know is why frameworks like Reflex are stuck on GHC 8, I think all the current frontend frameworks will eventually need to make the move to the new ghc backend.

But I dont know how much of what exists like ghcjs-dom etc is carried over. Something I need to test, but it all takes time and im keen to finish my framework. If someone else know about these libraries Im really curious.

Ghcjs-dom and jsaddle have been updated to work with the new JS backend. I’ve only used the WebSockets backend but I think compiling to JavaScript should work as well.

3 Likes