Closed remy closed 2 years ago
Great to hear you're liking this! It is still behind an experimental flag/origin trial right now, but hopefully soon it will make it to unflagged-stable :).
I'm a little unsure what you're asking, especially with the last paragraph. But I'll try to answer the question of how to set up a page that uses modules if modules + import maps are supported, or bundles if not. Here's what I've got off the top of my head:
<script type="importmap">
{
"imports": {
"vue": "/vendor/vue.js"
}
}
</script>
<script nomodule src="./bundle.js"></script>
<script type="module" src="./index.js"></script>
<script>
import ("vue").catch(() => {
// We'll get here if we're in a browser that doesn't support import maps,
// because import "vue" will fail (with no evaluation performed). In that case
// the <script type=module> was also a no-op (mostly).
const s = document.createElement('script');
s.src = "./bundle.js";
document.body.append(s);
});
</script>
This solution is a little sub-par, because in the case of browsers that support modules but not import maps, it will fetch (but not evaluate) your module graph up until it sees a bare specifier and fails. I see a couple ways to rejigger it, but they all move that kind of small delay around, instead of eliminating it. It's quite possible there's a solution if I think harder though...
I'll think a bit harder about if there's something more elegant, and /cc @philipwalton and @hiroshige-g who might have better ideas for this particular case.
If we were to add something for this case, one idea is to add the stub of an imperative API, e.g. an empty window.importMap
class instance with no properties for now, that in the future we can expand per #128. That would allow cleaning up the above code in a way that involves no extra fetches:
<script nomodule src="./bundle.js"></script>
<script type="module">
if (window.importMap) {
import("./index.js");
} else {
const s = document.createElement('script');
s.src = "./bundle.js";
document.body.append(s);
}
</script>
One thing you can also do today is avoid using bare specifiers in your code, but instead use something like /vue
which is valid in all module supporting browsers. You'd then need to configure your web server to also take the import map as the fallback case. I know that using bare specifiers is the goal, but this is the smoothest option I've found so far.
For some use cases, you can use Import Maps as their own feature detect. For example, you can build an app with two different entry points (e.g. main-map
and main-nomap
), and then you can determine whether the browser support Import Maps based on which entry module loads.
<script type="importmap">
{
"imports": {
"./main-nomap.mjs": "./main-map.mjs"
}
}
</script>
With this techinque, both main-map
and main-nomap
would import a common main
module, and anything extra that you need to do to handle the no-map case you can run in main-nomap
. (e.g. fall back to SystemJS or something like that).
But this only solves part of the problem.
When loading modules (at least at the moment) you need to preload/modulepreload them in order to get good load performance, and there's not really a good way to conditionally preload modules based on whether you have Import Map support, so it's hard to see a strong adoption story until that's addressed (or I guess until most browsers support Import Maps).
(Note: bundled exchanges would probably help address the modulepreload issue, but it'd also be nice to have something that works now. And of course then there'd be the issue of detecting bundled exchange support.)
@philipwalton on that performance note, I've been pushing for some time for a generic web preload spec as discussed in https://github.com/systemjs/systemjs/issues/1953. Exactly about dealing with this lazy / conditional graph preloading.
Is it implied above that any browser that supports type="module"
also supports type="importmap"
too? I can't find a browser support matrix for type="importmap"
anywhere, but I do see support for type="module"
is easy to find.
It is not; i believe only chrome has import maps support at this time.
Did we add something for detecting import maps in the end?
I'm also interested. How do we know if the browser supports import maps?
Once I tried to write code to detect importmap support. I just found back the relevant file, I guess my past self wrote something usable 🤞 . I was apparently too lazy to put explicit comments but code might be enough to explain what is going on 🤞
<html>
<head></head>
<body>
<script>
//import-map feature detection
const supportsImportmap = async () => {
const specifier = jsToTextUrl(`export default false`)
const importMap = {
imports: {
[specifier]: jsToTextUrl(`export default true`),
},
}
const importmapScript = document.createElement("script")
importmapScript.type = "importmap"
importmapScript.textContent = JSON.stringify(importMap, null, " ")
document.body.appendChild(importmapScript)
const scriptModule = document.createElement("script")
scriptModule.type = "module"
scriptModule.src = jsToTextUrl(`import supported from "${specifier}"; window.__importmap_supported = supported`)
return new Promise((resolve, reject) => {
scriptModule.onload = () => {
const supported = window.__importmap_supported
delete window.__importmap_supported
resolve(supported)
}
scriptModule.onerror = () => {
reject()
}
document.body.appendChild(scriptModule)
})
}
const jsToTextUrl = (js) => {
return `data:text/javascript;base64,${window.btoa(js)}`
}
supportsImportmap().then((has) => {
console.log(has)
})
</script>
</body>
</html>
Edit: Don't know in what scenario one could want to use this piece of code but there is maybe something to learn from it.
Edit2: I guess it does not work because data:
url cannot be remapped anymore since https://github.com/WICG/import-maps/issues/166
Edit: Don't know in what scenario one could want to use this piece of code
Better than if ( /apple/i.test(navigator.vendor) ) return false;
...
Edit2: I guess it does not work because data: url cannot be remapped anymore since #166
So it doesn't work?
I wanted to verify if remapping data:
urls is possible so I wrote a little Node.js code and runned it against the reference implementation available in this repository.
My test code
const { parseFromString } = require("./parser.js");
const { resolve } = require("./resolver.js");
const from = `data:text/javascript,false`;
const to = `data:text/javascript,true`;
const importmapObject = {
imports: {
[from]: to,
},
};
const importmap = parseFromString(
JSON.stringify(importmapObject),
"file:///directory/"
);
console.log(resolve(from, importmap, "file:///directory/file.js").href);
Link to reference implementation
Screenshot of the result
So it should work.
Running the code in https://github.com/WICG/import-maps/issues/171#issuecomment-786577380 twice in Chromium Version 90.0.4421.0 logs the following error in the console:
It means the code should be run before injecting any script type "module" in the page or it becomes non reliable (promise resolved to undefined
instead of true
)
I've found that
importmap
has landed in Chrome stable (not sure exactly when or if it's an experimental flag that's turned on, but bear with me).This makes me very pleased that this code works seamlessly:
Where index.js is doing
import Vue from 'vue
and thenomodule
bundle is able to use webpack to bundle up Vue as required.Except as soon as I ran this in Firefox the code failed hard. Firefox has support for
type="module"
but knows nothing about import maps, so it failed to import Vue.I'm happy to target only browsers that have
type="module"
andtype="importmap"
as I have mynomodule
fallback support - but how?I can't see any way that I can state this code only works if
importmap
is supported. Is this included in the specification anywhere?