Recommended tooling for PureScript applications in 2019

There is now a version of this guide for 2020:
https://discourse.purescript.org/t/recommended-tooling-for-purescript-in-2020


I often see questions in Slack about what tools are supposed to be used in PureScript projects. This post is an attempt to clarify tooling for the common case: a PureScript app run in the browser, possibly with a mix of JavaScript and PureScript code.

There are a few sections here, feel free to skip around:

  1. Summary of tools
  2. Starting a new project, installing tools
  3. Installing PureScript libraries, building the project
  4. Installing JavaScript libraries
  5. Bundling our application to a single .js file, including JavaScript and PureScript code
  6. Using Yarn + package.json as a script runner

Side note: all of this stuff is used in practice in the Real World Halogen example application, if youā€™d like to see it in action.

Summary of Tools

If you are starting a new application in PureScript today, I recommend the following tools:
  1. yarn to install all the other tools (including the PureScript compiler), install JavaScript libraries, and run scripts from a package.json file. As of Feb 2020: The new Yarn 2 doesnā€™t support installing the PureScript compiler. Yarn 1 is still fine, but you should prefer NPM to Yarn 2.
  2. spago to install PureScript libraries, build the PureScript project, generate documentation, and perform other miscellaneous PureScript tasks
  3. webpack or parcel to perform bundling and optimization steps on the JavaScript compiled by PureScript and any JavaScript dependencies, to ultimately produce a single app.js file.

Some other tools worth mentioning:

  • psa is an error / warning reporting frontend for the compiler which lets you customize warnings, get better source spans in errors, and more. If you are using Spago, then Spago will use psa by default if you have it installed.

  • npx, which comes with your Node / npm installation, lets you run all commands using locally-installed tools instead of globally-installed ones. For example, to use a local Spago install instead of a global one: npx spago build instead of spago build. Note: @cprussin mentioned that Yarn supports the same functionality without using npx: yarn spago build will look for spago in your scripts and then in your local installed modules.

  • If you use nix, then you can just use nix to install all these tools instead, skipping npx. In fact, with spago2nix and yarn2nix you can generate Nix expressions for all your dependencies. You can also easily install all the tools in this section on Nix using easy-purescript-nix.

  • purty is a code formatting tool for PureScript. Itā€™s excellent, and you can install it the same way as everything else.

  • zephyr is a dead code elimination tool for PureScript. If you have a lot of dependencies with unused modules (purescript-web-* is a common culprit) then it can nicely trim down your bundle size. You may have to download the binary directly, or use nix to install it.

  • psc-ide provides really good editor support for PureScript, usable in VSCode, Emacs, Vim, etc. and is provided as part of the compiler by default (you donā€™t need to install anything).

  • pscid is a really fast file watcher which will report errors and provide suggestions in the shell. As mentioned by @Benjmhart this can be useful when the compiler + your bundler doesnā€™t provide a fast enough feedback cycle.

  • psvm is a version manager for PureScript; if you donā€™t use local PureScript installs for your projects and install rely on a global installation, then psvm lets you easily switch between different versions of PureScript. nvm is a similar tool for Node versions, which inspired psvm.

Some other tooling from the PureScript ecosystem which I no longer use often, but which youā€™ll see in the wild:

  • npm is used for the same things as yarn (install JavaScript libraries, install tooling from the NPM registry like the compiler and Spago, run scripts from package.json) and was the only option before 2016. I and many others prefer Yarn, but it doesnā€™t really matter which one you use for a PureScript project.

  • bower was a package manager for JavaScript which happened to work well for PureScript, too. Itā€™s now in maintenance mode, and most folks use Yarn for JavaScript dependencies and Spago for PureScript dependencies. Bower is still used as a registry for PureScript packages and you will still need to use it if you are publishing a library (though Spago will support this soon).

  • pulp was the de facto standard build tool for PureScript projects until Spago was released. Both tools will let you build your project using the PureScript compiler (purs) and largely have similar commands.

  • psc-package was the first package manager built on the concept of package sets (a collection of packages which are all known to compile together, which you install without specifying a specific version). However, its main deficiency was that you had to either download the main PureScript package set manually and place it in your project, or write your own. You then had to manually add packages and maintain the package set, all in difficult-to-maintain JSON files. It is not a build tool, but is used by other build tools under the hood. Spago provides a much better workflow, and is also based on package sets. See this issue from the Real World project for problems I had with psc-package and this pull request which migrated the project from Bower to Spago.

  • purp is a tool for building projects using psc-package. I havenā€™t used the tool myself, but as far as I can tell its functionality is matched (and exceeded) by Spago.

  • spacchetti was the first tool to manage package sets as Dhall files instead of JSON, unlocking significantly easier modification of the main package set + adding your own packages. Itā€™s now used to power the main package set and other functionality has been rolled in to Spago instead.

