RFC: The Future of the PureScript Bundler

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:

  1. To ensure users can run their PureScript code in the browser and on Node without requiring additional tools
  2. 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:

  1. 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)
  2. 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:

  1. ES modules are usable in the browser without bundling. Therefore user interfaces can be developed in PureScript without the need for a bundler.
  2. 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 set type: "module" in a package.json file. For now, the compiler output contains a package.json file containing type: "module".
  3. Bundlers like Webpack and esbuild are able to provide dead code elimination for ES modules comparable with purs bundle optimized with zephyr, 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 replace pure 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:

  1. Dead code elimination is not necessary during development (it’s typically an optimization needed for production builds)
  2. It is already a best practice to rely on external tools for dead code elimination (namely, Zephyr).
  3. 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.
  4. 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:

  1. We need a more rigorous comparison of bundle sizes from PureScript 0.14 + purs bundle vs. PureScript 0.15 with an external bundler (say esbuild). 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.

  2. 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).

  3. We need to decide what should happen to the existing purs bundle and spago bundle commands to maintain a good user experience. Should they be removed altogether? Should they alias to calling a particular bundler like esbuild? Something else?

  4. 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 existing purs 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:

30 Likes

I’m for dropping the bundler.

8 Likes

On the narrow subtopic of what our bundle commands should do in the future: I’m for spago bundle calling out to some reasonable external bundler, and purs bundle being removed without replacement. This just follows from my general position (I don’t think this is controversial) that spago should be offered as our official batteries-included interface for quickly taking a PureScript project from idea to production, and purs should be a do-one-thing tool that could be integrated into other workflows as needed with a minimum of baggage.

19 Likes

I support the proposal, and I agree with @rhendric. :+1: Remove purs bundle without a replacement.

6 Likes

Yes, I’m also for removing the bundler. And I agree with @rhendric about what to do with the bundle command in spago and purs.

2 Likes

I also support this, including removing the bundler from the compiler. Having ES modules should improve the bundling options for client-side projects quite a bit.

3 Likes

I’m on board with dropping purs bundle. As for suggesting an alternative bundler, I think Snowpack sounds very appealing and may be worth a look, although I haven’t given it a proper go just yet.

4 Likes

I also support dropping the bundler and in spago bundle just print some options for code elimination like using esbuild or zephyr

1 Like

Question as a matter of form - the draft survey currently asks about issues by linking to GH compiler issues, but it does not yet take stock of the most pressing issues on other forums like Discourse or Discord threads. I definitely think we should ask about this in the survey, but I’m wondering what to link to so that folks have more information. Should I link to this Discourse thread, or should we open a GH issue specifically for this (if there’s not one already)? The advantage of a GH issue is that it’s consistent with the other issues in the survey, but A foolish consistency is the hobgoblin of little minds….

1 Like

I support this proposal (removing purs bundle without replacement), and I see some proposals about what to do with spago commands in this case, but I’m not sure I have an opinion on this latter aspect yet.
I opened an issue in the Spago repo to track this, proposals are welcome there!

4 Likes

I think this should end up as a GitHub issue, and I can take the feedback from this thread to polish up the contents to make that issue sometime next week. I published here first to get some extra visibility on a big change!

3 Likes

Thought this article was worth sharing: Comparing the New Generation of Build Tools | CSS-Tricks - CSS-Tricks

2 Likes

Especially seeing new tooling not built in JavaScript means better performance and less reliance on NPM. :+1:

2 Likes

I have also opened up an issue on the PureScript compiler with this proposal.

3 Likes