scalacenter / scalajs-bundler

https://scalacenter.github.io/scalajs-bundler
Other
237 stars 101 forks source link

BundlingMode.LibraryOnly pollutes a global namespace #282

Open glmars opened 5 years ago

glmars commented 5 years ago

scalajs-bundler enables CommonJSModule module kinde, so scala-js doesn't wrap JS output to IIFE wrapper (please, see here) and in this case we have the same problem as described in https://github.com/scala-js/scala-js/issues/69 (especially on fullOptJSoutput)

easel commented 5 years ago

I'm running into this issue as well. I'm pretty sure when we initially implemented LibraryOnly the fastopt / fullopt outputs were wrapped in an IIFE. @julienrf do you happen to know if the requirement on CommonJSModule is new, or if the upstream behavior has changed?

easel commented 5 years ago

It appears as if the IIFE on commonjs modules disappeared in scala.js 0.6.16 -- see https://github.com/scala-js/scala-js/issues/2785

As a workaround, you can use Application mode with fullOpt, you'll just lose the properly functioning sourcemaps. So far, I have not been able to get application mode with fullopt to work properly with webpack 4, ymmv.

glmars commented 5 years ago

Yes, as workaround we use BundlingMode.LibraryOnly only for fastOptJS:

webpackBundlingMode in fastOptJS := BundlingMode.LibraryOnly()

Do you think we need a new option for the scala-js compiler to preserve IIFE to solve this issue?

easel commented 5 years ago

I think the correct solution is to consume ES modules instead of CommonJS modules from scala.js, but I could be totally wrong on that. @sjrd would probably know if that's a reasonable direction to pursue.

As a follow up, I did get full application bundling working with Webpack 4 by eliminating webpack minification process. It might be possible to "tune" it instead of turning it off entirely but I haven't pursued that yet.

sjrd commented 5 years ago

CommonJS modules do not need to be IIFE'ed. Their specification says that they are semantically scoped in their own scope. If a tool breaks that semantic, it is the tool's fault.

ES modules go even further: they must not be IIFE'ed, because export and import must be at the top-level of the module.

There's nothing Scala.js core can/should do here.

easel commented 5 years ago

@sjrd is there some other path to getting an isolated module out of Scala.js? Re wrapping the entire file with the library ends up destroying the sourcemaps let alone the processing delay.

sjrd commented 5 years ago

A CommonJS module is isolated. So is an ES module. It's Webpack or its configuration that is destroying the isolation. Scala.js can't do anything.

easel commented 5 years ago

@sjrd webpack isn't involved here. In BundlingMode.LibraryOnly we extract the dependencies from the Scala.js output and use them to trick webpack into creating a bundle of all the dependencies, then load the Scala.js output file directly into the browser via a script tag.

The issue we're trying to work around here is that the Scala.js output file isn't getting loaded into the browser in an isolated context. Is there no Scala.js output mode that's designed for direct loading into a browser via a script tag?

easel commented 5 years ago

After some further investigation, I think I have the path forward. @sjrd I'd appreciate your feedback on the approach.

Net-net ES2015 modules are here. Scala.js can output them, and if we can import them directly into modern browsers with <script type="module" ... >. We'll have to adjust some stuff on the bundler side of things to make the webpack library available as an ES module, but assuming we can figure that part of things out we'll be properly encapsulated and not have to rely on nasty hacks like

<script language="JavaScript">
var exports = window;
exports.require = window["ScalaJSBundlerLibrary"].require;
</script>

Make sense?

easel commented 5 years ago

After some further investigation, it doesn't look like webpack can emit an ES2015 module so we'd have to use rollup to create the library bundle. https://github.com/webpack/webpack/issues/2933

dispalt commented 5 years ago

I think I ran into this, and it was because I was including the foo-fastOpt.js file instead of the file processed by webpack. I recommend looking at https://github.com/shadaj/create-react-scala-app.g8/tree/master/src/main/g8 it basically helped me out by looking at @shadaj's configuration setup.

sjrd commented 5 years ago

The issue we're trying to work around here is that the Scala.js output file isn't getting loaded into the browser in an isolated context. Is there no Scala.js output mode that's designed for direct loading into a browser via a script tag?

There is. It's the default ModuleKind.NoModule. But that does not allow to use @JSImport, only global variables. The library-mode seems to be a hack that wants to load a CommonJS module as a script, and as such it breaks the isolation spec'ed for CommonJS modules.

Using ES 2015 modules could solve your issues, as they are natively supported modules with the appropriate isolation.

easel commented 5 years ago

Thanks @sjrd, that's what I suspected. The trick with going direct ES-to-the-browser is going to be automating making the dependency tree available to the browser as ES modules -- I'm not even sure how (or if) it's being done in anger by the ES community. My first attempt at bundling with rollup ran into snags due to invalid common-js code in my dependency tree so it's not looking trivial.

@dispalt that's an interesting workaround. It looks like they have replaced the webpack config for fastopt with one that has been modified to load the -fastopt file with a "noparse" flag. My assumption is that it speeds up the webpack process considerably, probably to the point of being almost no overhead when using dev server. In a play or sbt plugin workflow it will still invoke webpack though so it will never be quite as fast as directly including the -fastopt file.

All that being said, this problem is not nearly as bad with -fastopt. Most (all?) of the global symbols are long and have "$" or "scalajs" in the name so they don't conflict much. Things really blow up when you try to use the optimized file where they've all turned into one and two character variable names.

In the interim, it would probably be good to document that it's not recommended to use LibraryOnly with FullOpt.

dispalt commented 5 years ago

His workflow is completely dependent on using webpackDevServer. I use play and still use webpackDevServer, still get auto compiling, its really complicated though because of how much play assumes (which is sort of it's point, in all fairness).