DEV Community

Art Deineka
Art Deineka

Posted on

How to use PureScript with React in 2020

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.

Top comments (0)