`import source`: why not an attribute?

I'm finding the new import source syntax barely justifiable because we already have the now-omnipotent with that can basically change everything about module-importing, so I'm wondering: what benefits does import source x from "x" offer over import x from "x" with { phase: "source" }, given that both are statically analyzable, and that both import("x", { phase: "source" }) and import.source("x") are only statically analyzable to a limited extent?

(I might have seen this discussion in one of the proposal repos, but there are too many with overlapping concerns.)

I wonder if the key issue is that phase and import attributes are intended to live at different conceptual layers.

My understanding is that with { ... } is specifically for import attributes: metadata that influences how the referenced module is handled by the host. By contrast, phase seems to affect what kind of thing the import operation itself yields. For example, a source-phase import produces a ModuleSource-like value rather than the usual module namespace object.

If that's the distinction being drawn, then phase may not naturally fit into the import-attributes space at all. In that framing, the alternatives would be:

  • a dedicated syntax such as import source ...
  • some separate syntactic position for phase information (analogous to how dynamic import could conceptually separate { phase, with })

rather than expressing it as another entry inside with { ... }.

So perhaps the question is less "why not with { phase: "source" }?" and more "should phase be considered an import attribute in the first place?"

Now when I look back at it, the biggest difference might be module caching? import x from "foo" and import source x from "foo" both import from foo so the module wouldn't be fetched and evaluated twice. import x from "foo" and import x from "foo" with { phase: "foo" } will be two different modules.

That's an interesting point. Although I wonder if that might itself be evidence that phase doesn't fit naturally into the import-attributes model.

If import attributes participate in module identity and caching, but source-phase imports are expected to refer to the same underlying module record as normal imports, then perhaps phase is describing a different aspect of the import system entirely rather than an attribute of the module request.

Caching is certainly one aspect for sure.

import source doesn't evaluate the module but you're right that it wouldn't be fetched twice in that example. The cache key for module source is the combination of: referrer, specifier, and attributes.

So:

import source src from "here" with { someHostAttribute: "42" };
import * as namespace from "here" { someHostAttribute: "42" };
const namespace2 = await import(src);
// The spec says that the following must hold:
assert(namespace2 === namespace);

The spec could have reserved "phase" as a special attribute that is handled at a different layer but the champions felt it was better to keep the layering syntactically separate.

Thanks, that makes sense. By "not evaluated twice", I mean that with await import(src) as well as import * as namespace, the module in "here" will only be evaluated once, right?

Yep exactly