ptrdom / scalajs-vite

Bundles Scala.js projects and their npm dependencies with Vite
MIT License
14 stars 1 forks source link

Support for testing #39

Closed johnhungerford closed 5 months ago

johnhungerford commented 8 months ago

It would be great if you could use scalajs-vite to bundle and run tests. The basic approach is pretty straightforward: use the Test / fastLinkJS outputs as the bundler input and pass the output to Test / jsEnvInput.

One edge case here that is easily missed is that while tests might need to have access to both node_modules and other non-Scala.js sources, those non-Scala.js source might in turn need access to Compile / xLinkJs outputs. This means that the build context where tests are bundled should also include Compile Scala.js outputs.

For context: I am migrating a project from TS to Scala.js, and I'm reimplementing components piecemeal. I export those Scala.js components using JSExportTopLevel("SomeComponent", "someModule"), and I update the TS components that depend on them to import as: import { someComponent } from 'scalajs:someModule.js';. If I want to run Scala.js tests that include those TS components, I need to also have the Scala.js outputs available.

ptrdom commented 8 months ago

Aha, interesting. IIUC, there is more than one problem described here:

It would be great if you could use scalajs-vite to bundle and run tests. The basic approach is pretty straightforward: use the Test / fastLinkJS outputs as the bundler input and pass the output to Test / jsEnvInput.

I was playing around with this implementation here - https://github.com/ptrdom/scalajs-vite/tree/test-bundling. I think it covers the basic use cases, but I might be missing something. Would it be a good solution?

One edge case here that is easily missed is that while tests might need to have access to both node_modules and other non-Scala.js sources, those non-Scala.js source might in turn need access to Compile / xLinkJs outputs. This means that the build context where tests are bundled should also include Compile Scala.js outputs.

I assume that these exports need to be bundled, otherwise this would be outside of this project's scope. I think you are describing library bundling, this should be achievable now with custom Vite config. Would that work for you?

johnhungerford commented 8 months ago

I was playing around with this implementation here - https://github.com/ptrdom/scalajs-vite/tree/test-bundling. I think it covers the basic use cases, but I might be missing something. Would it be a good solution?

I think that's what I have in mind, although I'm still not 100% on how the scoping works. Do you have separate target directories for Test and Compile?

I assume that these exports need to be bundled, otherwise this would be outside of this project's scope. I think you are describing library bundling, this should be achievable now with custom Vite config. Would that work for you?

Yes I'm thinking in the case of Scala.js exports being bundled, but this need not mean using vite in library mode (although it would be good to support that use case). Here are three cases in which a runnable (non-library) Scala.js project would need to support resolving imports from Scala.js:

  1. Your application entrypoint is JavaScript. Example: I have a JS react app, and one or more components of it are in Scala.js. I have some App.jsx file that imports 'scalajs:ScalaJSComponent1.js' and 'scalajs:ScalaJSComponent2.js'. I expect vite to bundle App.jsx with Scala.js artifacts produced by fullLinkJS (which I expect to include ScalaJSComponent1.js and ScalaJSComponent2.js)
  2. Your application entrypoint is Scala.js, but it depends on some non-Scala.js sources. These sources in turn depend on exports from Scala.js. Imagine, e.g., fullLinkJS produces main.js (runnable entrypoint), and utils.js (module generated by a call to JSExportTopLevel("someUtility", "utils")). main.js imports a JS module someJsModule.js, and someJsModule.js in turns imports 'scalajs:utils.js'. These imports all need to be resolvable for vite to generate a runnable bundle from main.js.
  3. As a corollary to (2), now image you have a test in your Scala.js source that includes the dependency to someJsModule.js. For this test to run, the bundler is going to need access not only to the compiled test code and the JS sources, but also the compiled Scala.js application code, since theTest / fastLinkJS outputs will not necessarily include the utils.js export!
ptrdom commented 8 months ago

Your application entrypoint is JavaScript. Example: I have a JS react app, and one or more components of it are in Scala.js. I have some App.jsx file that imports 'scalajs:ScalaJSComponent1.js' and 'scalajs:ScalaJSComponent2.js'. I expect vite to bundle App.jsx with Scala.js artifacts produced by fullLinkJS (which I expect to include ScalaJSComponent1.js and ScalaJSComponent2.js)

So I think here you should have your Scala.js components exported as a library and that is that. scalajs-vite would only do the library bundling, if said Scala.js components depend on npm dependencies.

Your application entrypoint is Scala.js, but it depends on some non-Scala.js sources. These sources in turn depend on exports from Scala.js. Imagine, e.g., fullLinkJS produces main.js (runnable entrypoint), and utils.js (module generated by a call to JSExportTopLevel("someUtility", "utils")). main.js imports a JS module someJsModule.js, and someJsModule.js in turns imports 'scalajs:utils.js'. These imports all need to be resolvable for vite to generate a runnable bundle from main.js.

As a corollary to (2), now image you have a test in your Scala.js source that includes the dependency to someJsModule.js. For this test to run, the bundler is going to need access not only to the compiled test code and the JS sources, but also the compiled Scala.js application code, since theTest / fastLinkJS outputs will not necessarily include the utils.js export!

This is a highly custom workspace-like workflow that I think should be split up and not implemented directly by this project. In npm world you would likely have utils.js package, someJsModule.js package and main.js application that uses those packages. This is only missing library bundling and we can add support for that. Now the gluing of the workflow can be done outside of this project. Do you see this working for you?

johnhungerford commented 8 months ago

In npm world you would likely have utils.js package, someJsModule.js package and main.js application that uses those packages.

utils.js isn't a package, it's just a script that's emitted by the Scala.js linker; similarly, someModule.js is just a javascript source file. The question is only whether the bundler can resolve import statements that reference them. I promise this has nothing to do with library bundling. When you run fullLinkJS or fastLinkJS, the linker will produce one or more files (depending on your linker configuration and what kind of JSExport annotations you include in your code). There is no reason why other non-Scala.js source files included in viteResourcesDirectory should not be able to import from them.

At any rate, I definitely understand if you don't want to support complex interop here, as it will definitely increase the complexity, but it's a requirement for me. I already have a working solution for it, so if you don't want to move in that direction, I can just keep working on my project. I just thought it would make sense to deduplicate our efforts.

ptrdom commented 8 months ago

But then if utils.js is not a standalone library, I do not see what is the problem. For example, how case 1 and 2, and even 3 with https://github.com/ptrdom/scalajs-vite/tree/test-bundling merged, cannot be made to work with current version of this project right now? It might need more than one Scala.js module to be emitted, but it should not need multiple sbt modules.

Main thing I am suggesting is to try and decompose your problem into smaller bits that would make smaller, reusable features, then determine what is within scope of this project. If you are not willing to proceed in this manner, then working on your own plugin will make most sense.

And you will need to have some patience with me, because I am not familiar with this particular workflow 🤷

johnhungerford commented 8 months ago

Actually I just went back and tried something out and it looks like I had an incorrect assumption: the exported modules are included in the Test / xLinkJS outputs! I must have missed something when I tested this before. (It had looked to my like Test / xLinkJS didn't generate exports, in which case Compile / xLinkJS outputs would need to be included in the build directory when bundling tests to support case 3)

This being the case, you are correct that your approach should be able to handle all three cases.

ptrdom commented 5 months ago

Implemented with https://github.com/ptrdom/scalajs-vite/pull/89.