When importing a JS module (e.g. https://example.com/module.js
) from a URL that returns a 301/302 redirect to a specific version of the module (e.g. https://example.com/module-42.js
), the redirected module is initiated as a separate instance.
This means that when explicitly importing the module from both the initial URL and the versioned URL, two instances of the module are created:
import "https://example.com/module.js"; // -> redirects to https://example.com/module-42.js
import "https://example.com/module-42.js";
Nevertheless, the value of import.meta.url
inside the module is "https://example.com/module-42.js"
in both cases:
// https://example.com/module-42.js
console.log(import.meta.url)
This behavior is the same in all browsers (Chrome, Firefox, Safarie), but in Deno, the redirected URL and the versioned URL both point to the same module instance and only initialize the module once.
Imo, the way Deno handles redirects makes more sense because the redirect does not just point to the same module, it points to the identical resource which should not be treated as a separate module instance.
Reproducible example:
(async () =>
console.log(
(await import("https://redirect-example.unyt.app/module")).default ==
(await import("https://redirect-example.unyt.app/module@version")).default
)
)()
This evaluates to false
when executed in a browser environment, but evaluates to true
when executed in Deno.
This is a big problem when the modules have side effects during module initialization, since all side effects are executed twice.
Another scenario for which this is a big issue is when a redirected module (https://example.com/moduleA.js
-> https://example.com/version1/moduleA.js
) has a relative import to ./moduleB.js
, which resolves correctly to https://example.com/version1/moduleB.js
.
But if we are not importing https://example.com/moduleA.js
and https://example.com/moduleB.js
(which gets resolved to https://example.com/version1/moduleB.js
) from our root module, we get one instantiation of the redirected https://example.com/version1/moduleA.js
, but two instantiations of moduleB
: One instantiation through the root module import, and a different instantiation through the relative import from moduleA
– the two modules are not treated as the same module!
Is this intended behavior (is there a specification for this somewhere since this is not part of the ECMAScript spec) and is there any way to get around this problem?
2
Answers
Browser loads two modules, but their contents are identical (point to same space in memory). Usually you use what is exported from a module, not some itermediate object module is kept in. If we take your example and modify it to compare what’s inside the modules, you will get different result:
And it’s pretty fine, since usually you want content of the module anyway.
So, you can pretty much do something like that:
Context
Before getting to an answer… I interpret the question to be asking how:
Answer
Before anything else: you were correct when you said:
Module specifiers are indeed simply opaque strings — a given runtime can resolve specifiers however it is programmed to do so.
Some specifiers might resemble URLs, and a runtime might or might not resolve the specifier by making a network request. Or the runtime might implement its own internal specifier mappings… or it might implement a caching layer… or any other number of intermediate steps between parsing the specifier string and resolving module data. The spec doesn’t prescribe how this should happen, which allows for flexibility.
There are no committed alignments across runtimes in regard to the way that URL-like specifiers should be resolved to module data (or whether those modules should be evaluated more than once), and — as you observed — they aren’t implemented in the same way. The network layer itself in each runtime can also be implemented in an arbitrary way.
For these reasons, there are no guarantees of conformity beyond what each runtime provides for itself. You’ll have to discover and verify claims for yourself.
Toward helping you test various runtimes
Below, I will provide some code that you can use to test various runtimes/browsers. It was created and tested using Deno v
1.45.3
, but you should be able to adapt the code in the server module to other JS runtimes — I used only ECMA and WHATWG APIs (nothing Deno-specific):.env
:deno.json
:env.ts
:serve.ts
:server.test.ts
:server.ts
:You can run the server with the following command in order to test various runtimes:
…or test with Deno directly using