Electron Apps with PureScript

Posted on July 5, 2016

I’ve recently taken another look at the Electron Framework for building native applications using web technologies, in particular, the PureScript programming language.

To my enjoyment there has been lots of progress since I last checked and starting an Electron project with PureScript is simpler than ever. In this blogpost I’ll show you how to:

Set up a new Electron project Add a little PureScript Freely mix Node and Browser API’s Add the Thermite library and build a minimal React application

Prerequisits

To follow along you’ll want to install:

System things: git node

Node things: npm install -g purescript bower pulp



Setting up a new Electron Project

Following the Getting started path for Electron leads to the electron-quick-start repository. We can use it to bootstrap a new Electron project like so:

# Clone the Quick Start repository $ git clone https://github.com/electron/electron-quick-start # Go into the repository $ cd electron-quick-start # Install the dependencies and run $ npm install && npm start

This should greet you with a simple Electron Application like so:

A few things to note right away:

We can reload the Application using the Menu with View -> Reload

We get to use the awesome Chrome-Dev-Tools for developing native applications

Screw the default browser styles…

Let’s take a look at the directory structure of our project:

> ls -1 LICENSE.md README.md index.html main.js node_modules package.json renderer.js

Pretty simple, huh? We’ve got the entry point for our application main.js , an HTML file we can load up in the window that main.js creates, and an empty JS file renderer.js that gets require’d from that HTML file.

I’ll not go into detail about any of those however, since this setup is already enough for us to…

Add a little PureScript

The usual way to start a PureScript project is pulp init , but since we’ve already done some scaffolding that won’t work this time. Instead we’ll do the few steps by hand.

We start by setting up a bower file to manage our PureScript dependencies:

$ bower init

After confirming, or modifying some of bower’s questions we can install the purescript-console library to prepare for our Hello Electron.

$ bower install --save purescript-console

Next up we’ll create a simple PureScript module at src/Main.purs .

module Main where import Control.Monad.Eff.Console (log) main = log "Hello, Electron"

and compile it with

$ pulp build

By default the PureScript compilers emits CommonJS modules into the output/ directory. If we want to run these in a browser we usually have to use a bundling tool like Webpack or psc-bundle. Luckily Electron knows how to deal with the CommonJS format and so we can just put the following line into the renderer.js file and reload our Electron application.

require ( './output/Main' ). main ()

If you open the Console tab inside the Chrome Dev Tools you should be greeted with Hello, Electron . YAY!

Effects and Environments

In PureScript we use the built-in rowtypes to track the effects of our programs. A program with a signature like Eff (dom :: DOM) Unit tells us, that the program accesses the DOM (Document Object Model). It also tells us that we cannot use this program inside a node interpreter, since it relies on the DOM being present. On the other hand a signature like Eff (fs :: FS) Unit , where FS is the file system effect, tells us that this program can not be run inside the browser, as file system access is only available inside a nodejs interpreter.

So usually when we encounter a signature like Eff (fs :: FS, dom :: DOM) Unit we are looking at a useless program that can neither be run in the browser, nor inside the nodejs interpreter.

Enter Electron

Electron’s secret sauce, is that it allows you to access both the Node and the Browser API’s in your programs at the same time. Let’s see how can we utilize these capabilities in a seamless manner with PureScript. We’re going to write a program, that reads the current directory and displays all the found files as an unordered list. Eventhough it’s totally overkill for this example we’ll be using the purescript-thermite library to render our webpage.

Thermite is a lightweight wrapper (350 lines including docstrings) around the React library. Since we’re looking to access the file system we’ll also need to install some node bindings.

$ npm install --save react react-dom $ bower install --save purescript-thermite $ bower install --save purescript-node-fs

Next up is modifying our Main module. PureScript’s ecosystem embraces modularity, which means we have to get a few imports out of the way first.

module Main where import Prelude import React as R import ReactDOM as RDOM import Thermite as T import Control.Monad.Eff ( Eff ) import Control.Monad.Eff.Exception (try) import DOM ( DOM ) import DOM.HTML (window) as DOM import DOM.HTML.Types (htmlDocumentToParentNode) as DOM import DOM.HTML.Window (document) as DOM import DOM.Node.ParentNode (querySelector) as DOM import Data.Either (either) import Data.Maybe (fromJust) import Data.Nullable (toMaybe) import Node.FS ( FS ) import Node.FS.Sync (readdir) import Partial.Unsafe (unsafePartial) import React.DOM (text, li', ul')

We’ll use React’s props mechanism to pass the list of file names to our component, so we’ll define a type for that first

type FileNames = { names :: Array String }

Next up we’re defining the Spec for our directory listing component. Because we don’t support any actions, we just pass defaultPerformAction and our render function is pretty simple aswell. We use map to turn our list of names into a list of <li> ’s and wrap it inside a <ul> .

dirListingComponent :: forall eff . T.Spec eff Unit FileNames Unit dirListingComponent = T.simpleSpec T.defaultPerformAction render where render _ props _ _ = [ ul' (map (\file -> li' [text file]) props . names) ]

Last is our main function in which we first grab all the file names inside the current directory.

main :: Eff ( fs :: FS , dom :: DOM ) Unit main = void do fileNames <- either (const []) id <$> try (readdir "." )

We then create a React class out of our Spec and since our component is stateless, we just pass unit as the initial state.

let component = T.createClass dirListingComponent unit

Next up is some boilerplate to grab the body element of of our page. This is considered bad practice and React will warn us, but it could easily be fixed by adding a container element inside index.html and grabbing that for rendering. For the sake of this blogpost body is fine though.

document <- DOM.window >>= DOM.document container <- unsafePartial (fromJust <<< toMaybe <$> DOM.querySelector "body" (DOM.htmlDocumentToParentNode document))

Finally we tell React to render our component into the body of the webpage, and pass the file names that we grabbed earlier.

RDOM.render (R.createFactory component {names : fileNames}) container

If everything went well, all that’s left now, is to run pulp build and refresh our Electron application.

Tada!

Wrapping up

I think Electron is an amazing opportunity for PureScript. It allows us to take our m browser-libraries, our n node-libraries and create n x m new libraries and applications. PureScript’s openness and good FFI story let us benefit from all the amazing work the JS community brings forth.

I was surprised at how easy it was it integrate Thermite into Electron. There is no need for a bundler at all. This is a lot different from when I last attempted to combine the two technologies and I’m super excited and motivated to continue exploring and writing native applications in PureScript.

The code for this blogpost is uploaded to Github.