We are in the process of open sourcing a lot of the general purpose libraries we’ve built at Silk under the BSD3 license. We’re planning to write a series of blog posts introducing them so please stay tuned!
First off is one of the smaller packages,fay-builder (github).
Fay is a proper subset of Haskell that compiles to JavaScript, as well as a compiler. I’ve been helping out with the project since its first release in July 2012.
We have several Haskell backend services with admin interfaces written in HTML and JavaScript that communicate with the running server using REST APIs. It seemed like a good idea to write their admin interfaces in Haskell and we have been experimenting with using Fay for this. Since Fay supports Cabal packages we can use our usual tools and upload these packages to the public hackage or our private instance.
fay-builder lets you store options needed to compile Fay code along
with an executable. This is done by using custom cabal flags that can
be read either by Setup.hs
(but see caveats below) or from within the
application at startup, on request, or at some other point.
If you want to try it out you can install it by running cabal install
fay-builder
.
Join our team if you enjoy this stuff too, we’re hiring!
The Cabal file
Here’s how you can define a Cabal file that uses fay-builder for a project:
name: my-program
version: 0.1
Use a custom build type
build-type: Custom
extra-source-files
specifies files what should be included with a
source distribution. The fay files are added here so they don’t get
lost when running cabal sdist
.
extra-source-files: fay/*.hs
data-files
are also included, with the addition that Cabal will
generate a paths module that you can use to fetch the resources during
runtime. We probably want to precompile the fay files so the program
can just serve them.
data-files: my-program.cabal, js/*.js
x-foo
specifies custom flags that Cabal itself ignores, but we can
use it for extra configuration options.
x-fay-packages: fay-jquery, fay-text
x-fay-root-modules: Index, Misc
x-fay-include-paths: src
x-fay-output-dir: data/js
x-fay-source-dir: fay
executable my-program
main-is: Main.hs
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -Wall
This is the haskell module Cabal generates to let us access data-files.
other-modules: Paths_my_program
To make sure we can build the Fay code we add all the dependencies it has here along with the GHC libraries dependencies.
One thing to note is that we can’t depend on base and fay-base at the same time since they have clashing module names. But if we depend on some other fay package we get a transitive dependency to fay-base and we know it will be available.
build-depends: base, Cabal, fay, fay-builder, fay-jquery, fay-text
We have a few options for when to build the Fay code.
Building as a Cabal hook
With a custom Setup.hs we can add a postBuildHook that will compile the Fay code after the executable has been compiled, but before an sdist is created. This is nice because it will generate the needed .js files before the tarball is created. The disadvantage is that Setup.hs cannot have explicit dependencies so you won’t be able to sdist your program unless you already have all dependencies installed.
It is possible to do this, and fay-builder includes a defaultFayHook
that you can use in Setup.hs like this:
import Fay.Builder (defaultFayHook)
main = defaultFayHook
It’s pretty cool, but maybe not very useful because of the dependency
issue. Just make sure to have fay-builder installed before runningcabal configure
.
Building inside the program
For one application I started out with the above Setup.hs approach,
but soon realized that this would not work out since sdisting wasn’t
possible in a clean package DB. I instead chose to add a--compile-fay
flag that would compile all Fay sources when the
process starts when developing. The live setup won’t do this and
instead read the data-files.
The trick here is that we added the .cabal file as a data-file to let us access it from other modules.
import qualified Paths_my_program as Paths
import qualified Fay.Builder as Builder
compileFay :: IO ()
compileFay = do
pkgDb <- getPackageDb
packageDesc <- Paths.getDataFileName "my-program.cabal">>= Builder.readPackageDescription
Builder.build packageDesc pkgDb
where
-- Some clever way to fetch the current package DB
-- if you are using a sandbox.
getPackageDb :: IO (Maybe FilePath)
If we had added the settings elsewhere, such as in a customconfigurator file
like snaplet-fay
does, we wouldn’t be able to read it from within Setup.hs
so the cabal
file might be the only place to put this configuration if we want both
features at once.
One of the biggest advantages in using fay-builder is that all dependencies can be listed in one cabal file instead of having to split the client and server code into multiple packages (which would turn into three different packages if there’s shared code).
Image may be NSFW.Clik here to view.