How to use PureScript with React in 2020
Tags: react, webdev, functional, javascript
Published: 2020-05-17 19:37:13

CRA (`create-react-app`) is de facto standard for creating new React apps, but unless you `eject` its build mechanisms remain quite inflexible and opinionated - so one cannot just plug in arbitrary 3rd party language to be used in an app. Currently only JavaScript and TypeScript are supported out of the box.

What if I want to use PureScript - a  strongly-typed functional programming language that compiles to JavaScript for some mission critical parts of my React app. Turns out it can be done quite easily. This short article explains how.

Start a new CRA project

```
~$ npx create-react-app purs-test && cd purs-test
```

Our project will live in a folder inelaborately called `purs-test`. Verify by running `npm start`

Once inside that folder - initialize a PureScript project inside the same folder. PureScript uses `spago` as its build companion. `spago` for PureScript is what `npm` is for JavaScript, and more!

```
~/purs-test $ npm install -g spago purescript
~/purs-test $ spago init
```

Spago will detect that `src` folder and `.gitignore` file exist and re-use them. Verify that initialization succeeded and run `spago bundle-module` - this will build module `Main` that came with `spago init`

Ok - we have two projects sharing the same folder structure unaware of one another - next step is to establish friendship between the two.

As mentioned earlier CRA build mechanism is quite locked in (unless you `eject` in which case you become responsible for all build scripts) - so we will try to fool it. Luckily `spago` is capable of producing a `CommonJS` compatible bundle. It is also capable of watching your files change and rebuilding on the fly, as well as taking a parameter of where to put the artifacts.

### Attempt 0
By default `spago` places artifacts it to the current folder, but that's not very neat so let's ask it to place it some place special

```
~/purs-test $ bundle-module -w -x -t ./ps-output/index.js
```

This will download dependencies if necessary, build the project as library, and place it to `ps-output`

We can then refer to it from out React project by 

```
import Main from '../ps-output/'
```

but wait.. CRA yells at us with

```
Failed to compile.

./src/App.js
You attempted to import ../ps-output/index.js which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.
You can either move it inside src/, or add a symlink to it from project's node_modules/.
```

Turns out that all JS files, even external need to live inside `src`. Ok - we can do that:

### Attempt 1

```
~/purs-test $ bundle-module -w -x -t ./src/ps-output/index.js
~/purs-test $ npm start
```

Unfortunately this leads to an interesting situation where `spago` puts a file inside `src`, that's then picked up by CRA and triggers a rebuild which in turn triggers `spago` rebuild (That's the `-w` flag) and so it just goes in circles... forever.

### Attempt 2

One other place in out project where CRA pick's up 3rd party libraries is .. surprise surprise `node_modules`. This also allows to import dependencies by specifying non-relative paths, as if it was installed via `npm`

Let's change our `package.json` to reflect that:

```
"scripts": {
    "start": "concurrently 'npm run ps-watch' 'react-scripts start'",
    "build": "npm run ps-build && react-scripts build",
    "ps-build": "spago bundle-module -x -t ./node_modules/ps-interop/index.js",
    "ps-watch": "spago bundle-module -w -x -t ./node_modules/ps-interop/index.js",
...
}
```

And your imports now look like:

```
import Main from "ps-interop";
```


A bit of explanation needed:
`npm start` - runs 2 build concurrently, and is used for development. PureScript library is seen as 3rd party, npm supplied library

`npm build` - runs two builds sequentially - PureScript first as it's used as dependency in CRA build


## Let's build it 

Add a file to `src` folder and call it `Main.purs` - this will be our mission critical code written in a pure functional style.

```
module Main where

import Prelude

double :: Number -> Number
double n = n * 2.0

hello :: String -> String
hello name = "Hello, " <> name

```

Obviously this is just a simple example. Real PureScript code can be of arbitrary complexity, taking advantage of all the features of functional programming.

#### Use it in React

```
import React from "react";
import Main from "ps-interop";

const App = () => {
  return (
    <div className="App">
      <strong>{Main.hello("from PureScript")}</strong>
      <strong>3 time 2 is</strong> {Main.double(3)}
    </div>
  );
};

export default App;
```

Running `npm start` rebuilds when either React or PureScript parts of your app change - and so you get seamless experience and hot reload.

Running `npm build` produces bundled app, with all PureScript and React integrated into single package.

*That's it folks!*


## Should you do this?

#### No!

Don't do it unless you're doing it for fun or experimentation like I was. If you want to use PureScript library in your React app for production build - have it as a separate library, and install via your favourite package manager.