Repository: node
Issue: Trying to load two modules with the same URL throws an empty object
Labels: esm,loaders

In a loader module I'm handling sources with omitted `type` import attribute on JSON imports, 
by assigning `context.importAttributes.type` in the load hook, while caching the outputs. 

When the attributes are consistently omitted from JSON imports, or consistently aren't, this works well. 
But when the import signature changes on one occasion, the process breaks 
with an empty error object (`{}`) on returning the cached output. 

I can't tell the reason for this, for as far as the loader is concerned, 
the load hook output should be idempotent whether the input `context.importAttributes.type` is defined or not: 

```js
[Object: null prototype] {
  format: 'json',
  responseURL: 'file:///home/ranger/loaded.json',
  source: <Buffer 7b 22 61 22 3a 31 7d 0a>,
  shortCircuit: true
}
```

Here's a loader module to reproduce: 
```js
// loader.js
export const address = new URL(import.meta.url).pathname;
export const file = address.replace(/.*\//, "");

// register loader
if (
  !globalThis.window &&
  process.execArgv.some((flag) =>
    new RegExp("^--import[= ][^ ]*" + file).test(flag),
  )
)
  Promise.all(
    ["module", "worker_threads"].map((module) => import(module)),
  ).then(
    ([{ register }, { MessageChannel, isMainThread }]) =>
      isMainThread &&
      [new MessageChannel(), import.meta.url].reduce(
        ({ port1, port2 }, parentURL) =>
          register(address, parentURL, {
            data: { socket: port2 },
            transferList: [port2],
          }) || port1.on("message", console.log),
      ),
  );

function initialize({ socket }) {
  socket.postMessage(" Registered loader module:\n " + import.meta.url + ".");
}

const load = function (source, context, next) {
  if (this[source])
    // return cached value.
    return console.log(this[source]) || this[source];
  if (source.endsWith("json")) context.importAttributes.type = "json";
  return next(source, context).then(
    (context) =>
      // cache output
      (this[source] = Object.assign(context, { shortCircuit: true })),
  );
}.bind({});

export { initialize, load };

```

and here's a file with inconsistent attribute signatures to load 
(in my real use case the inconsistency was in separate modules, one importing the other): 
```js
// loaded.js
 import json1 from "./loaded.json";
 import json2 from "./loaded.json" with {type:"json"};

console.log(json1===json2);

// loaded.json
{"a":1}
```

Result with node 21.1.0 (only notable difference is `<Buffer >` being empty in `nextLoad`'s output already on the first import): 
`node --watch --import=./loader.js ./loaded.js`
```console
 Registered loader module:
 file:///home/ranger/loader.js.
(node:1498) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
[Object: null prototype] {
  format: 'json',
  responseURL: 'file:///home/ranger/loaded.json',
  source: <Buffer >,
  shortCircuit: true
}

node:internal/process/esm_loader:40
      internalBinding('errors').triggerUncaughtException(
                                ^
{}

Node.js v21.1.0
Failed running './loaded.js'
```