UPDATE: There is now also an issue for discussion open on the PureScript compiler.
The PureScript compiler includes a bundler that performs dead code elimination and produces a single .js
file from PureScript sources, which can then be used in the browser or on Node.
The bundler exists for a few reasons:
- To ensure users can run their PureScript code in the browser and on Node without requiring additional tools
- To trim the size of the generated code by performing dead code elimination while bundling
Unfortunately, the bundler is slow, somewhat buggy (source maps don’t work, comments aren’t preserved, etc.), and difficult and risky to work on. I speak only for myself, but I think there’s been a growing feeling among the compiler developers that the bundler is a high-effort, high-risk, low-reward part of the compiler and we in general try not to touch it.
Enter ES modules, one of the major initiatives planned for PureScript 0.15. To support ES modules, we have to consider changes to the bundler – after all, we now wish to accept ES modules via the FFI, emit ES modules instead of CommonJS modules, and we’d like to add PURE
annotations so that commonly used bundlers like Webpack and esbuild can remove unused, pure functions. This is much trickier than it seems at first!
We have two paths forward for ES modules support as far as the bundler is concerned:
- We fix the bundler so it works for ES modules, which includes making changes to the
language-javascript
library upstream (ie. taking over de facto maintainership, as it is not actively maintained) - We remove the bundler altogether
There is little appetite among the compiler team for taking on (1), and essentially no interest in sinking engineering resources into the bundler over the long haul; this is a big part of why ES modules support has taken so long. @kl0tl, the author of the ES modules PR to the compiler and member of the core team, has indicated their preference for (2).
My proposal is to remove the bundler altogether.
In the remainder of this post I lay out the ramifications so we can evaluate this decision as a community.
Why Now?
As previously stated, the bundler has historically been necessary to support dead code elimination and emitting a single .js
bundle without any external tooling.
However, things have changed with ES modules:
- ES modules are usable in the browser without bundling. Therefore user interfaces can be developed in PureScript without the need for a bundler.
- ES modules are usable in Node since v14 – which will be the oldest supported version by the time PureScript 0.15 comes out – so long as you use the file extension
.mjs
or settype: "module"
in a package.json file. For now, the compiler output contains apackage.json
file containingtype: "module"
. - Bundlers like Webpack and esbuild are able to provide dead code elimination for ES modules comparable with
purs bundle
optimized withzephyr
, and may even perform better if we make other compiler changes like changing the representation of data types. These bundlers of course are also able to produce a single.js
file for use in the browser or on Node, and so they are able to replacepure bundle
.
Points (1) and (2) mean that PureScript users can run their code in the browser and on Node without requiring any additional tools, and point (3) means that users can get effective dead code elimination without using the PureScript bundler.
Unfortunately: users would no longer get effective dead code elimination without external tooling. Still, I don’t think this is much of a problem, because:
- Dead code elimination is not necessary during development (it’s typically an optimization needed for production builds)
- It is already a best practice to rely on external tools for dead code elimination (namely, Zephyr).
- Every production project I’m aware of (other than Try PureScript) already uses a JS bundler like Webpack or esbuild, which will perform the dead code elimination on the JS on your behalf.
- If you have any JS dependencies in your PureScript project, then you already must use a JS bundler like Webpack or esbuild (or take your FFI dependencies via a CDN, which is exceedingly uncommon).
For these reasons I do not think the loss of dead code elimination in the compiler warrants maintaining the bundler.
A Path Forward
We’ve established that removing purs bundle
altogether still allows users to build and run PureScript projects in the browser and on Node without any additional tooling, and that using a standard JS bundler like esbuild
or webpack
provides equivalent or superior speed and bundle sizes to purs bundle
in the case you want to optimize for production. I’ve also suggested that the built-in bundler is a drain on the already thin compiler dev team and that we’d be better off not maintaining it for the long haul. My proposal is to remove the bundler altogether in PureScript 0.15.
However, without a bundler, what does PureScript development actually look like?
I think that we still have some investigating to do before we can commit to removing purs bundle
:
-
We need a more rigorous comparison of bundle sizes from PureScript 0.14 +
purs bundle
vs. PureScript 0.15 with an external bundler (sayesbuild
). I suggest comparing minimal examples in tiny projects, larger (but small) examples like the Halogen template or specific libraries, and bigger projects like Real World Halogen and Real World PureScript React. -
We need an actual template project that uses PureScript 0.15 and Spago with no other tooling so that we can test the development experience. I suggest forking and tweaking the Halogen template, since it has no JS dependencies, or perhaps the Halogen extended template, which is a bit more real-world. We ideally do the same thing with a small Node project as well. Essentially, we need to prove that PureScript without a bundler still works (we know that PureScript + an external bundler does work).
-
We need to decide what should happen to the existing
purs bundle
andspago bundle
commands to maintain a good user experience. Should they be removed altogether? Should they alias to calling a particular bundler likeesbuild
? Something else? -
Since now essentially all PureScript users building a project will want to use a JS bundler, I think we should take a stance on suggesting one by default throughout our documentation. My suggestion is hands down
esbuild
, but whatever the case is, we need to make sure we can provide an ergonomic experience and a transition guide for existingpurs bundle
users to whatever they should be using instead.
I haven’t worked directly on the ES modules implementation, so I may be missing other things we need to investigate.
Wrapping Up
In this post I’ve proposed that we remove the PureScript bundler altogether, and I’ve suggested a path forward if we do. I’m sure that I have missed some things while putting this post together, and I would appreciate questions and clarifications from folks in the community and involved in this effort so that this decision can be fully understood. Please respond if you have things to add!
Also: For those interested in the ES modules feature, there are a few good ways to stay updated:
- You can join the
#es-modules
channel on the PureScript chat, where the ES modules working group has weekly meetings on Fridays at 9am PST. - You can follow along with discussions in the ES modules working group GitHub organization
- You can watch the PureScript compiler repo for discussions that relate to ES modules