Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES modules #3613

Open
hdgarrood opened this issue Apr 28, 2019 · 4 comments · May be fixed by gasi/purescript#3

Comments

@hdgarrood
Copy link
Member

commented Apr 28, 2019

Previously: #2207, #2558, #2574, #2575, #3427. I'm making a new issue because the discussions in many of the linked issues are quite long, and because the ES modules landscape has changed quite a lot since when most of the discussion happened two years ago (in particular, all major browsers now support ES modules). Also, we should probably implement emitting ES modules at the same time as switching to ES modules for FFI, so I think all of these things should be considered together.

ES modules provide a number of benefits over CommonJS; most notably easier dead code elimination and wide browser support (see https://caniuse.com/#feat=es6-module). Having the compiler use and understand ES modules natively would mean that many workflows wouldn't require bundling at all, which would significantly speed up and simplify things, especially during development.

It should remain possible to run a PureScript-only app in modern browsers and in Node.js with just purs, i.e. without having to bring in external JS tools such as webpack. Currently this is achieved by invoking purs compile and then purs bundle (or just purs compile if you only want to run on Node.js).

I think the following is relatively uncontroversial, and is what we generally have settled on in the above linked issues:

  • Compile import declarations in PureScript to ES module import statements, so that import Prelude compiles to something like import * as Prelude from "../Prelude/index.js". Note that import Prelude currently compiles to var Prelude = require("../Prelude/index.js").
  • Compile exports as ES module exports, perhaps export { apply, identity } rather than the current module.exports = { apply: apply, identity: identity }
  • Mangle primes in imports and exports in both regular PureScript modules and in FFI modules, so for example if a PureScript module exports an identifier foo', then the compiled ES module exports it as foo$prime. In particular, note that writing foreign import foo' :: Int in a PureScript file means that the compiler should look for foo$prime in the corresponding FFI module.
  • Support writing FFI modules both as ES modules and with the current CommonJS format, at least for a period of time.

There are some slightly harder questions to be resolved about how we approach FFI files still. The only part of the compiler which needs to care about the format of FFI modules is purs bundle of course, but we should also consider other workflows (i.e. bundling with tools like rollup or webpack, or running directly in Node.js). For example: what file extension should we use? I'd prefer to avoid .mjs and .cjs if we possibly can, as they're only a temporary Node.js thing, and lots of existing tools and scripts will expect to find things under e.g. output/*/*.js. In fact I think if we were using .js files everywhere, including for FFI modules (which could be a mixture of ES modules and CommonJS), the only setup where we might have problems would be apps running in Node.js with no bundling step, and perhaps that's not too much of a problem: if you're using Node.js there's a good chance you are already using webpack or babel or something similar anyway, but if not it's probably not too difficult to set one up, either with one of the JS bundling tools or with purs bundle.

I don't think we should have people use the .mjs extension for FFI files which are written as ES modules; instead, I'd prefer to always use the .js extension and always parse FFI modules in strict mode / module mode, as I expect the proportion of FFI modules which would parse differently in strict mode is very small and possibly even zero (most include "use strict"; directives already). We can then handle the syntax trees of both CommonJS modules and ES modules after parsing.

@hdgarrood

This comment has been minimized.

Copy link
Member Author

commented Apr 28, 2019

I should probably also address the question of whether the compiler should be able to produce both output modes (ESM and CommonJS) via a flag. I'd much prefer to not do that: maintaining both ESM and CommonJS output at once would be a lot more difficult, and most workflows should be able to handle ES module output from the compiler without much adjustment, and possibly without any adjustment at all, since the plan is that purs bundle will still produce the same format.

@kevinbarabash

This comment has been minimized.

Copy link

commented Apr 28, 2019

I'm excited about this. Support for ES6 modules in node is still experimental but it does support the .js extension when the nearest package.json has "type": "module" set, see https://nodejs.org/api/esm.html#esm_enabling.

In addition to mangling primes we should probably mangle keywords and null as well.

@hdgarrood

This comment has been minimized.

Copy link
Member Author

commented Apr 28, 2019

In addition to mangling primes we should probably mangle keywords and null as well.

That's not necessary and I actually think it would be better not to; see discussion in #2558.

@gasi

This comment has been minimized.

Copy link

commented Jun 3, 2019

@twitchard and I spent this weekend’s NYC CoHack Haskell hackathon setting up a test bench for evaluating dead code elimination (DCE) / tree shaking using webpack + PureScript. Please give it a shot: https://github.com/gasi/purescript-dce-benchmark#purescript-dead-code-elimination-proof-of-concept

We used basic TDD to see status quo and where my fork of the compiler gets us.

As many of the referenced issues point out: We are currently blocked by the lack of support for ES2015+ in language-javascript. In gasi#2, I propose we replace language-javascript with the full-fledged tree-sitter parser and its backing by GItHub.

Curious to hear other people’s thoughts. Would love to move the PureScript compiler + ecosystem to ES modules 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.