proposal-import-attributes: Semantics of unrecognized types

In several threads, @ljharb has suggested that unrecognized type values be treated as a missing type. Let’s centralize discussion of that question here.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 1
  • Comments: 37 (26 by maintainers)

Most upvoted comments

I agree with @ljharb that we need to have an upgrade path for new type values, but disagree that the strategy should be “load modules with unknown type value as if the attribute were absent”.

For constraining preexisting types, new attributes would be better than new type values (e.g., type: "esm", pure: "true" rather than type: "esm-pure").

For extending preexisting types in a way that does not break loading (such as use of new ECMAScript standard library functionality, e.g. String.prototype.matchAll), no changes at all are necessary for importing, just guards inside the module.

However, for extending preexisting types at such a fundamental level that it does break loading (such as use of new ECMAScript syntax or a new WASM version/section id/etc.), a new type value is warranted—and no amount of import signals would help an old client load those modules. What I would hope for there is a way to express that an import is conditional, ideally with sufficient power to also define fallback imports (although that may be out of scope for this proposal).

Personally for Node.js I would want to encourage using conditional exports over an environment specific assertion. Code that can work is preferable to code that does not work! And environment assertions would be naturally incompatible with conditional exports / conditional internal imports (https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#packages_conditional_exports). Even for things like import { readFile } from 'fs', we can use conditional exports to shim this layer between environments such that import { readFile } from 'fs' assert { env: 'node' } would actually inhibit better virtualization.

The web is going to reject unknown type values (see the import assertion integration PR ). On the web this seems clearly necessary because otherwise browsers without support for a new module type will be vulnerable to the MIME type security issue where an unexpected JS MIME type could cause unexpected script execution:

// Consider the case where third-party-site responds with a JS payload with MIME type application/javascript.
// If unknown 'type' values were ignored, this would then trigger unexpected script execution on browsers that
// don't yet support JSON modules.
import json from "https://third-party-site/info.json" assert { type: "json" };

@MylesBorins , do you know what Node is doing for unsupported type values? If there is some convergence among hosts here then I think we should consider enforcing this behavior with something like https://github.com/tc39/proposal-import-assertions/pull/111. It would be nice to have interoperability on this.

To distill my earlier comments to just that topic, I’m advocating for unrecognized types to abort loading the module graph. When an import comes with an explicit type signal, the host must either honor that by processing it correctly, or fail altogether.

I don’t want to jump down your throat over this example, but: Oof, querying on ES version is a whole other can of worms. The thing is, implementations don’t really tend to ship a whole ES version in lockstep, but rather go feature by feature, so I am not sure how meaningful the query would be if it refers to spec versions. Similar attempts in the web platform found implementations lying, rendering the feature useless.

@jfparadis Hmm, I would be surprised if we gave the semantics that, if the type is unrecognized, you get a module out that somehow has lots of named exports, that are all undefined or null. Generally, I’d imagine module graph loading to fail if one of the loads is unsuccessful. (As you point out, this is not a great loading strategy because you always load the fallback.) Top-level await may be our friend here!

It’s possible that you personally still write in or transpile to ES5, but authors all over the Internet are constantly clamoring for new syntactic sugar and the ability to use it, with varying claims of the resulting performance and/or convenience. I’ll refer you to https://github.com/whatwg/html/issues/4432 for a particularly relevant example.

@gibson042 If I can choose between three scripts with identical behaviour, and the only differences is the features they use internally, why wouldn’t I just use whatever has the broadest support instead of setting up a matrix of loading conditions?

It becomes relevant for the consumer as soon as they try importing it. Cooperation between the importing and exporting sides would obviously be required, but at least it becomes possible to use new formats and new syntax rather than being bound by the lowest common denominator.

I had a suspicion that bikeshedding this here might be a bad idea… ☹️

But as stated above, the primary need for this is syntax… ECMAScript source modules can dynamically test for things like Array.prototype.flat or Object.fromEntries, but they won’t load at all on old hosts if they use e.g. import.meta or private fields.

Even querying on a specific feature can end up hitting implementation bugs or missing the granularity required for the checks you want to perform.

I do like the idea of falling back between module types somehow. Note, fallbacks were removed from import-maps. Even if they were still present, we’d have to think about how to represent a URL together with module attributes in the import map, where currently, import maps only map to a URL.

I think that import-maps would be a great fit for this proposal on the web. It could allow to fallback if a certain type isn’t known to the current environment by providing an alternative implementation (security guarantee would not be preserved however).

Migration to WebAssembly with a fallback to JS is a good example.

https://github.com/nodejs/node/pull/29978 might allow it on Node.js as well, but there have different use-cases. Edit: https://github.com/littledan/proposal-module-attributes/issues/25 might a better place to discuss Node.js implementation details.

Yes, but that’s not our only slogan - the web isn’t the sole constituent for the JS language. You’re right that there’s always a compatibility concern, but when it’s within the web, the concerns can be limited to web concerns.

Things that go inside code are always cross-platform concerns.

My worry, on the other hand, is that if we permit unknown type values, then it could quickly become Web-incompatible to add a different interpretation to that type in the future. This is one reason why the Web went with <script type=module> – since elements of that form were inert!

The same reason unrecognized attributes are ignored - that if a “magic” attribute is added in 2021, 2020 browsers shouldn’t fail on it or else anyone trying to support 2020 browsers can’t use “magic” - apply to a type value.

If a new type is added in 2021 (not like a new module type, but like “modules-stricter” or “wasm-extra”, or something else we can’t predict) then code that wants to support 2020 engines can’t use it if it fails; they can if it’s ignored.