|
| 1 | +# PureScript 0.15 Migration Guide |
| 2 | + |
| 3 | +## ES modules migration guide |
| 4 | + |
| 5 | +### tl;dr |
| 6 | + |
| 7 | +* [How can I update CJS to ESM?](#how-can-i-update-cjs-to-esm) |
| 8 | +* [How can I use Purescript v0.15 on Node.js?](#how-can-i-use-purescript-v015-on-nodejs) |
| 9 | +* [How can I bundle my library or application?](#how-can-i-bundle-my-library-or-application) |
| 10 | + |
| 11 | +### A little bit of background |
| 12 | + |
| 13 | +In April 2021 the Node.js LTS version 10 reached end-of-life, which was the last version that did not yet support ES modules (ESM). This means, that all Node.js LTS and current versions support ES modules. And since all major browsers support ESM already since a long time, there is no need anymore in the JS ecosystem to support Common JS (CJS) and the community is advocating to [drop CJS support](https://github.com/sindresorhus/meta/discussions/15), which kinda makes sense. The community is following suit and is dropping its support for CJS, some notable examples are [remark](https://github.com/remarkjs/remark/tree/main/packages/remark#install), [`node-fetch`](https://github.com/node-fetch/node-fetch#commonjs) and [`framer-motion`](https://github.com/framer/motion) amongst many others. |
| 14 | + |
| 15 | +What does that mean for Purescript v0.14? Well, a little bit of bad news, because Purescript v0.14 only supports CJS. Fortunately, there has been a [long-standing PR](https://github.com/purescript/purescript/pull/3791) to support ES modules in Purescript. So time to get this over the finish line and get ESM and |
| 16 | + |
| 17 | +### Welcome to Purescript v0.15 with ESM support! |
| 18 | + |
| 19 | +Purescript v0.15 is here and supports ESM with all of its advantages: |
| 20 | + |
| 21 | +* You can use ESM-only libraries |
| 22 | +* A cleaner and simpler way of writing FFI |
| 23 | +* Helping tools to do their job like [DCE](https://en.wikipedia.org/wiki/Dead_code_elimination) or [Code-Splitting](https://reactjs.org/docs/code-splitting.html#code-splitting) |
| 24 | + |
| 25 | +However, this has a couple of implications that will need you to migrate your code and tooling setup: |
| 26 | + |
| 27 | +* v0.15 drops support of CJS |
| 28 | + |
| 29 | + With no real [reason](#a-little-bit-of-background) to keep supporting CJS, we have decided to drop CJS support alltogether. The maintenance burden would have been just too high to support CJS as well. This means you will need to update your FFI to ESM. More on this here: |
| 30 | + |
| 31 | + [How can I update CJS to ESM?](#how-can-i-update-cjs-to-esm) |
| 32 | + |
| 33 | + |
| 34 | +* v0.15 drops support for Node.js versions < 12 |
| 35 | + |
| 36 | + This is just the logical consequence of Node.js versions < 12 having reached EOL and not supporting ESM. More on this here: |
| 37 | + |
| 38 | + [How can I use Purescript on Node.js?](#how-can-i-update-cjs-to-esm) |
| 39 | + |
| 40 | +* v0.15 drops `purs bundle` and relies on an external bundlers |
| 41 | + |
| 42 | + Yes, you heard right. The Purescript compiler no longer comes with a built-in `bundle` command. `purs bundle` was already broken in a couple of ways, didn't do a great job on bundle size, and was basically unmaintained. Updating `purs bundle` to ESM would have required a considerable amount of work, taking time away from the compiler team to work on more urgent matters in the compiler. |
| 43 | + |
| 44 | + Therefore, v0.15 relies on an external bundler like `esbuild`, `webpack` or `parcel`. And that is good news because these tools are used industry-wide and do a much better job on bundling than `purs bundle`. You will see significantly improved bundle sizes with v0.15, like e.g. for [purescript-halogen template](https://github.com/purescript-halogen/purescript-halogen-template): |
| 45 | + |
| 46 | + | Bundler | Size | |
| 47 | + |--------------|-----------| |
| 48 | + | v0.14 with `purs bundle` | **259Kb** | |
| 49 | + | v0.14 with `purs-bundle` and `esbuild` | **110Kb** | |
| 50 | + | v0.15 with `esbuild` | **82Kb** | |
| 51 | + |
| 52 | + Another advantage is, that bundles are much more readable which facilitates debugging. Compare: |
| 53 | + <details> |
| 54 | + <summary>v0.15 bundle</summary> |
| 55 | + |
| 56 | + ```purescript |
| 57 | + (() => { |
| 58 | + // output/Effect.Console/foreign.js |
| 59 | + var log = function(s) { |
| 60 | + return function() { |
| 61 | + console.log(s); |
| 62 | + }; |
| 63 | + }; |
| 64 | +
|
| 65 | + // output/Main/index.js |
| 66 | + var main = /* @__PURE__ */ function() { |
| 67 | + return log("\u{1F35D}"); |
| 68 | + }(); |
| 69 | +
|
| 70 | + // <stdin> |
| 71 | + main(); |
| 72 | + })(); |
| 73 | + ``` |
| 74 | + </details> |
| 75 | + <details> |
| 76 | + <summary>v0.14 bundle</summary> |
| 77 | +
|
| 78 | + ```purescript |
| 79 | + // Generated by purs bundle 0.14.5 |
| 80 | + var PS = {}; |
| 81 | + (function(exports) { |
| 82 | + "use strict"; |
| 83 | +
|
| 84 | + exports.log = function (s) { |
| 85 | + return function () { |
| 86 | + console.log(s); |
| 87 | + }; |
| 88 | + }; |
| 89 | + })(PS["Effect.Console"] = PS["Effect.Console"] || {}); |
| 90 | + (function($PS) { |
| 91 | + // Generated by purs version 0.14.5 |
| 92 | + "use strict"; |
| 93 | + $PS["Effect.Console"] = $PS["Effect.Console"] || {}; |
| 94 | + var exports = $PS["Effect.Console"]; |
| 95 | + var $foreign = $PS["Effect.Console"]; |
| 96 | + exports["log"] = $foreign.log; |
| 97 | + })(PS); |
| 98 | + (function($PS) { |
| 99 | + "use strict"; |
| 100 | + $PS["Main"] = $PS["Main"] || {}; |
| 101 | + var exports = $PS["Main"]; |
| 102 | + var Effect_Console = $PS["Effect.Console"]; |
| 103 | + var main = Effect_Console.log("\ud83c\udf5d"); |
| 104 | + exports["main"] = main; |
| 105 | + })(PS); |
| 106 | + module.exports = PS["Main"]; |
| 107 | + ``` |
| 108 | + </details> |
| 109 | + More on this here: |
| 110 | +
|
| 111 | + [How can I bundle my library or application?](#how-can-i-bundle-my-library-or-application) |
| 112 | +
|
| 113 | +### How can I update CJS to ESM? |
| 114 | +
|
| 115 | +When you are writing JS FFI the most common situations where you will see changes are: |
| 116 | +
|
| 117 | +* Importing a module |
| 118 | +
|
| 119 | + In v0.14 you had to import a module using `require` |
| 120 | +
|
| 121 | + ```javascript |
| 122 | + const mymodule = require('mymodule') |
| 123 | + ``` |
| 124 | + |
| 125 | + In v0.15 you need to use `import` |
| 126 | + |
| 127 | + ```javascript |
| 128 | + import * as M from 'mymodule' |
| 129 | + // or import specific items from the module |
| 130 | + import { main } from 'mymodule' |
| 131 | + ``` |
| 132 | +* Exporting variables and functions in your FFI |
| 133 | + |
| 134 | + In v0.14 you had to use `exports` |
| 135 | + ```javascript |
| 136 | + exports.world = "🗺" |
| 137 | + exports.greet = function() { return "hello " + world } |
| 138 | + ``` |
| 139 | + |
| 140 | + In v0.15 you need to use `export` |
| 141 | + ```javascript |
| 142 | + export const world = "🗺" |
| 143 | + |
| 144 | + export function greet() { return "hello " + world } |
| 145 | + ``` |
| 146 | + |
| 147 | +Fortunately, there are tools that can automatically perform this conversion for you in most of the cases. |
| 148 | + |
| 149 | + |
| 150 | +#### Automatically convert CJS to ESM |
| 151 | + |
| 152 | +The best migration tool we have evaluated and recommend is [lebab](https://github.com/lebab/lebab). |
| 153 | + |
| 154 | +You can do just that transformation with one of the following commands: |
| 155 | +```bash |
| 156 | +# replace all *.js files in the src directory by rewriting them from |
| 157 | +# commonjs modules to es modules |
| 158 | +lebab --replace src --transform commonjs |
| 159 | +lebab --replace test --transform commonjs |
| 160 | + |
| 161 | +# you can also provide glob patterns, if you would like |
| 162 | +lebab --replace 'src/js/**/*.jsx' --transform commonjs |
| 163 | +``` |
| 164 | + |
| 165 | +The CommonJS → ES modules transform is considered unsafe, because there are some edge cases the tool is unable to handle. These are issues worth checking for in your updated code: |
| 166 | +https://github.com/lebab/lebab#unsafe-transforms |
| 167 | + |
| 168 | +In general though it works well in most of the cases. |
| 169 | + |
| 170 | + |
| 171 | +Another option you can try is [`cjstoesm`](https://github.com/wessberg/cjstoesm). |
| 172 | + |
| 173 | +### How can I use Purescript v0.15 on Node.js? |
| 174 | + |
| 175 | +Purescript v0.15 dropped support for Node.js versions below 12. If you are on an older version, you will need to upgrade to at least version 12, but better to the current versions. At the time of writing these are Node.js 16 (LTS) and Node.js 17 (Current). |
| 176 | + |
| 177 | +To run your application, you can either use `spago run` or create an `index.js` |
| 178 | + |
| 179 | +```javascript |
| 180 | +// index.js |
| 181 | +import { main } from 'output/Main/index.js' |
| 182 | +main() |
| 183 | +``` |
| 184 | + |
| 185 | +and run |
| 186 | +```bash |
| 187 | +node index.js |
| 188 | +# or if you are on Node.js 12 |
| 189 | +node --experimental-modules index.js |
| 190 | +``` |
| 191 | + |
| 192 | + |
| 193 | +### How can I bundle my library or application? |
| 194 | + |
| 195 | +As discussed before, v0.15 drops support for `purs bundle` and therefore relies on an external bundler. We recommend three bundlers, namely [`esbuild`](https://esbuild.github.io/), [`webpack`](https://webpack.js.org/) and [`parcel`](https://parceljs.org/docs/). To make the transition easy for you, we have decided to make `spago bundle-app` and `spago bundle-module` use `esbuild` internally. The reasons for this are: |
| 196 | + |
| 197 | +* the evaluated bundlers don't differ much in minified bundle size and are much better than the current purs-bundle |
| 198 | +* `esbuild` outperforms the other bundlers when producing a "readable" bundle |
| 199 | +* `esbuild` is a standalone tool that doesn't require npm or node |
| 200 | +* `esbuild` doesn't require a config in comparison to webpack |
| 201 | +* `esbuild` is way faster than the others |
| 202 | + |
| 203 | +So you can keep using `spago` to bundle, but it requires you to install `esbuild`. More on this [later](#using-spago-to-bundle). |
| 204 | + |
| 205 | +Nonetheless, the other two bundlers are also great options: |
| 206 | +* [`webpack`](https://webpack.js.org/) - has shown the best results in bundle size, but is also the slowest and most difficult to set up. If you need the smallest bundle size and full flexibility, this is probably the one you want. |
| 207 | +* [`parcel`](https://parceljs.org/docs/) - a good compromise between ease of use, speed and bundle size. If you need bundling involving html and css but still want a small, simple bundler, this is probably the one you want. |
| 208 | + |
| 209 | +For a full discussion see [the github issue](https://github.com/working-group-purescript-es/challenges/issues/5). |
| 210 | + |
| 211 | +#### Using `spago` to bundle |
| 212 | + |
| 213 | +See [`spago` documentation](https://github.com/purescript/spago#bundle-a-project-into-a-single-js-file). |
| 214 | + |
| 215 | +Basic usage: |
| 216 | +```bash |
| 217 | +spago bundle-app # bundle for the browser |
| 218 | +spago bundle-app --platform node # bundle for node |
| 219 | +spago bundle-app --minify # minified bundle for the browser |
| 220 | +spago bundle-app --platform node --minify # minified bundle for node |
| 221 | + |
| 222 | +spago bundle-module # bundle for the browser |
| 223 | +spago bundle-module --platform node # bundle for node |
| 224 | +spago bundle-module --minify # minified bundle for the browser |
| 225 | +spago bundle-module --platform node --minify # minified bundle for node |
| 226 | +``` |
| 227 | + |
| 228 | +#### Using `esbuild` to bundle |
| 229 | + |
| 230 | +See [`esbuild` documentation](https://esbuild.github.io/). |
| 231 | + |
| 232 | +Basic usage: |
| 233 | +```bash |
| 234 | +esbuild --bundle index.js --platform=browser --outfile="bundle.js" # bundle for the browser |
| 235 | +esbuild --bundle index.js --platform=node --outfile="bundle.js" # bundle for node |
| 236 | +esbuild --bundle index.js --platform=browser --minify --outfile="bundle.minified.js" # minified bundle for the browser |
| 237 | +esbuild --bundle index.js --platform=node --minify --outfile="bundle.minified.js" # minified bundle for the node |
| 238 | +``` |
| 239 | + |
| 240 | +#### Using `webpack` to bundle |
| 241 | + |
| 242 | +See [`webpack` documentation](https://webpack.js.org/configuration/mode/). |
| 243 | + |
| 244 | +Basic usage: |
| 245 | +```bash |
| 246 | +# Create webpack.config.js according to docs |
| 247 | +webpack --mode=development # bundle |
| 248 | +webpack --mode=production # minified bundle |
| 249 | +``` |
| 250 | + |
| 251 | +#### Using `parcel` to bundle |
| 252 | + |
| 253 | +See [`parcel` documentation](https://parceljs.org/docs/). |
| 254 | + |
| 255 | +Basic usage: |
| 256 | +```bash |
| 257 | +parcel build index.html --no-source-maps --no-optimize --no-scope-hoist --dist-dir "dist/" # bundle for the browser |
| 258 | +parcel build index.html --no-source-maps --dist-dir "dist/" # minified bundle for the browser |
| 259 | +``` |
0 commit comments