Could the static and dynamic import syntaxes be more symmetric?
devongovett opened this issue ยท 14 comments
Reading through the readme, I'm confused about the syntax, as it seems inconsistent between static and dynamic imports.
import json from "./foo.json" assert { type: "json" };
import("foo.json", { assert: { type: "json" } })
Then, there's also this mentioned, but it appears to be a separate proposal (?):
import json from "./foo.json" assert { type: "json" } with { transformA: "value" };
There is no equivalent dynamic import shown, so it's unclear which of the following it would be:
import("foo.json", { assert: { type: "json" }, with: { transformA: "value" } })
import("foo.json", { assert: { type: "json" }, transformA: "value" })
My questions based on this:
-
Why are
assert
andwith
separate keywords in the static import case? Couldassert
be an option within thewith
options object? This seems like it would be more symmetric with the dynamic import syntax:import json from "./foo.json" with { assert: { type: "json" }} import("foo.json", { assert: { type: "json" } })
-
In the dynamic import syntax, is
assert
the only supported key like it appears to be for static imports? Will engines throw if other keys are added?
We're interested in using custom attributes in Parcel to indicate bundle preload/prefetch hints: parcel-bundler/parcel#5158. Webpack currently does this via their magic comments, but we'd like a less hacky syntax and this proposal looks very promising for that.
My questions above are based on this use case. If assert
is the only supported attribute at the parser level, then we likely won't be able to use it. I'm mainly wondering why it needs to be limited in this way (if it is), and why a more general proposal was rejected. Please feel free to point me to other threads if this was already discussed! ๐
What you're talking about are evaluator attributes, that can impact how a module is loaded - assertions, by spec, must not alter how a module is loaded/interpreted.
My thought was that, if we add this separate proposal for transformation, it would support dynamic import in the way you suggest, with a second option for the transformations. This all hasn't been written up yet. Would you be interested in working together on this proposal?
I guess I'm wondering why they are separate syntactically? Why is assert
a keyword rather than just a property?
As I mentioned in the issue, this seems much more symmetric and extensible.
import json from "./foo.json" with { assert: { type: "json" }}
import("foo.json", { assert: { type: "json" } })
That's not entirely symmetric in that there's no "with" appearing in the dynamic form. Your first example in the OP seems the most symmetric and consistent to me, since the only difference is some curly braces and whatnot.
Sure ok, we can bikeshed all day but honestly I don't care too much about the actual syntax. I am really looking for a reason why this is two proposals rather than one. It seems to me that adding two separate more specific keywords to the language is more work/less elegant than adding a single one that's general purpose.
The point I am making is that the dynamic import syntax is extensible: you can add additional options without going through the spec process to add it to the syntax. The static import syntax is less flexible in that way, and I'm wondering whether it can be made equivalently extensible without changing the syntax each time a new option is added.
Is there anything more that you can say about the extensions you're interested in? I thought that, between assertions and with
, the space would be covered. We are starting with just assertions since they are more regular and easier to understand, and we have concrete use cases for them. Additional use cases would help drive further development.
I think the preload usecase above is interesting. I guess it could fall into the with
category if you think there is really a need to categorize these things syntactically (that's what I'm asking). All the examples I've seen of with
made it seem like it was meant for transforms or something, and preloading/prefetching hints don't really affect that. Another example is webpack's chunk name magic comment that can be used to influence the output filename for a bundler at a dynamic import callsite. I imagine there could be more examples as well, so I was wondering whether categorizing these options separately made sense.
Does preloading affect module evaluation order?
No. It essentially injects a <link rel="preload">
or <link rel="prefetch">
element, which fetches the script but does not evaluate it. There's also <link rel="modulepreload">
which also parses the script in advance but still does not execute it until it is actually imported. We do this when the script containing the dynamic import with this attribute loads, therefore preloading these scripts before the dynamic import is actually called. It's very similar to what webpack does but with a different syntax.
Preloading, then, seems like neither an assertion nor an evaluator, but an annotation - which could be done by a comment, but could also be done by a no-op assertion. Would either of those be satisfactory?
It sounds like these preload options are neither assert
nor with
logically, and they only are needed for dynamic import. Is that accurate?
Correct, it doesn't seem to fit either category. With dynamic import it seems like maybe we can do this as long as extra top-level options passed to the second argument are ignored, but it's not clear from my reading whether this is the case. Given that only assert
and with
are allowed in the static import case, I wasn't sure whether this was also the case for dynamic imports.
I've only just come across this proposal, so I might not be qualified to make a comment here, but while going through the readme I immediately had the same thought as @devongovett. As Devon said, the dynamic import's option object is easily extendable with other properties (e.g. for sub-resource integrity, if that ever gets an in-band version):
import "./foo.mjs" with { assert: { type: "json" }, integrity: "...", referrerPolicy: "..." };
import("./foo.mjs", { assert: { type: "json" }, integrity: "...", referrerPolicy: "..." });
Developers are accustomed to a JSON-like format, and will already be using it for the dynamic import, so this seems like the most intuitive and future-proof syntax.
I think integrity fits into assertions, and preload fits into transformations. We already have an extensible key/value format; it is just sorted into whether we are talking about assertions or transformations. So I am not convinced that more generalization is needed.