As of today, if you are publishing a library then you will most likely want to use Pulp + Bower, and otherwise you will most likely want Spago. In the near future, Spago will also support library publishing.

Starting a New Project

Going back to the original list of tools, hereā€™s a sequence of commands to set up a new project which relies on a few libraries from the PureScript and JavaScript. It assumes you already have Yarn installed, but will install the rest along the way.

mkdir my-project && cd my-project
yarn add --dev purescript spago parcel-bundler

At time of writing, Yarn has produced a package.json file containing these dependencies:

{
  "devDependencies": {
    "parcel-bundler": "^1.12.3",
    "purescript": "^0.13.3",
    "spago": "^0.9.0"
  }
}

We can now start using these locally-installed tools to get our project started. First, weā€™ll initialize our PureScript project ā€” this will create a .gitignore file, src and test directories, and sync us with the latest official package set.

# psc-0.13.3-20190831 is the package set at time of writing
yarn spago init

We can also compile and run the Main.purs file which has been created in the src directory:

$ yarn spago run
Installation complete.
           Src   Lib   All
Warnings   0     0     0
Errors     0     0     0
Build succeeded.
šŸ

Installing PureScript Dependencies

Letā€™s say weā€™re going to use Halogen to build our UI ā€” we can now use Spago to install this PureScript library:

yarn spago install halogen

Now that itā€™s installed, we can recompile our project so Halogen and its dependencies are built:

yarn spago build

And we could now start using Halogen in our project:

module MyApp.MyComponent where

import Halogen as H
import Halogen.HTML as HH
...

Installing JavaScript Libraries

Letā€™s say we want to use `marked`, a JavaScript library for Markdown, via PureScriptā€™s foreign-function interface. We can go back to Yarn to install the library:
yarn add marked

And now our package.json has the marked library installed, too.

{
  "devDependencies": {
    "parcel-bundler": "^1.12.3",
    "purescript": "^0.13.3",
    "spago": "^0.9.0"
  },
  "dependencies": {
    "marked": "^0.7.0"
  }
}

Setting Up Bundling (Serving to the Browser)

Weā€™ve compiled our PureScript project, which has produced JavaScript files in our `output` directory, but we havenā€™t yet got anything we can actually send to the browser.

Note: this section is going to be rapid-fire just so youā€™re aware of the basics. Itā€™s not a tutorial on using tools like Parcel and Webpack. See projects like Vidtracker or Real World Halogen for real world examples.

Letā€™s use Spago to create a single app.js file from our project, instead of just compiling a bunch of modules in the output directory. That way we can actually import the resulting JavaScript file into an HTML file and view it in the browser.

mkdir dist
yarn spago bundle-app --main Main --to dist/app.js

Now, letā€™s create a tiny HTML file which imports this JavaScript. Weā€™ll then be able to open that file in the browser and use our app.

mkdir assets && touch assets/index.html

We can use a tiny HTML file like this one ā€” just enough to provide a DOM node for our application to run inside, and an import for our bundled app.js file, which weā€™ll create in a moment:

<!-- my-project/assets/index.html -->
<!DOCTYPE html>
<html>
  <body>
    <div id="app"></div>
    <script src="../dist/app.js"></script>
  </body>
</html>

If we had actually made a Halogen app, weā€™d be able to see it now. But this isnā€™t quite enough ā€” we actually need to bundle in a JavaScript library, too, the marked library we installed earlier.

Most single-page applications will use a bundler like Parcel or Webpack to bring all their JavaScript, CSS, and HTML together, minify them, and do other processing for efficiency. They also typically support other conveniences like dev servers and hot reloading.

Letā€™s get Parcel working to bring our JavaScript and PureScript together in a single JavaScript file which can be shipped to the browser. Luckily for us, all we need to do is run parcel on our index.html file:

yarn parcel build assets/index.html

Running Scripts

Itā€™s a bit tedious running all these commands manually. What if we wanted some automation? We donā€™t need to introduce any new tools.

We can adjust the package.json file to have a ā€scripts" key where we put commands weā€™d like to be able to run with Yarn. Hereā€™s a small example:

{
  "scripts": {
    "postinstall": "spago install",
    "build": "spago build",
    "clean": "rm -rf node_modules output .spago dist/* *.lock",
    "bundle": "spago bundle-app --main Main --to dist/app.js && parcel build assets/index.html",
  },
  "devDependencies": {
    "parcel-bundler": "^1.12.3",
    "purescript": "^0.13.3",
    "spago": "^0.9.0"
  },
  "dependencies": {
    "marked": "^0.7.0"
  }
}

