-
Notifications
You must be signed in to change notification settings - Fork 384
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
Support for emitting modules into a subdirectory #4742
Comments
Uh oh, that was never meant to be allowed. The infrastructure of
I sympathize, but IMO this falls under the category of "frameworks that want a specific shape for the output code rather than semantics". We don't guarantee that we will support every possible framework's idea of how the target .js files should "look like". Have you considered defining the structure separately, as a hierarchy of .js files that |
Yeah, so for now I've just been maintaining a bunch of glue JS files that just import and re-export the Scala.js modules. Because in Next.js' case they are so boilerplate-y though (https://github.com/shadaj/slinky/blob/main/docs/pages/docs/%5Bid%5D.js), was thinking it might not be too hard to support this use case in Scala.js since we don't have any restrictions on how the code inside the module is laid out, just where it is placed. But also see your point that this does introduce some more responsibilities that Scala.js may not want to handle. |
My 2cts:
I could see the value of extending
|
@armanbilge asked me to reference my own usecase for this - a while back I attempted to do the same for Cloudflare Pages. Quoting my Discord message:
|
There's one more challenge I forgot: How to synthesize internal module names. If they need to go into special directories, things might get more complicated (OTOH, a simple |
For Next.js, we would probably want to dump all internal modules into a |
For cloudflare, I believe only modules that explicitly Therefore other files and their names (intermediate modules) are likely to be ignored (*), so I would personally not expect any structure from them - they can all be dumped top level for all I care. If I have time, I will test the (*) assumption to make sure it works as I described. |
FWIW, this is a problem: We cannot guarantee that an internal module does not export a function with a name that is special to cloudflare. |
Just stumbled into this again. I understand that ESM-style npm packages are typically organized into subdirectories e.g. import '@shoelace-style/shoelace/dist/components/button/button.js'; So it would be nice to be able to make such things with Scala.js as well. |
Maybe, but do we have examples of where this is actually useful? For shoelace:
Even what the doc lists as utilities (e.g. |
I'd like to add another voice for supporting structured directories. It's pretty tedious to manually setup the file structure for Is this something where a PR would be considered? Alternatively, is there a mechanism that would allow extending the plugin to have this functionality within
I don't think it's important to make this guarantee for CloudFlare or any other tooling. Assuming that generated modules can have some user-defined directory structure + internal modules live somewhere else, I would expect it to be easy to post-process this into whatever structure the user needs/wants. |
IMO, yes, for sure. However, I'd urge you to first propose and discuss the exact changes you'd like to do (in this issue). Especially for internal modules, it is not clear to me how the interface could look like.
You could move the modules around after they are built by Scala.js. But that would also involve adjusting the import paths (since they are relative). So, yes, a mechanism could be built, but to the best of my knowledge, there isn't any at the moment. |
A couple of thoughts I had: The following might get us quite far:
Unclear with this approach is how to deal with module ID equality: Right now, the following are considered different module IDs: |
Given that any slash today results in a crash, and that a single |
Wouldn't it be simpler to just use something like java.nio.Path.normalize instead of trying to be clever about it? |
Our linker cross-compiles to JS. In Scala.js we don't have access to |
Also FWIW: Paths are platform specific (at least if we're using the default FileSystem). So we need to be careful: We do not want the linker's output to depend on the platform it is running on. |
Apologies that I don't have a better understanding of how the plugin works internally - I've not had a lot of time to do some digging just yet. I've also never done anything with cross compilation, so maybe my understanding is all garbage!
At some point it needs to execute platform-specific code though, no? ie. there is some
|
There are two notions of "platform" at play here. Usually we mean JVM v JS, indeed. But here we're talking about the OS and its filesystem. We want the linker to produce identical outputs regardless of the OS and of the filesystem(s) being used. Therefore, we cannot rely on |
I did some experimenting here; the linker already has runtime-specific (ie. Node, JVM) implementations and appears to require little change to (see the sketch here; note that this is pretty scrappy code that I'll clean up and test if people are happy with the approach).
I'm not sure that there's anything that we need to do to support various OS here. The underlying runtime can deal with this for us. |
As far as actually writing the modules to the filesystem is concerned, this is correct. However, we also need to consider the usage sites of the modules (i.e. the imports): When the linker splits off some code into an internal module, it will need to import that code. Currently, it will emit: import * as <internal name> from "./<moduleID>.<ext>" Where the specifics are determined by the OutputPatterns. So far, the assumption was that At this point, we need to take care, that:
You might correctly observe at this point, that (apart from the changes you're already working on in your branch), there is nothing to do for the success path. However, we need to make sure that we are not generating broken code (e.g. `import ... from './a/../b\foo.js'). This is what @sjrd and I have been discussing about. HTH |
This makes sense. It seems that I didn't do enough testing; I can replicate this once I start writing non-trivial code. Are you able to point to where this code is emitted from the linker?
I don't understand why this is important, but a simple solution here is to use only But more importantly I think upholding this assumption will end up with broken code if any form of directory nesting is allowed, regardless of the separator used. For example:
// If I'm working on a *nix system, I'm likely to have an export that looks like
@JSExportTopLevel("baz", moduleID = "app/bar")
// This would import modules from `foo` using `import <names> from "../util/foo"`; which would work correctly
// If I'm working on a Windows system, I'm likely to have an export that looks like
@JSExportTopLevel("baz", moduleID = "app\\bar")
// This would import modules from `foo` using `import <names> from "../util\\foo"`; which I believe _would break_ because of the mixed characters - Though I have not tested this assumption.
Note that in both of these cases, the break occurs because the user followed the conventions of their OS but scala-js uses only a single character for the separator. One might argue that the user could be educated to follow an alternative pattern (eg. always use *nix paths, even on Windows), but that is a situation that I expect would be ripe for confusion. So if this is a hard requirement; I don't think that there's any simple way to both support any OS and support emitting into subdirectories. |
We definitely have to standardize on |
Just to be clear; you're saying that the user must always use something that looks like Assuming you're happy with the direction I've started looking at earlier (the link here), I can start figuring out how to get the linker to emit |
Yes.
Yes. In fact, this is a requirement: The same input with the same options must produce the same output on all systems/OSs.
That looks like a good start to me.
HTH |
Nikita's comment on Discord just made me realize that when building this, we need to be extremely careful to keep relative path imports meaningful. |
When I have some time I'll aim to come back to this; however for now I've had some life/work stuff get in the way |
Was: Linker crashes when emitting modules into a subdirectory
If exporting a method/value under a module name that contains a
/
, the linker crashes because it attempts to pass the entire name directly tocreateTempFile
. Having a slash in the module name is useful when integrating with frameworks such as Next.js, which use directories to map components to application routes.For example, running
fastLinkJS
with a project containing the following export:results in the following crash
The text was updated successfully, but these errors were encountered: