Skip to content

Commit 4c917dd

Browse files
authored
Add es modules migration guide draft from working group challenges (documentationjs#423)
1 parent c063618 commit 4c917dd

File tree

1 file changed

+259
-0
lines changed

1 file changed

+259
-0
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
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

Comments
 (0)