Now that we have these scripts in place, we can run commands like:

  • yarn build to replace yarn spago build
  • yarn bundle to replace yarn spago bundle-app --main Main --to dist/app.js && parcel build assets/index.html
  • yarn install to replace yarn install && yarn spago install
30 Likes

This is a great post.

I will say that using halogen with hot reloading tends to result in duplicate components being rendered, so in your JS root file, itā€™s good to actually trigger a reload.

I have a little starter template with some built in hot reloading behaviour here: https://github.com/Benjmhart/purehmr

something i also find with this workflow is that the feedback cycle can be a bit long for the purescript compiler + parcel. (also I occasionally hit some bugs with the way purescript gets interpreted by parcel which canā€™t really be explained here)

to have a shorted feedback cycle where weā€™re concerned primarily with compilation errors, I recommend PSCID https://github.com/kRITZCREEK/pscid

1 Like

Some notes about npx:

  1. yarn spago (or yarn run spago) will run the locally installed spago, no need to use npx for this (see https://yarnpkg.com/lang/en/docs/cli/run/#toc-yarn-run-script).

  2. npx has a resolution order for running stuff. It will first check $PATH, then local packages, then if a command wasnā€™t found locally in either of those places, it will try to install the relevant package from the package repository (see https://github.com/npm/npx/blob/latest/README.md#description). It will not update the package.json of the project to indicate the packages that were installed. This has led my company to some hard to understand issues where things would work on one developerā€™s machines but not others, and makes it generally hard to reproduce system state.

Due to these points, I strongly recommend moving away from npx (my project at work has done so and the behavior of yarn run effectively supersedes npx). Note that the yarn run behavior mentioned here does not apply to npm, if youā€™re on npm you may still benefit from npx.

1 Like

@Benjmhart thatā€™s a good point about Halogen and hot reloading. Thanks for the link! Iā€™ve also added pscid to the list of tooling, because Iā€™d forgotten about it ā€“ I find the compile / error loop to be quite fast in VSCode and Vim with the IDE server, but Iā€™m not waiting on Parcel or Webpack.

@cprussin Thatā€™s good to learn about. I wasnā€™t aware you could do this with Yarn, and itā€™s one more reason to move away from npm / npx. Iā€™ve updated the post to include this information.

At this point you can easily get away with managing a PureScript project with only Yarn installed and then using it to install the compiler, Spago, and Parcel. Thatā€™s a reasonably lean set of tools. I have a soft spot for managing everything with Nix, but I donā€™t think thatā€™s a reasonable suggestion as the default for most people.

1 Like

Wow this is great. I vote the post is placed somewhere on the purescript.org site

3 Likes

Iā€™m not sure how opinionated purescript.org wants to be about tooling, but there is at least a movement to recommend Spago by default.

On the one hand I think it makes sense to add a file to the documentation repository for web-specific tooling which recommends Yarn + Spago + Parcel, but on the other, this stuff is largely a matter of opinion, is project-specific, and changes fast. That makes me ambivalent about any official endorsements.

On one hand, I like how unopinionated PS is, but on the other I donā€™t :slight_smile: I think a lot of people donā€™t know how to get started, and yarn + spago + parcel is a great way to get going. Having this list here is great, but I use PS every day and I barely ever visit this forum. Iā€™d imagine new users are similar.

1 Like

You make a good point. Iā€™ve opened an issue on the documentation repository relating to this.

1 Like

Fantastic post. It takes a while to put all the pieces together. Great to see all this in one place!

1 Like

I put together a minimal repo for anyone wanting a quick start with the tooling recommended in the thread. I left zephyr out, as I canā€™t find any docs on using it with spago. I also left out npx and psvm.

2 Likes

Nice. Whatā€™s the point in mentioning tmux though?
Not everyone uses it, and it is unrelated to PS, which is not obvious for a linux and PS newcomer.

Iā€™m a new user working through this guide and two other ā€œofficialā€ guides:

Iā€™ve found this guide to be the most helpful. Should the above two references be updated to follow this workflow?

Iā€™m attempting to update the first guide, and want to clarify what prereqs are needed to launch the repl.

Edit: Found yarn spago repl, so disregard the rest of this message.

Should purescript still be installed with npm or yarn globally, or should I find a way to temporarily add the projectā€™s specific version (located in node_modules/purescript/purs.bin) to $PATH?

Hereā€™s a log of what happens when attempting to launch the repl if purescript is only installed via yarn as described in the first post:

> purs repl
Command 'purs' not found

> node_modules/purescript/purs.bin --version
0.13.3

> node_modules/purescript/purs.bin repl
purs repl: No input files; try running `pulp psci` instead.
For help getting started, visit https://github.com/purescript/documentation/blob/master/guides/PSCi.md
Usage: For basic information, try the `--help' option.

> pulp psci
Command 'pulp' not found
1 Like

Yea, looks like the yarn ... commands are what youā€™re looking for if you want to run locally-installed tooling in your project. As far as updating the getting started guides, I opened an issue about adding this guide, but it hasnā€™t seen much progress yet.

Ultimately I think of this as my recommendation, but not necessarily something that would work as an official ā€˜PureScriptā€™ stance. Still, I think it deserves a place somewhere in the official docs.

If youā€™re new to PureScript, you might find my repo helpful as well: http://www.github.com/jordanmartinez/purescript-jordans-reference

2 Likes

This thread is super helpful, thank you all! Just thought Iā€™d offer my current WIP for using Purescript with Nix: https://github.com/tbenst/purescript-nix-example

Unfortunately, Iā€™m struggling to get spago bundle-app working (see default.nix):

purs bundle: No input files.
[error] Bundle failed.

Please let me know if you have suggestions!

Edit: that error was due to missing \ on line 28. Now I get a new error:

Couldn't find a CommonJS module for the specified main module: Example.Main
[error] Bundle failed.

I think this is because there is no Example in dist, but if I try to call spago without --no-install --no-build, it fails due to no internet connectivity in sandbox.

1 Like

Got it working! Hereā€™s my nix derivation:

with import <nixpkgs> {};

let 
  spagoPkgs = import ./spago-packages.nix { inherit pkgs; };
in
mkYarnPackage rec {
  name = "purescript-nix-example";
  src = ./.;
  packageJSON = ./package.json;
  yarnLock = ./yarn.lock;

  nativeBuildInputs = [ purescript nodejs-12_x ];

  postBuild = ''
    ${purescript}/bin/purs compile "$src/**/*.purs" ${builtins.toString
      (builtins.map
        (x: ''"${x.outPath}/src/**/*.purs"'')
        (builtins.attrValues spagoPkgs.inputs))}
    cp -r $src/assets ./
    '';

  postFixup = ''
    ${spago}/bin/spago bundle-app --no-install \
      --no-build --main Example.Main --to dist/app.js
    mkdir -p $out/dist
    cp -r dist $out/
    ln -s $out/libexec/${name}/node_modules .
    ${nodejs-12_x}/bin/node node_modules/.bin/parcel \
      build assets/*.html --out-dir $out/dist/
  '';


  meta = with stdenv.lib; {
    description = "Example for building Purescript Halogen app with Nix.";
    homepage = "https://github.com/tbenst/purescript-nix-example";
    maintainers = with maintainers; [ tbenst ];
  };
}

Full code here.

5 Likes

I agree that more opinionation would really improve the new user experience.

Thereā€™s currently no clear pathway to getting a web app running from the purescript.org landing page, even for users who read through both the quick start guide and the documentation page.

Something like create-react-app would be great. For now, it could just be a template clone command.

This would favor a particular toolchain and framework, but I donā€™t think thatā€™s worse than the existing alternative of no direction. The unfairness could be partially remedied with a link to a ā€œmore frameworksā€ page.

Having official templates would also help focus everyoneā€™s maintenance efforts and be a consistent springboard for tutorials.

For example, it would be nice if we could arrive at best practices from each of these 7 reasonable Halogen templates and track one of them under the purescript-contrib org.

purescript-halogen/purescript-halogen-template
citizennet/purescript-halogen-template
milesfrain/purescript-halogen-template/tree/tooling
cah6/purescript-halogen-template
charlescrain/create-react-app-halogen
tbenst/purescript-nix-example
JordanMartinez/learn-halogen/blob/latestRelease/src/05-ComponentTemplate-A.purs

Edit: Found some additional discourse discussion on create-react-app:

Also, here are details on how create-react-app manages templates.

We can also look to create-elm-app for inspiration, which is an excellent new user experience.

Maybe we could someday reach the state where weā€™re just telling new users to run one of the following:

spago init --template halogen
spago init --template react-basic-hooks
spago init --template cli
... etc.
9 Likes

@milesfrain great points! :clap:
I opened this issue in Spago to track the addition of the --template flag as you mentioned in your post. Iā€™ve been holding off on tracking this because I was not sure if it was a widespread need (vs just my feeling), but it looks like it would improve UX so Iā€™d be happy to see this happening

6 Likes

Does it make sense to include a link to psa? Error and warning message quality is particularly important to new users.

2 Likes

Yes, I think it makes sense to link to psa ā€“ and as a note, Spago uses psa by default under the hood if itā€™s installed.