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)