-
Notifications
You must be signed in to change notification settings - Fork 571
ES modules #3791
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 #3791
Conversation
|
Note that since primes in foreign modules exports are now required to be escaped, this isn’t usable before #3613 (comment) and enough time for the ecosystem to adapt. |
a5ea971 to
f42d247
Compare
|
Isn't the |
|
That I’ve rebased the PR on top of #3792 and dropped support for escaped primes in identifiers exported from foreign modules. |
|
@kl0tl thanks very much for tackling this, I'm really excited to give it a try! Just to set expectations, I think this is unlikely to get looked at least until v0.14.0 is out. After that, we might want to wait a while before merging this, to give people time to heed the warnings about primes in FFI identifiers, and also because we probably don't want to put breaking releases out too often. We can assess the effect this will have on the ecosystem, though; if it's not too great, that makes it easier to justify releasing ES modules more quickly after v0.14.0. I suspect ES modules won't affect libraries very much, but the migration might be more difficult for some apps, especially if they have complicated bundling setups. |
|
Regarding the ecosystem: I’ve submitted pull requests to all impacted libraries in the official package set, is there anything more I could do to help with the transition? |
|
Thanks! I can’t think of anything really, apart from trying to get lots of people with different setups to try this branch out on their apps and report back on how much effort was required to get them working, so that we can better see how much of an impact this will have on the ecosystem, and see if there’s anything else we can do to make the transition smoother. That should wait until after v0.14.0 is out too though, since people will need to update their apps to v0.14.0 first. |
|
I successfully bundled one of our projects at work with this branch and webpack@4! The resulting bundle weights 18% less and we’ll scrape a few more percents with webpack@5 and I have to upstream a patch to purs-loader but I hadn’t any change to make to the webpack configuration otherwise, beside what was required for testing webpack@5 and I expect bundling the output directory directly with webpack (without purs-loader), rollup or parcel to work as is. I took a look at rollup-plugin-purs but I don’t think it needs anything to work with ES modules. Also I believe this makes |
|
I was curious about how zephyr would perform on the same project so I did more tests today and bundling CJS modules optimized by zephyr still beats webpack@5 over ES modules 😅 Here’s a summary of my results with webpack@4:
and webpack@5:
I’ll compile zephyr against this branch to see how bundling optimized ES modules compares. Edit: I’ve updated the table with ES modules optimized by zephyr. |
|
I picked https://github.com/f-f/purescript-react-basic-todomvc as a simple enough but hopefully not too trivial project to test various bundlers configurations with ES modules. I have successfully tested (with zephyr when possible) so far: |
ES modules are already parsed in strict mode.
| "use strict"; | ||
|
|
||
| var fs = require('fs'); | ||
| import * as fs from 'fs'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be more idiomatic to write import fs from 'fs' or import { readFileSync } from 'fs' here but purs bundle does not support these syntaxes yet.
| var fs = require('fs'); | ||
| import * as fs from 'fs'; | ||
|
|
||
| var source = fs.readFileSync(__filename, 'utf-8'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works because the output of purs bundle is interpreted as a CommonJS module by Node.js but __filename is not available in ES modules, so we’ll have to support CommonJS foreign modules at least until language-javascript can parse import.meta.url.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how much __filename is used across the ecosystem? I expect it will ruin at least one person's day if we ship this with a version of language-javascript which can't yet parse import.meta.url.
.travis.yml
Outdated
| language: node_js | ||
| node_js: | ||
| - "10" | ||
| - "14" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also specify the version of Node.js with a .nvmrc file (https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#specifying-nodejs-versions-using-nvmrc). This would make it easier to enforce the version to use during development (tests won’t pass anymore on <14.13.0).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds fine as long as we aren't requiring people to use nvm. But I think having the tests fail on <14.13.0 is probably fine too. Why do the tests fail? Does the repl still work on older versions of node?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests and the REPL need to be able to import CommonJS foreign modules with ES named imports, but this isn’t possible before v14.13.0:
module Example where
example = "Hello world"> purs repl Example.purs 'bower_components/purescript-*/src/**/*.purs'
Compiling Example
PSCi, version 0.14.0
Type :? for help
> import Example
> example
> node
Welcome to Node.js v14.2.0.
Type ".help" for more information.
> import('./.psci_modules/index.js')
Promise { <pending> }
> (node:28607) UnhandledPromiseRejectionWarning:
export { cons, join, showArrayImpl, showCharImpl, showIntImpl, showNumberImpl, showStringImpl } from "./foreign.cjs";
^^^^
SyntaxError: The requested module './foreign.cjs' does not provide an export named 'cons'
at ModuleJob._instantiate (internal/modules/esm/module_job.js:92:21)
at async ModuleJob.run (internal/modules/esm/module_job.js:107:20)
at async Loader.import (internal/modules/esm/loader.js:179:24)I did not reconsider the version requirement when introducing ES wrappers for CommonJS foreign modules though, but we could import the default export of the CommonJS foreign module in the ES wrapper and re-export properties of the default export with export declarations:
// foreign.cjs
"use strict";
exports.log = function (s) {
return function () {
console.log(s);
};
};// foreign.js
import $foreign from "./foreign.cjs";
var log = $foreign.log;
export { log };This has the advantage of working all the way back to Node.js v12.17.0 (earlier v12 versions need --experimental-modules) which is great news since support for v10 stops at the end of the month!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach sounds good to me 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How far back is --experimental-modules understood?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Back in 2017, Node.js 8.9.0 shipped experimental support for ECMAScript modules, known for their import and export statements. This support was behind the flag --experimental-modules.
https://nodejs.medium.com/announcing-a-new-experimental-modules-1be8d2d6c2ff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We’re really interested in the implementation of --experimental-modules that was shipped with v12.0.0, this flag is of no use to us in v10.0.0 and earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if we can expect this approach to work when supplying --experimental-modules with any version of node.js >= v12.0.0, and if earlier versions of node are EOL, this sounds fine to me. I think we might want to check the version of node we are using in the repl and supply that flag where necessary. Perhaps we should test on node v12 as well as v14 for now as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The REPL and the tests now throw on unsupported Node.js versions (<12.0.0) and provide the --experimental-module flag for versions >=12.0.0 && <12.7.0.
Perhaps we should test on node v12 as well as v14 for now as well?
We don’t rely on the behaviour introduced in v14.13.0 anymore, so testing on v12.0.0 and the latest v12 might be enough?
This reverts commit 4976eee.
|
I merged master and implemented the strategy described in this comment to support Node.js >=12.0.0! |
|
Nice! Just FYI. CI failures are caused by hlint finding a few things. |
|
what is the reason to stop merge this |
|
@cocoddy It's not yet done. I don't recall everything, but one open question is what to do about |
I think we should support only ES modules in FFI for simplicity's sake. This means that CommonJS modules would not be accepted in FFI files, and we'd attempt no automatic conversions or processing to have ES modules and CommonJS work together. As far as the compiler is concerned it's all ES modules in and ES modules out. However, the world doesn't work this way: plenty of browser and Node projects currently rely on CommonJS and we need to be sure that they continue to work even if the compiler only accepts ES modules. Still, I think we're in the clear:
To use a CommonJS module in your FFI, you would need to take one of two routes. The first is where the CommonJS is using named exports: // commonjs uses named exports
// mymodule.js
exports.foo = ...
exports.bar = ...
// es modules FFI file can't do named imports directly; you'd have to import the
// full module then pick off what you need
import mymodule from "mymodule.js"
export const foo = mymodule.foo;
export const bar = mymodule.bar;The second is where the CommonJS is using a default export: // commonjs uses default export
// mymodule.js
module.exports = ...
// es modules
import mymodule from "mymodule.js"
export default mymodule;Either way, it's possible to write your FFI in such a way that you only write an ES module, and if you need to pull in CommonJS dependencies then you rely on either a bundler in the case of the browser (which is required anyway), or rely on Node's module resolutions. Either way we can get away with only supporting ES modules. Further reading: |
|
better using import mymodule from "./mymodule.js"since |
|
Closed in favor of #4232. |
This PR compiles to ES modules instead of CommonJS.
As summarised in #3613:
For instance, given this foreign module:
the following PureScript module:
compiles to:
and can then be bundled with
purs bundle.