PureScript <-> JavaScript Interop

tmountain [2:34 PM]
If I want to create some standalone purescript functions that integrate with a broader javascript codebase, is there a way to take the output from browserify and accomplish that?

module GCD where

import Prelude

gcd2 :: Int -> Int -> Int
gcd2 n m | n == 0 = m
gcd2 n m | m == 0 = n
gcd2 n m | n > m = gcd (n - m) m
gcd2 n m = gcd (m - n) n

$ cat build.sh
pulp browserify --main GCD --skip-entry-point --standalone index --to js/index.js

As-in, is there some magic required to import index.js and expose gcd2?

natefaubion [2:36 PM]
At Awake, we would write something like a GCD.Interop, which has bindings from JS reps to the PS reps, and then we would just import the output directly

tmountain [2:37 PM]
Can you give an example of what that would look like specifically?

natefaubion [2:39 PM]

module GCD.Interop where
import Prelude
import GCD as GCD
import Data.Function.Uncurried (mkFn2)

gcd2 :: Fn2 Int Int Int
gcd2 = mkFn2 GCD.gcd2

(edited)
That gets compiled to ./output/GCD.Interop/index.js (edited)
and then in a js file I can do

gcd2(4, 5);

tmountain [2:40 PM]
Okay cool. That is helpful. Thank you!

natefaubion [2:40 PM]
you can import the original file directly as well
but the reason we write an Interop file is so we can change PS files without breaking JS consumers, or at least, if we change types, we know that JS consumers need to be verified (edited)

tmountain [2:41 PM]
If I load the original file via a script tag, I can’t figure out how to call gcd2, as it feels like it’s hidden behind a closure, am I wrong?
I don’t totally understand the magic browserify is doing…

natefaubion [2:42 PM]
If you use browserify with standalone, then you can configure what the global variable is
so pulp browserify --main GCD --skip-entry-point --standalone GCD --to index.js (edited)
if you load that index file with a script tag
then you’ll have GCD.gcd2 available in the global namesapce (edited)

3 Likes

For anyone looking for a fully working example, here goes.

$ cat src/GCD.purs
module GCD where

import Prelude

gcd2 :: Int -> Int -> Int
gcd2 n m | n == 0 = m
gcd2 n m | m == 0 = n
gcd2 n m | n > m = gcd (n - m) m
gcd2 n m = gcd (m - n) n
$ cat src/GCD/Interop.purs
module GCD.Interop where
import GCD as GCD
import Data.Function.Uncurried (Fn2, mkFn2)

gcd2 :: Fn2 Int Int Int
gcd2 = mkFn2 GCD.gcd2

If you just want the original GCD file without the wrapper, do this.

pulp browserify --main GCD --skip-entry-point --standalone GCD --to js/index.js

Embed in your HTML as usual:

<script src="js/index.js"></script>

Then, you can invoke the curried version from the browser console.

> GCD.gcd2(10)(5)
> 5

To build the wrapped version, do this.

pulp browserify --main GCD.Interop --skip-entry-point --standalone GCD --to js/index.js

Because of the wrapper, you can do a typical JavaScript invocation.

> GCD.gcd2(10, 5)
> 5
4 Likes

Hi, folks. It’s July 2021. Does this thread still represent the state of the art in bundling Purescript code into a Javascript environment? I’m trying to run Purescript code in a Netlify function and I’m new. I would like to avoid using horribly out-of-date tools and I have no experience from which to judge.

Your advice is welcome.

2 Likes

I ended up here: GitHub - purescript/spago: 🍝 PureScript package manager and build tool powered by Dhall and package-sets

I suspect this is more what I’m looking for.

Your advice remains welcome.

1 Like

Welcome jbrains!
Yes, spago is definitely the recommended tooling on the PureScript side, and provides the bundle-app and bundle-module commands to do the same thing the pulp browserify command was doing in this post.
Oftentimes, you won’t get away with building a web application without using some other bundler like Webpack, especially if you’re integrating PureScript with some existing javascript solution instead of building a PureScript solution from scratch. In that case, another alternative is to skip the spago bundling commands, and have your javascript reference the compiled PureScript from spago build directly. In our case, we used Webpack and added an alias “@purs” for spago’s output directory, and then from javascript we could import ... from "@purs/MyModule", and then let Webpack handle all the bundling. (YMMV - disclaimers here about tree shaking, etc.)