tc39 / proposal-import-attributes

Proposal for syntax to import ES modules with assertions
https://tc39.es/proposal-import-attributes/
Apache License 2.0
567 stars 32 forks source link

Could the static and dynamic import syntaxes be more symmetric? #99

Open devongovett opened 3 years ago

devongovett commented 3 years ago

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:

  1. Why are assert and with separate keywords in the static import case? Could assert be an option within the with 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" } })
  2. 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: https://github.com/parcel-bundler/parcel/pull/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! 😄

ljharb commented 3 years ago

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.

littledan commented 3 years ago

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?

devongovett commented 3 years ago

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" } })
ljharb commented 3 years ago

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.

devongovett commented 3 years ago

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.

littledan commented 3 years ago

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.

devongovett commented 3 years ago

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.

ljharb commented 3 years ago

Does preloading affect module evaluation order?

devongovett commented 3 years ago

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.

ljharb commented 3 years ago

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?

littledan commented 3 years ago

It sounds like these preload options are neither assert nor with logically, and they only are needed for dynamic import. Is that accurate?

devongovett commented 3 years ago

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.

josephrocca commented 3 years ago

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.

littledan commented 3 years ago

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.