Building a Reflex FRP Project with Nix!

reflex-frp.jpg

Over these last few weeks, we've gone over a few different package managers we can use with Haskell. Of these, Nix has the broadest potential, but it's also the most complicated to use. This week, we'll conclude our look at package management by putting Nix to work. We'll be using Reflex FRP, a Haskell framework for frontend web development.

This framework has many complicated dependencies, so it's basically necessary to use Nix. Stack and Cabal by themselves aren't going to be able to capture all the requirements. In this article, we'll go through some steps to set ourselves up to make a Reflex FRP project. Along the way, we'll learn a little bit about the basics of this library.

If you're ready for Nix and FRP frontend apps, you're ready to use Haskell for many cool things! Download our Production Checklist for some more ideas!

Project Setup

Before you do this setup phase, take note that it will take quite a while due to the volume of packages. We'll be following along with this guide. You'll want to make a new directory with access to the reflex-platform Git repository.

>> mkdir ReflexProject && cd ReflexProject
>> git init
>> git submodule add https://github.com/reflex-frp/reflex-platform

You then need to run the try-reflex script within that repository. If you don't have Nix, this will install Nix. But regardless, it will then use Nix to install many dependencies needed for Reflex. So feel free to run the command and let your terminal sit for a while.

Managing Multiple Packages

For this project, we'll be using a combination of Nix and Cabal, but in a different way than we tried a couple weeks ago. Back then, we converted a .cabal file to a Nix project. For Reflex FRP, the recommended project structure is to use three different packages. First we would want one for frontend web elements. The second is for a backend server, while we'd have a third common package for elements used by both. To reflect this structure you'll make three different directories. Then run cabal init in each of them.

>> mkdir common frontend backend
>> (cd common && cabal init)
>> (cd frontend && cabal init)
>> (cd backend && cabal init)

The common package will be a library and you should ensure it exposes at least one module. The other two packages should be executables that depend on common. All three should depend on reflex-dom.

Now we need to make a default.nix file to pull this all together. It should look like this:

{ system ? builtins.currentSystem }:
(import ./reflex-platform { inherit system; }).project ({ pkgs, ... }: {
  packages = {
    common = ./common;
    backend = ./backend;
    frontend = ./frontend;
  };

  shells = {
    ghc = ["common" "backend" "frontend"];
    ghcjs = ["common" "frontend"];
  };
})

The simplest part to see is that we list each of our different packages within this expression. Then we declare two "shells" for the different compilers we can use. We can compile any of our 3 packages with GHC. But then for our frontend and its common dependency, we also have the option of using GHCJS. We'll see this come into play in a little bit.

The important thing to notice at the top is that we are using the reflex-platform submodule as a dependency. This lets us avoid worrying about a lot of other dependencies in this file.

A Simple Frontend

Now let's take a step back from our project structure for a second and write out a basic frontend landing page. This code will go in frontend/Main.hs:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Reflex.Dom

main :: IO ()
main = mainWidget $ el "div" $ text "Welcome to Reflex!"

The Reflex.Dom module exports most of the different elements we'll need to build a simple web page. The mainWidget expression provides us with a way to run a widget as an IO action. Then el allows us to make an element of the specified type (a div in this case). We can then provide some simple text using the text element. As you can probably tell, this will give us a webpage that displays our desired text.

We'll definitely explore more details about Reflex FRP syntax at a later date. But for now, the important thing to understand is that we have a Haskell executable that displays a webpage.

Building Our Code with Nix

But how do actually use this executable? Well there are a couple ways. The first way uses Nix by itself:

>> nix-build -o frontend-result -A ghcjs.frontend

Note how we're using the ghcjs shell to compile this instead of ghc. GHCJS knows how to generate Javascript from Haskell, rather than a raw binary.

We can then look in the output directory, frontend-result/bin to see the results. There's another directory frontend.jsexe in there. It contains several Javascript helper files, but one main index.html file. If we pull this index file into our browser, we'll see our web page text!

Building with Cabal

Relying on Nix for building does have a weakness though. Nix doesn't do incremental builds. So it will need to build our whole package every time. This can be frustrating if you like re-building many times after small changes.

So we can also build our frontend with Cabal, which knows how to do things in a more incremental way! We'll still use a nix-shell to ensure we have our all our dependencies.

>> nix-shell -A shells.ghcjs --run \
  "cabal --project-file=cabal-ghcjs.project \
    --builddir=dist-ghcjs new-build all"

Note how we use the GHCJS project to again ensure we get the right output. This will also build our code, producing the same frontend.jsexe directory. This time though, it will live within dist-ghcjs.

A small price of using Cabal is that we have to dig deeper in the file structure to find it (eight directories)! But scripting can relieve this burden. The point is, we can now incrementally build our frontend executable!

Conclusion

This concludes our series on package management! You should now have the tools to get started with some pretty interesting projects. You can use any of the different programs we've covered here, whether Cabal, Stack, Nix, or a combination!

This marks a nice transition point for us. From here we'll move on and look at some interesting concepts when it comes to web development. We'll stick with the general app structure we started building here with Reflex FRP. It will be a little while before we look at frontend concepts in earnest though. We'll start by exploring more backend considerations next week.

The coming weeks' topics will be more related to Haskell in production! Download our Production Checklist for more ideas to try!

Previous
Previous

Announcing Practical Haskell!

Next
Next

Using Nix to Fetch C Libraries!