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
Support ES6 and beyond? #2207
Comments
I think we do need to know where any given export assignment statement ends for DCE in |
@hdgarrood I was thinking that DCE (and possibly all of psc-bundle) might be unnecessary after #2204 and related. We already have tools that do this for sufficiently declarative JS. |
Good point. Did you perhaps mean to link to #2206 though? |
Also, I am 90% sure that you are indeed correct - that in |
@hdgarrood Yes, "and related" was meant to include #2206 and any similar issues that are spawned from #2204.
That's about how sure I am as well. Confirmation from @garyb would be helpful here.
Well I have a significant amount of experience working on JS parsers, so I think I have a pretty good idea how difficult a standalone JS lexer will be, but I'm not sure how I can convey that to you. Are you looking for number of lines of code or something? |
I'm also only 90% sure! @hdgarrood wasn't it you who added the FFI parsing to check exports? I didn't think we did any FFI parsing before that, aside from the ad-hoc find
|
It was me who added it, yes, and it was the only part of @michaelficarra I was aware of your experience :) I guess I'm asking how easily someone else might be able to read through the code and fix a bug, were we to find one. If we go for ES6 modules and try to reuse existing JS tooling as much as possible, which I think is very probably going to happen at some point anyway, we might also consider dropping this feature from |
For instance, if you were running a DCE tool, that would probably catch this anyway, right? |
("this" being an error where you |
@hdgarrood That is a very good point. It does seem like an awful lot of effort to go through to do something that an external tool is probably going to be doing anyway. |
The recent PR #2210 gives a reason to continue finding FFI definitions. psc-ide-server can return the definition location of a value. This is helpful for IDE jump-to-definition functionality. |
With a new lexer that just identifies exports? It's a bummer we can't use Babel or TS for FFI because of |
Yes. What's wrong with that? That's what I've proposed in this issue. |
Just confirming 💓 |
Getting back to the matters brought up by #2204 and #1206 (this issue is linked as a continuation of them), I would say that generating modern JS and relying on the tools from JS universe (webpack, babel, etc.) may look like a good idea, but in practice it isn't that good. Both webpack and babel aren't really reliable, neither they are fast. We have a huge project that takes more than 4 minutes to build. We are systematically suffering from all kinds of the nasty bugs coming from both webpack and babel or one of their dependencies. Every npm-shrinkwrap.json generation for a dependency update is a risk of destabilizing everything because a tiny bug in any of the myriads of dependencies and sub-dependencies used by webpack/babel can break something. With npm, there is no good way to prevent these dependencies from switching to higher (and probably broken) versions even if you are updating just some unrelated stuff in your package.json Maybe webpack 2 is more stable, I don't know. But I doubt it is because it's really hard to write reliable software in JS and there is no good quality control for all those dependencies. Both webpack and babel have a lot of code full of that JS-style interlinked data bags being passed around, especially where it relates to plugins and loaders. Modern JS is something that cannot work without further processing, and unfortunately there is no really robust tools for this. With all my respect to webpack and babel I'd try to avoid them if I could. However, my current project is JS mostly and I cannot do it anyway. Direct ES5 generation is faster and more robust than ES6 generation with further processing by babel or any other transpiler. And it's much easier than writing our own robust ES6 to ES5 transpiler tool. As for bundling, @wegry shared his experience in #2204. We have very similar case (huge JS code base with growing but still relatively small PureScript part). But we have very different results. In our case, using purs-loader with PureScript bundling and DCE reduces resulting code size very well comparing to using purs-loader without bundling. The difference is that we avoid depending on JS code from PureScript as much as we can. This allows PureScript bundler to optimize PureScript part and webpack to optimize JS modules independently. As it was already said, JS tree shaking will hardly be as effective as PureScript-aware DCE (or will require "tree shaking-optimized" code generation which can be suboptimal in many other ways). Modern JS is good, but it's also good to be able to avoid depending on webpack/babel. From the practical standpoint, it will be very nice to preserve support for ES5 + CommonJS code generation and PureScript-aware bundling we currently have. |
I agree with @dimsmol I also do JavaScript development with Webpack/Babel/Rollup. They work well enough for JS code, but I have had issues with breakage. On a fundamental level, a PureScript bundler will be able to perform optimizations that a JS bundler will never be able to do. This is because of PureScript's static + pure behavior, compared to JavaScript's dynamic + impure behavior. In addition, a PureScript bundler has a much higher level understanding of the code (e.g. function type specialization, inlining, currying, etc.) PureScript will need to support ECMAScript 6... just not right now. When ES6 becomes more mainstream (enabled by default in Node.js and/or modern browsers), then we will need to migrate over. But for now, I think the status quo is okay. You can use On the other hand, I think it would be good to grab some low-hanging fruit: it would be nice if PureScript would parse ES6 in FFI files, so that it would work with tools like In other words, rather than doing full ES6->ES5 transpilation, it would simply parse the |
I have the following concrete proposal:
Step 1 shouldn't take that long, it just requires somebody to do the work. Steps 2 and 3 will probably take a few years, but it doesn't require much work from the PureScript compiler, it's just a general community transition. |
Sorry for the wall of text.
@dimsol Correct me if I'm wrong, but the optimizations #1206 covered (undocumented elsewhere?) in 0.9.x
As covered in #1206, there doesn't seem to be enough there currently to warrant usage in projects that happen to mix JS and PS. I understand that the PS community finds JS and its tooling as whole undesirable. I've just been pushing for not crowding out alternate implementations and want no part in that debate. For us, at least, Decurrying is the only optimization my team currently dearly misses, because the other two issues are mostly covered by Webpack 2 + postprocessing. Decurrying could potentially be patched up with eval/apply instead of push/enter in psc-@ekmett might be able to answer questions there-but that's probably it's whole own issue. The biggest drawbacks to using
In response to @Pauan's post:
Before my team used ES modules, we used AMD and obviously CJS for PS. To get our My team, in an enterprise environment, doesn't control the entire page. So periodically, someone would change a dependency version and stomp on our library with a different version or API in the global namespace. If someone has to use a fallen JS dependency that hangs variables on the window (jQuery, Lodash, etc.), how does psc-bundle account for that? Webpack allowed us to, at the very least, shield our internal dependencies from the global namespace-common libraries like jQuery and Lodash. In addition to bundling and minifing, we also use it for placing and versioning svgs, fonts, images, LESS, etc. It's not really an apples to apples comparison between psc-bundle and webpack when those things are considered. Juche is fine but it's not free. And I'm not advocating breaking up the old psc, on account of only supporting valid ES identifiers as of 0.9, doesn't have to change anything to use the loader I linked to very recently in #1206 for tree-shaking. No transpilation required @dimsmol. This has the added benefit of as @Pauan mentioned:
|
@wegry My proposal was about migrating all PureScript projects to ES6, regardless of whether they use Webpack, Rollup, psc-bundle, etc. Therefore it is very gentle and makes the minimal number of changes while retaining backwards compatibility. I agree that Webpack is amazing when you want to seamlessly tie into the rest of the JS ecosystem, including all the wacky ES6/CommonJS/RequireJS/npm/global stuff. I also agree that PureScript should have good support for Webpack as an alternative (not replacement) to In my opinion, good support for alternative workflows (e.g. Webpack) is a separate (but very important!) issue. However, there may be some overlap between the two issues. I tried out your Webpack loader, but I had to make some minor tweaks to get it to work right: https://gist.github.com/Pauan/53b1af11a71d6ebac8defafad070b3fe It's obviously very hacky, but it does work: in my testing, it was able to remove all of the Despite that, Webpack2 still didn't do a very good job of tree shaking. From what I understand, Webpack2 doesn't do any actual treeshaking itself, instead it relies on UglifyJS to remove dead code. But UglifyJS is very conservative in the code that it removes, so the end result is a lot of bloat. In addition to that, Webpack2 still wraps each ES6 module in a separate function, which also prevents tree shaking. I will try to write a Rollup plugin and then we can see if Rollup does a better job of tree shaking compared to Webpack2. |
Okay, I created a repository which tests the different ways of doing tree shaking with PureScript: https://github.com/Pauan/purescript-tree-shaking It compiles a program which is only slightly more complex than "Hello world". It imports JS files into PureScript, imports JS files into JS files, and imports PureScript files into JS. I found that the best way to make it work is to use The If anybody has any suggestions for improvements, you can either tell me or submit a pull request. P.S. This feels a bit off-topic, so perhaps we should move this discussion to a new issue? Correctness
Size
I suspect that psc-bundle's dead code elimination is more aggressive than Rollup, probably due to purity. Webpack's output is super bad, which makes me think that I'm doing something wrong. If anybody has any ideas, please do share. |
@wegry thanks for explanation! Speaking about your particular case, as I understand, everything already works via not using psc-bundle and do all the bundling with webpack instead. Do you have any problems with your workflow that must be addressed in PureScript tooling? I guess, in your scenario you could benefit from PureScript DCE if it could work on it's own without bundling. It still won't be at it's full power for JS code importing PureScript modules because in this case it works on module level, but it can remove a lot of stuff within PureScript code. Tracking which PureScript modules are required can be tricky, purs-loader actually does it incorrectly for now, but it can potentially be solved. If we could understand what exact parts of PureScript modules are required, PureScript DCE could work with all its power even for imports from JS code. I propose to consider the following:
For the drawbacks of psc-bundle you've mentioned, I totally agree that it would be nice to address the problems with global variables leakage and large module name strings. At the same time I don't think it's a task for psc-bundler to do JS tree-shaking or support wide variety of JS modules. It's better to use JS tools like webpack if you need such things. For async loading, it's a nice feature to have, but I'm ok with psc-bundler not having it (again, one can use webpack or some other JS bundler if really need it). I like the proposition by @Pauan to incorporate ES6 modules support. In future we will need them anyway. I also like an idea to start using other ES6 features in compilation results, but not by default. Maybe it worth to have compilation flag(s) for this. Eventually we will need to use them all, but browsers and node.js must be ready first. One more thing I want to address is psc's inability to understand non-CJS exports when checking FFI modules. I don't think there should be a support for wide variety of JS modules, but having support for ES6 would be nice because it's a future anyway. For the other kinds of modules the problem can be solved by making this check optional (allow to turn it off via compiler flag). The integrity in these cases must be checked by JS tooling (if possible). The common idea is to make PureScript tools work best with the PureScript stuff and not care about JS specifics too much, but at the same time to not block the way for JS tools one may want to use over resulting JS code. |
Trying https://jsperf.com/curried-vs-uncurried-body-overhead in
The duplicate body case was faster in every single case, except for Safari where it was a tie. Invoking functions has an overhead, tracing VM or not. |
I wanted to try the plugin on SlamData for a good real world example of how many curried vs uncurried functions there are, but unfortunately I wasn't able to. I spent about half an hour hacking case after case that it was complaining about but couldn't really afford to spend any more time on it. I'll open some issues when I get a chance. Mostly it was complaints about "invalid module" though, with a few cases of "duplicate export" ( |
@garyb My opinion about the priority of perf work seems to be heterodox, and performance has historically been a sensitive issue. Please note I have no desire for a repeat of the tenor of #1206. That said, type-classes are indeed tremendously slow in our experience. The problem rears its head in JSON deserialization. We believed that inlining would save us a lot agony in that regard, but our pragma proposal looks to have died in committee. The last time configuration came up, specifically concerning ES module exports and imports, it was shot down as fragmenting the build chain. That portion could easily be in psc (and @texastoland had already done the work at one point). It's the whole reason this particular transpiler was started in the first place. And here we are with a JS based optimizer pass.
Having blessed support of JS exports isn't a common use case because the blessed toolchain cannot currently support it. Both @Pauan and I have been arguing for better JS support here and elsewhere because many real world projects are not green field and have to accommodate JS. At the very least, it would help with adoption of the language. And yes, if idiomatic exports aren't a thing still, I agree with you that the perf penalty of decurrying is not worth it. The difference in uncurried v. curried perf at least in iOS Firefox was 3% (worth it in my opinion for idiomatic exports). On the desktop, I'm seeing a diff of 8%, which is significant. In the event that exports are not decurried, duplicating code or paying a >5% in runtime both seem to be unacceptable at least to me. |
@garyb Function calls have zero overhead when the function is optimized/traced/inlined/etc. Benchmarking and performance is hard, especially with advanced JIT engines. When I said "extensive benchmarking" I mean benchmarks of actual programs, not microbenchmarks (which are unreliable for predicting application performance). Please do file bug reports. |
I think I would slightly prefer de-duplication as a default, and code duplication (inlining) as an option: some arguments I can think of are
That would really be great ! |
I don't have time to post a full reply right now, but let me just summarize: I'm not thinking about optimization yet, but I plan to think about it seriously after 1.0. Right now, I'm focusing on 1.0, and anything not already prioritized will be put in the "Approved" milestone at best, unless there is a really good reason for it to go into 1.0. For 1.0, I would prefer that we cut DCE from I have no problem with supporting ES6 modules before 1.0, but I don't want to support two modes of code generation, and we would need to come up with a list of work to be done, since a change like that would mean changes to |
@paf31 I just wanted to add that I think it would be a huge shame if we released 1.0 while generating CommonJS-based modules instead of real ECMAScript modules. In my opinion, it is an absolute must for the 1.0 release. |
I agree, and if we cut features from |
I would just like to point out that So by compiling your PureScript library/application with In fact, it's possible to write a program which will take in CommonJS source code and spit back ES6 source code (which is what @paf31 From what I understand, the only reason for |
@Pauan Inside I think I'd be happy to drop those checks from |
@hdgarrood Here is the error that
Here is the warning that Rollup gives:
It's a lot messier (because the message includes a few lines of actual source code), but it does tell you the name of the function ( |
Nice :) |
I just pushed out version 1.0.4 of
This will replace the CommonJS file |
While I'd obvs much rather see the compiler itself output ES6 natively, I'd say it's time to start playing around with integrating |
@bodil Of course, but there will be a transition period where PureScript libraries need to convert their FFI files from CommonJS to ES6, that's what Also, as a side note, |
@Pauan I've just opened a selection of issues that I encounter when trying to use the plugin on SlamData, although I've only just realised some of them are warnings only, and shouldn't block compilation (I think?). On Windows I just see squiggle-boxes rather than the icons or whatever it is that is supposed to be next to the errors, so it's not easy to tell which are fatal. |
@garyb Thanks, I'll address those now. If the compilation succeeded, then they were warnings, if the compilation failed then it was an error. Rollup seems to use |
@Pauan that's why I can't tell, I never got the compilation to succeed 😉 and yeah, those do not show up as they're supposed to: I'm assuming that it halts on a fatal error now though, so won't open issues for anything above the last message that I encounter in the future (unless the warning seems incorrect somehow). |
@garyb Ah yeah, I can see how that's confusing. I'm pretty sure that fatal errors cause it to immediately stop, so anything other than the bottom-most error is a warning. |
@garyb Would you be able to paste the output that generates the dynamic require warning? Webpack has a similar constraint on static and verifiable imports (and exports). If One of the major pitfalls of transpilation as a post processing step as opposed to being baked into psc is that name mangling is pretty tricky. With our internal transpilation method (which has been in use for some time now) we recently found a bug where exports that would be invalid ES6+ were affecting object properties of the same name ( e.g.
becoming
Edit to include the link to the issue @garyb mentioned. |
@wegry see the issues on the plugin repo. Those dynamic things are just warnings, so it's fine - they occur in a couple of modules when there is behaviour that needs patching so a module works in node as well as the browser. |
@wegry This is the warning with the dynamic require, but it's not actually a problem, it should work fine with There is a more serious issue with
|
I think it might be time to close this and split it up into individual issues, as it's drifted a long way from the original proposal. We already have #2545; are there any other items that we need to track? edit: I suppose we will want an issue for generating ES6 modules instead of CommonJS in psc, although we're not quite ready to actually start doing that yet. |
@hdgarrood I created #2574 and #2575 |
I think it's probably safe to assume that covers it then, so I'm going to close this. Please do add a comment if there's anything else I've missed. |
Inspired by #2204 (which I strongly support), I think we can avoid waiting for language-javascript to support modern JavaScript features by instead implementing a simple lexer. I don't believe we actually require the AST that language-javascript gives us. If we just look for the token sequences that indicate an export, we can infer that the name is exported regardless of context (thus not requiring a full parse). And if I'm not mistaken, the only reason we parse the FFI is to confirm that the required names are exported.
I'd be willing to do this work. Thoughts? Concerns?
edit: I forgot to mention, but this also allows adoption of future ECMAScript features in FFI without explicit support in PureScript, language-javascript, or some dependency. For instance, TypeScript/Flow annotations would pass through just fine.
The text was updated successfully, but these errors were encountered: