-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Add multi module compilation for elm #8076
Add multi module compilation for elm #8076
Conversation
The Elm compiler can compile multiple entrypoints into one bundle, which helps keep the asset size small since they all share the same runtime. Use like this: import { Elm } from './Main.elm?with=./AnotherModule.elm,./YetAnotherModule.elm' Elm.Main.init(); Elm.AnotherModule.init(); Elm.YetAnotherModule.init();
I'm not very familiar with Elm, but what's the difference between doing this vs making a new entry file and importing things there instead? The syntax here of importing files with a query string seems kinda strange to me. |
@devongovett Let me give you a bit more background on this. The difference is basically creating smaller bundles. Multiple compile steps vs a single compile stepimport { Elm } from "src/Main.elm";
import { Elm as Elm2 } from "src/Main2.elm";
Elm.Main.init();
Elm2.Main2.init(); and import { Elm } from "src/Main.elm?with=src/Main2.elm";
Elm.Main.init();
Elm.Main2.init(); is that
When using the elm compiler on the command line those cases be equivalent to this
How common is this?I wouldn't say this is super common, but if anyone wants to do this they couldn't use Elm with parcel right now. Query string syntaxI agree that it seems a bit odd. I was wondering how to do this and found that this is how it's done with Vite. https://github.com/hmsk/vite-plugin-elm#combine-multiple-main-files-experimental-from-270-beta1 |
For reference, webpack handles it like this. |
Sure. I guess what I was asking is if you could make a new Elm file that imports all of your entry points and re-exports them somehow, and then import that from JS instead? |
Elm-applications are imported by their module names, and for a module to be considered an application, it has to contain a function named |
Yes, what @teppix said. The Elm compiler doesn't allow more than one |
So you can't do something like this? module Main exposing (ModuleA, ModuleB)
import ModuleA
import ModuleB and then from JS: import { Elm } from './Main.elm';
Elm.ModuleA.init();
Elm.ModuleB.init(); I have no idea if this is valid syntax, but that's what I meant. If possible, that seems nicer than doing it in the query string. |
Unfortunately not. In order to have an .init() function on the JS side you need to define a main function on the Elm side. Currently Elm only allows one main function per entry point module and it has to be in the topmost module (not imported by any other module). |
Got it. Maybe that would be a good issue to open in Elm then. In the meantime, I guess this is fine. Would you mind adding a test? We'll need to add this to the documentation as well. |
Testssure, I'll add a test for this. DocsI can also prepare some documentation if you'd like. Would that be another PR in this repo over here https://github.com/parcel-bundler/website/blob/v2/src/languages/elm.md ? Syntax / API designI've been thinking a bit more on the not-so-perfect syntactical way of using this. import { Elm } from './Main.elm?with=./AnotherModule.elm,./YetAnotherModule.elm' When there are more than one extra modules I separate them with a I thought maybe this could be improved a little by specifying the import { Elm } from './Main.elm?with=./AnotherModule.elm&with=./YetAnotherModule.elm' and then get those values with Do you have any thoughts on this? Improvement or just as bad? |
Better I think! |
3c9eb60
to
4e8eec7
Compare
I made the changes regarding multiple |
I have a question that I just thought about: What would happen if someone imported this twice? // in file1.js
import { Elm } from './Main.elm?with=./AnotherModule.elm&with=./YetAnotherModule.elm';
// in file2
import { Elm } from './Main.elm?with=./AnotherModule.elm&with=./YetAnotherModule.elm'; I would assume the asset is compiled once and imported in both files, right? // in file1.js
import { Elm } from './Main.elm?with=./AnotherModule.elm&with=./YetAnotherModule.elm';
// in file2
import { Elm } from './Main.elm?with=./YetAnotherModule.elm&with=./AnotherModule.elm'; How does parcel treat the specifiers where the only difference is the order of the query params, i.e. when the name is the same, when the queries are the same, when the exact string is the same? Would parcel run the transformer twice and include the compiled JS twice in the resulting bundle? |
Here's where the query comes from in the resolver:
and it's then turned into a string to store it in the cache.
It looks like this won't normalize the order. So there will be two assets. And theoretically you could also write a transformer where this order matters, so there's not much we can do here. |
@mischnic thanks for the clarification. I see how that could be difficult to change, but also surprising at the same time. (i.e. when importing I wonder if the current solution with the query string is the way to go then, because I could see that leading to unintentionally increasing the bundle size significantly without warning or specifying the path slightly diffently (i.e. no leading So I'd like to propose a different solution for mutli-module compilation for Elm: In the package.json, one could add something like this: "@parcel/transformer-elm": {
// this means: whenever Main.elm is imported, also compile AnotherModule.elm and YetAnotherModule.elm with it
"extraSources": {
"./src/Main.elm": ["./src/AnotherModule.elm", "./src/YetAnotherModule.elm"]
}
} This is more of a static config but it has a few benefits IMO
Any thoughts on this new approach? If you're in favor of this different approach, would you prefer me closing this PR and opening a new one or add commits to this one? |
I went ahead and implemented the approach described above and it works. nicely e716224 |
The biggest difference I see between query param and the central config: is there a usecase for importing Apart from the other advantages you've listed, if the only concern is deduplicating two different but equivalent transform({asset}) {
if(isQueryNormalized(asset.query)) {
return whatTheElmTransformerDoesAtTheMoment(...);
} else {
let specifier = "./" + path.filename(asset.filePath) + "?" +
// Sort, normalize leading "./", etc.
normalizeQuery(asset.query).toString();
asset.setCode(`export * from "${specifier}";`);
asset.type = "js";
return [asset];
}
} |
Hi @mischnic ,
I don't see one to be honest. The whole point of this multi-module compilation is saving on bundle size. If one needs
This is very interesting. Nice trick to sort the query string and turn it into a JS asset with a normalized query. Maybe that would also be good for the image transformer. Despite of there being a solution to the duplication problem, I think advantages of the config approach outweigh the ones of the query string approach (which is mostly not having to have a config). Especially the fact that the query approach gets really tedious with long file paths and/or many extra sources. So I'm proposing to go with the config solution. |
…ype but "error" type
It doesn't seem to work as intended
So I ended up removing the query string deduplication logic for two reasons
Other than adding documentation for this feature and publishing the resolver package I think the work here is done. Does this look like something we could merge? |
…istophP/parcel into add-multi-module-compilation-for-elm
(Sorry, I might have confused you there. There will be multiple assets called |
Ah ok, yeah I got confused a bit. Would there be a good way to test that the other ones are just If so I could do that and bring back the deduplication. But overall, I guess the deduplication logic is not that important when the recommended way to use this feature is the resolver you described. |
I can't think of a better way than But yeah, I don't think that the deduplication is essential |
Ok, if you're fine not having the deduplication logic then I guess this PR is ready. Would you like me to create another PR for the documentation? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Yes updating the documentation would be really helpful. 😄
Added some docs here parcel-bundler/website#1038 Will still finish up the extra third party resolver package @mischnic was proposing. here #8076 (comment) |
Thanks @mischnic will look at some of the suggestions |
I also bumped terser to the latest version to fix some issues around production builds that hang forever |
You'll need to run |
Handled terser bump separately |
* upstream/v2: (22 commits) Cross compile toolchains are built into docker image already Also fix release build Update centos node version v2.7.0 Changelog for v2.7.0 Use placeholder expression when replacing unused symbols (#8358) Lint (#8359) Add support for errorRecovery option in @parcel/transformer-css (#8352) VS Code Extension for Parcel (#8139) Add multi module compilation for elm (#8076) Bump terser from 5.7.2 to 5.14.2 (#8322) Bump node-forge from 1.2.1 to 1.3.0 (#8271) allow cjs config files on type module projects (#8253) inject script for hmr when there is only normal script in html (#8330) feat: support react refresh for @emotion/react (#8205) Update index.d.ts (#8293) remove charset from jsloader script set (#8346) Log resolved targets in verbose log level (#8254) Fix missing module in large app from Experimental Bundler (#8303) [Symbol Propagation] Non-deterministic bundle hashes (#8212) ...
Fixes #2508
Fixes #8263
↪️ Pull Request
Add multi module compilation for Elm](58174b5)
The Elm compiler can compile multiple entrypoints into one bundle, which
helps keep the asset size small since they all share the same runtime.
The approach used here has been inspired by the elm plugin for vite: https://github.com/hmsk/vite-plugin-elm#combine-multiple-main-files-experimental-from-270-beta1
💻 Examples
🚨 Test instructions
Use a js file like this:
and another Elm module