You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In #4226 I proposed that we remove the PureScript bundler. If we remove the bundler, then we only use language-javascript to verify that FFI declarations have a corresponding export in a .js file of the same name as the PureScript module. This library has been a notable maintenance burden on us over the past few years and I would like to move away from it.
Therefore, I propose that we drop the language-javascript dependency altogether if & when we remove the PureScript bundler.
To do this, we would need to do one of:
No longer verify FFI declarations have a corresponding .js export, instead relying on the immediate errors that would happen on application load if there was no corresponding export
Write our own minimal JavaScript lexer / parser that only looks for top-level declarations and exports for the purposes of validating FFI, but which doesn't attempt to parse all of the JavaScript.
While a good library, it has plenty of bugs in it and it's become a maintenance burden for the compiler team. Removing language-javascript allows us to drop this burden.
Proposal
I am proposing that we remove language-javascript if & when we remove purs bundle.
Without purs bundle, the only reason to have a JavaScript parser is to ensure that FFI declarations have a corresponding implementation. This leaves us with a decision:
No longer verify this, leaving it to be a runtime or bundler error
Write a "simple" JavaScript lexer / parser that only cares about exports
In the first case there is really nothing for us to do besides removing the dependency and corresponding code.
In the second case, we would need to write our own code. This code would need to be able to lex JavaScript literals like strings, brackets, export declarations, identifiers & keywords, and comments, so as to parse out exports. Everything else could be skipped over.
I think we should do (2) and maintain our own simple parser. I think this parser should only support "common sense" JavaScript rather than try to cover all edge cases. FFI files are not "do what you want" JavaScript, after all: they're a bridge between the PureScript and JavaScript worlds, and so I think we can place some restrictions on the JavaScript to make it easier for us to parse the export declarations.
It's not necessary to drop language-javascript in order to drop purs bundle, but dropping purs bundle opens the door to drop language-javascript as well.
Examples
Since this is the removal of a feature rather than an addition, I don't have any examples. However, I'm happy to edit this proposal to add them if y'all have ideas on what you'd like to see!
If purs bundle is removed, I would say dropping language-javascript and not reimplementing the FFI check initially is what we should do. In other words, I think dropping the FFI check initially and then adding that feature back in later would be a wise strategy. Supporting ES modules will already take a lot of work, so this will make it easier to make that transition.
There is a downside to option 1 which I didn’t previously identify, which is that the current check can catch extraneous exports - if the FFI module exports something which the corresponding PureScript module does not foreign import, it’s nice to be warned about that because it’s usually a mistake, and I imagine JS bundlers probably won’t tell you if this is the case. That said, I do prefer option 1 here.
I think this parser should only support "common sense" JavaScript rather than try to cover all edge cases. FFI files are not "do what you want" JavaScript, after all: they're a bridge between the PureScript and JavaScript worlds, and so I think we can place some restrictions on the JavaScript to make it easier for us to parse the export declarations.
This approach makes me a little uneasy, because I think most of the problems people have with purs bundle can be traced back to the fact that we previously took this exact approach. These problems are made worse by the fact that we never documented exactly what we meant by “common sense JavaScript,” which meant that it was usually impossible to answer questions about why purs bundle was misbehaving without referring to the implementation, which sucks. I think we should only roll our own incomplete JS parser if we intend to document exactly what we mean by “common sense JavaScript,” because it certainly won’t be obvious where the boundaries are otherwise (eg. is it “common sense” to allow omitting semicolons?)
I also think we should consider depending on a better maintained JS parser if we really don’t want to lose the ability to parse FFI modules. For example, tree-sitter is probably a decent option, if a bit low-level. I did previously object to using tree-sitter, but I think having purs bundle out of the picture does simplify the matter a lot and I also think I underestimated the value of not having to fix upstream bugs ourselves.
There is a downside to option 1 which I didn’t previously identify, which is that the current check can catch extraneous exports - if the FFI module exports something which the corresponding PureScript module does not foreign import, it’s nice to be warned about that because it’s usually a mistake, and I imagine JS bundlers probably won’t tell you if this is the case. That said, I do prefer option 1 here.
Is that really a problem? To me it looks more like a nice-to-have when I forgot to remove an FFI export that I don't use anymore rather than something that can cause issues.
Also, I checked it with esbuild and it will remove the unused export when bundling.
E.g. in my demo the foreign.js exports `someoneForgotToRemoveMe`
export { helloFromCJS } from "example-cjs";
export { helloFromESM } from "example-esm";
import { helloFromPSESM } from "example-ps-esm"
export const helloFromPSESMviaFFI = helloFromPSESM
export const someoneForgotToRemoveMe = "Unused"
in the index.js it is not re-exported
// Generated by purs version 0.15.0import * as $foreign from "./foreign.js";
import * asEffect_Class from "../Effect.Class/index.js";
import * asEffect_Class_Console from "../Effect.Class.Console/index.js";
import * asExample_ESM from "../Example.ESM/index.js";
var main = /* @__PURE__ */(function __do() {
Effect_Class_Console.log(Effect_Class.monadEffectEffect)("Hello from my project")();
Effect_Class_Console.log(Effect_Class.monadEffectEffect)($foreign.helloFromCJS)();
Effect_Class_Console.log(Effect_Class.monadEffectEffect)($foreign.helloFromESM)();
Effect_Class_Console.log(Effect_Class.monadEffectEffect)(Example_ESM.helloFromPSESM)();
return Effect_Class_Console.log(Effect_Class.monadEffectEffect)($foreign.helloFromPSESMviaFFI + " via FFI")();
});
export {
helloFromCJS,
helloFromESM,
helloFromPSESMviaFFI
} from "./foreign.js";
export {
main
};
It’s not a major problem but it is nice to have, for the same reason that the warning for unused bindings is nice to have. From time to time it can prevent people from being genuinely misled in a way that can lead to bugs or at least wasted time, I think.
Just out of interest - the original issue proposes dropping language-javascript if and when we drop purs bundle. We are dropping purs bundle in 0.15 right? If so, why not drop language-javascript at the same time?
IIRC, language-javascript is not being dropped because it'll be used to fail the build if it successfully parses the file and CJS is detected. If it isn't able to parse the file, no error or warning is emitted.
Activity
JordanMartinez commentedon Jan 17, 2022
If
purs bundleis removed, I would say droppinglanguage-javascriptand not reimplementing the FFI check initially is what we should do. In other words, I think dropping the FFI check initially and then adding that feature back in later would be a wise strategy. Supporting ES modules will already take a lot of work, so this will make it easier to make that transition.sigma-andex commentedon Jan 17, 2022
As per my comment here I think esbuild is at least able to provide this simple sanity check when bundling.
hdgarrood commentedon Jan 17, 2022
There is a downside to option 1 which I didn’t previously identify, which is that the current check can catch extraneous exports - if the FFI module exports something which the corresponding PureScript module does not
foreign import, it’s nice to be warned about that because it’s usually a mistake, and I imagine JS bundlers probably won’t tell you if this is the case. That said, I do prefer option 1 here.This approach makes me a little uneasy, because I think most of the problems people have with
purs bundlecan be traced back to the fact that we previously took this exact approach. These problems are made worse by the fact that we never documented exactly what we meant by “common sense JavaScript,” which meant that it was usually impossible to answer questions about whypurs bundlewas misbehaving without referring to the implementation, which sucks. I think we should only roll our own incomplete JS parser if we intend to document exactly what we mean by “common sense JavaScript,” because it certainly won’t be obvious where the boundaries are otherwise (eg. is it “common sense” to allow omitting semicolons?)I also think we should consider depending on a better maintained JS parser if we really don’t want to lose the ability to parse FFI modules. For example, tree-sitter is probably a decent option, if a bit low-level. I did previously object to using tree-sitter, but I think having
purs bundleout of the picture does simplify the matter a lot and I also think I underestimated the value of not having to fix upstream bugs ourselves.rhendric commentedon Jan 18, 2022
Option 1 makes the most sense to me; I don't have anything to add that @JordanMartinez and @hdgarrood haven't already said well.
sigma-andex commentedon Jan 18, 2022
Is that really a problem? To me it looks more like a nice-to-have when I forgot to remove an FFI export that I don't use anymore rather than something that can cause issues.
Also, I checked it with esbuild and it will remove the unused export when bundling.
E.g. in my demo the foreign.js exports `someoneForgotToRemoveMe`
export { helloFromCJS } from "example-cjs"; export { helloFromESM } from "example-esm"; import { helloFromPSESM } from "example-ps-esm" export const helloFromPSESMviaFFI = helloFromPSESM export const someoneForgotToRemoveMe = "Unused"in the index.js it is not re-exported
hdgarrood commentedon Jan 18, 2022
It’s not a major problem but it is nice to have, for the same reason that the warning for unused bindings is nice to have. From time to time it can prevent people from being genuinely misled in a way that can lead to bugs or at least wasted time, I think.
JordanMartinez commentedon Apr 22, 2022
I believe this issue will be resolved in a future
0.16.0release.hdgarrood commentedon Apr 22, 2022
Just out of interest - the original issue proposes dropping language-javascript if and when we drop purs bundle. We are dropping purs bundle in 0.15 right? If so, why not drop language-javascript at the same time?
JordanMartinez commentedon Apr 22, 2022
IIRC,
language-javascriptis not being dropped because it'll be used to fail the build if it successfully parses the file and CJS is detected. If it isn't able to parse the file, no error or warning is emitted.