vmunier/sbt-web-scalajs

sbt-web-scalajs is breaking idempotent SBT contract

mdedetrich opened this issue · 4 comments

Quoting from @jroper from sbt/sbt#3578 (comment)

I've just had a look at sbt-web-scalajs, and I see what the underlying problem is here. It's looking at what command has been run on the CLI to establish whether to run the fast or full JS command. Doing this is really working against the tool, sbt tasks should be idempotent, invoke it once, invoke it 100 times, doesn't matter the context, it should always return the same result if the files its processing haven't changed. This is a fundamental assumption of sbt, and when you break that assumption, such as the way sbt-web-scalajs does, then things start to break in ways that are really difficult to fix.

Fixing this would probably be quite involved, I'm not that experienced with scala.js to know exactly how it works, but what you'd probably need to do is have it configured to run the fast process initially as a source generator, then produce a directory that all assets are copied to correctly prefixed that can be added to your development classpath (this is something that sbt-web could provide, but I don't think it currently does provide, however, Play framework provides exactly this in its development mode, and that directory is isolated and not included in the final production artifact). Then, you'd add a pipeline stage that does the full build, possibly dropping the dev compiled assets from the pipeline if they are no longer applicable. This would be the idiomatic sbt way of doing things that would work well with other tooling.

Hi,
fastOptJs should be run during development and fullOptJs for production.
sbt-web-scalajs has an internal isDevMode task to know wether we should run fastOptJs or fullOptJs, i.e. if the user executes a dev command (run, compile, full list below), then sbt-web-scalajs considers that we are in Dev mode, hence we should execute fastOptJs.

devCommands in scalaJSPipeline := Seq("run", "compile", "re-start", "reStart", "runAll"),

This is of course fragile because we may want to execute fastOptJs in other scenarios but it provides a nice default out of the box, without requiring the user to do any configuration.
However, note that this behaviour can easily be customised on your end; you can edit your build.sbt and add isDevMode in scalaJSPipeline := true to explicitly say that you are in dev mode.
Before publishing for production, you would then have to change isDevMode in scalaJSPipeline to false (either manually editing your build.sbt or calling set isDevMode in scalaJSPipeline := false in the sbt shell).

Then, you'd add a pipeline stage that does the full build, possibly dropping the dev compiled assets from the pipeline if they are no longer applicable.

That was the first solution I tried, but even though I removed the dev files inside the pipeline task, I was always able to find them in the produced jar, so I could not get rid of the dev compiled assets for production somehow. That's a problem because it's okay to have Scala and sourcemap files when developing but you don't want them in your production jar most of the time.

Isn't a better way of finding out if you are in dev mode (or not) is to just simply only execute fullOptJs when doing either a packageBin in Universal or the deliver/deliverLocal/publish/publishLocal tasks?

It sounds like this is quite brittle behaviour, and typically speaking production in SBT/maven/scala/jar land means creating the actual package itself

nafg commented

I agree that prodCommands makes more sense than devCommands

sbt-web-scalajs v1.1.0 now relies on scalaJSStage to know whether fastOptJS or fullOptJS should be run.