-
Notifications
You must be signed in to change notification settings - Fork 396
Enable Closure when emitting an ES module. #3893
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
Comments
Yes, this is a known limitation of the version of Closure that we use. A PR that upgrades GCC and manages to make this work would be welcome. |
@sjrd does scala.js 1.X have the same issue? |
Yes, it does. |
I thought this commit 5c8f049 has already upgraded GCC to a very recent version and that version has ES6 support already? |
There is support for ES 6 (official name ES 2015) except modules. We do enable GCC for ES 2015 code, which is even the default in Scala.js 1.x. We disable GCC when emitting an ES module. |
Thanks for the explanation. |
An update on this: I have a branch with the closure AST transformers built to support module related constructors ( I have not yet found a way to disable this behavior. |
In practical terms, the problem is that we cannot configure this snippet in What we would want is a |
I think the discussion at google/closure-compiler#2770 means that it's simply not supported by Closure. |
Ah, yes, indeed. |
A quick update on this: I have tried to reach out to the closure folks Google-internally to see if they would be willing to accept a contribution that enables this. The higher level idea was to add a PassthroughModuleResolver (extends ModuleResolver) which simply returns the moduleAddress as-is. Add a new value PASSTHROUGH to ResolutionMode which uses the new resolver. Unfortunately I didn't get a reply back so far. @sjrd do you have contacts we could reach out to? It seems at this point we're pretty much blocked on this, unless we are willing to expose all the relevant options in the linker config and force users to set them according to their environment. |
I'm afraid I don't any direct contact in the Closure team. However, I've a good experience reporting issues and sending PRs to fix things. In this case it's a feature rather than a bug fix, so the story might be different, but I think there's a good chance they would consider it. |
Unfortunately, the closure team has confirmed (Google-internally) that they cannot accept a contribution of this form. Mainly because it is just too much of a stretch from how GCC works. For Scala.js, this means we either need to try and work around this or close this as wontfix. |
The only thing I can think about is replace imports with a function call to a "magic" function, then run GCC, then replace the function calls again with imports. In the process, we of course somehow need to make sure GCC doesn't optimize these "too much". Not sure how feasible this is :-/ |
That workaround could work for dynamic imports, but what about static |
I did mean this for static imports, but then probably, indeed, the likelihood of GCC optimizing it is too high. I meant something like: import { a as b } from "foo.js";
// translate to
const b = _my_magic_function("a", "foo.js"); My hope was that we can get GCC to rename the local identifiers of what is imported. |
Hum... It could work. As an external reference, GCC must keep the function call for sure, and give the right values as parameters. But it could move the call around, or separate the literals from the call, etc. |
From gitter: google/closure-compiler#2770 (comment) looks like GCC might actually support this now. |
OK. So a bit of clarification: What Scala.js needs is support for what GCC calls "Foreign Modules" (modules that are not known at the time of compilation). Scala.js needs this for both dynamic and static imports. The issue in question is about dynamic imports ( |
Nearly a year later, a polite bump on this :) I know the requests just don't stop, sorry! Thanks to I believe lack of GCC support for ESModules is (hopefully) the last sore spot, specifically relating to production deploys. In my experience GCC makes significant optimizations to the generated JS that IIUC are impossible to replicate after-the-fact (e.g. see http4s/http4s-dom#119 (comment)). Currently the only way to use GCC in production is to build an alternative bundling pipeline that is based on CommonJS, alongside the ESModule-based development bundling pipeline. Thanks :) |
There's apparently this thing now in GCC: |
Setting that option still seems to make it fail:
For posterity, here's how you translate case ImportNamespace(binding, from) =>
new Node(Token.IMPORT,
new Node(Token.EMPTY),
setNodePosition(Node.newString(Token.IMPORT_STAR, binding.name), binding.pos.orElse(pos)),
transformExpr(from),
) |
@gzm0 I've tried playing with it a bit. To be honest I'm not sure if I know what I'm doing =); nevertheless, I managed to get the This error happens when we try to do Changing the import to be There is also the options.setModuleResolutionMode(ResolutionMode.NODE) It will also want the I also tried another option: Transpiling Dynamic Import options.setDynamicImportAlias("__scala_js_import__") Which is supposed to make closure "support" dynamic imports even when the output is lower than ECMASCRIPT_2020:
But it didn't work for me - it complains:
I think this is why: google/closure-compiler#3941 Not sure if this is helpful at all. I hope it is :) P.S. When running this: import com.google.javascript.jscomp.{
SourceFile => ClosureSource,
Compiler => ClosureCompiler,
CompilerOptions => ClosureOptions,
_
}
val options = new ClosureOptions
options.setPrettyPrint(true)
CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(options)
options.setLanguage(ClosureOptions.LanguageMode.ECMASCRIPT_2015)
options.setWarningLevel(DiagnosticGroups.GLOBAL_THIS, CheckLevel.OFF)
options.setWarningLevel(DiagnosticGroups.DUPLICATE_VARS, CheckLevel.OFF)
options.setWarningLevel(DiagnosticGroups.CHECK_REGEXP, CheckLevel.OFF)
options.setWarningLevel(DiagnosticGroups.CHECK_TYPES, CheckLevel.OFF)
options.setWarningLevel(DiagnosticGroups.CHECK_USELESS_CODE, CheckLevel.OFF)
val ScalaJSExterns =
"""
function callMe(x) {};
"""
val chunk = new JSChunk("chunk1")
chunk.add(
ClosureSource.builder().withPath("test.js").withContent("""
import { x as y } from './foo.js'
const z = y();
callMe(z);
"""
).build())
chunk.add(
ClosureSource.builder().withPath("foo.js").withContent("""
export const x = () => "Hi!"
"""
).build())
compiler.compileModules(
Arrays.asList(ClosureSource.fromCode("ScalaJSExterns.js", ScalaJSExterns)),
Arrays.asList(chunk),
options
)
val source = compiler.toSource
println(source) it prints:
P.P.S. I also tried _.withModuleKind(ModuleKind.ESModule)
.withESFeatures(_.withESVersion(ESVersion.ES5_1))
.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("myapp")))
.withClosureCompiler(true) Had to add this to the
I think it worked (although I messed something and it looks like Also when I tried
|
What is the current status? Does it mean fullOpt / fullLink is limited with Could the limitation be documented, perhaps at https://www.scala-js.org/doc/project/module.html? As I read it now, there is no mention of any limitations at all. |
The status is still the same as when this issue was opened. Closure is disabled with |
Can some documentation / warning be placed the the module.html page then? Background: As a result of dropped plain script support (mrdoob/three.js#25435) I am now changing the way three.js is included in my application . I was considering whether to include it now as a common module or ES module, and given ES modules are easier to use in the browser and given even Common JS module script was intended to be removed originally (mrdoob/three.js#25341) I was favoring ES modules, but having |
That seems like a fair ask. Feel free to open a PR or file an issue against scala-js-website. |
@gzm0 @sjrd How feasible would it be for us to maintain our own downstream fork. Similar to how the sbt team ended up doing with ivy if I'm not mistaken. I'm not familiar with the internals off GCC but if the areas we're touching are pretty stable already upstream then it should mainly involve merging in future upstream changes. I'm sure a few people (including myself) are happy to dedicate time towards that if need be. |
We haven't studied the feasibility of having our own fork. The fork itself is one thing, but most importantly we would need to implement the support in the first place. It's unclear that doing that would be any less effort than implementing global property name minimization in our toolchain instead. |
IMO the idea of forking or using more low level APIs of GCC is an interesting one worth investigating. But I agree with sjrd that we have no chance to guess the maintenance effort before doing the work of figuring out how such an integration could look in practice. |
I've had two ideas:
|
I've tried to use chunks. It also seems to be a dead-end for general case (but might be the way to go for js.dynamicImport support):
As a result, I still don't see any other practical way that 1. rewriting imports to function calls before GoCC is called and 2. rewriting function calls to imports afterwards. It works well** in my hacky (regex-based) PoC and I believe it would work well in the general case if regex hacks are replaced by AST transformations. The reason why I am not going this way is that it probably wouldn't be accepted. *) One might be tempted to use |
If I recall correctly, aside from property name minification, wasn't GCC also responsible for some optimizations involving uh... inlining / unwrapping / de-abstracting function calls? Or something like that? I seem to recall seeing an example of using a method like |
That's all done by the Scala.js optimizer. GCC has nothing to do with it. |
Not really, given minimal project of 4 commonjs modules using println and scala collections: Also the same code without modules: Therefore I guess GCC is twice as good as current scalajs linker. edit: by removing unused exports, i managed to reduce archive size to 55KB (new optimizer + gcc in simple mode + gzip case). Should I create a ticket for that? |
When the ES module option is active:
the js file obtained trough
fullOptJS
isn't minified andproject-opt.js
is only 3% smaller in size thanproject-fast-opt.js
The text was updated successfully, but these errors were encountered: