Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detecting support / extensible web? #171

Closed
remy opened this issue Aug 14, 2019 · 12 comments · Fixed by #256
Closed

Detecting support / extensible web? #171

remy opened this issue Aug 14, 2019 · 12 comments · Fixed by #256

Comments

@remy
Copy link

remy commented Aug 14, 2019

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:

<script type="importmap">
{
  "imports": {
    "vue": "/vendor/vue.js"
  }
}
</script>
<script type="module" src="./index.js"></script>
<script nomodule src="./bundle.js"></script>

Where index.js is doing import Vue from 'vue and the nomodule 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" and type="importmap" as I have my nomodule 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?

@domenic
Copy link
Collaborator

domenic commented Aug 14, 2019

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>

@matthewp
Copy link

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.

@domenic domenic closed this as completed Sep 23, 2019
@domenic domenic reopened this Sep 23, 2019
@domenic domenic added this to the API milestone Sep 23, 2019
@philipwalton
Copy link

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.)

@guybedford
Copy link
Collaborator

@philipwalton on that performance note, I've been pushing for some time for a generic web preload spec as discussed in systemjs/systemjs#1953. Exactly about dealing with this lazy / conditional graph preloading.

@trusktr
Copy link

trusktr commented Nov 28, 2020

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.

@ljharb
Copy link

ljharb commented Nov 29, 2020

It is not; i believe only chrome has import maps support at this time.

@LarsDenBakker
Copy link

Did we add something for detecting import maps in the end?

@mrdoob
Copy link

mrdoob commented Feb 26, 2021

I'm also interested. How do we know if the browser supports import maps?

@dmail
Copy link
Contributor

dmail commented Feb 26, 2021

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 #166

@mrdoob
Copy link

mrdoob commented Feb 26, 2021

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?

@dmail
Copy link
Contributor

dmail commented Feb 26, 2021

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

exports.resolve = (specifier, parsedImportMap, scriptURL) => {

Screenshot of the result

image

So it should work.

@dmail
Copy link
Contributor

dmail commented Mar 14, 2021

Running the code in #171 (comment) twice in Chromium Version 90.0.4421.0 logs the following error in the console:

image

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)

horo-t added a commit to horo-t/import-maps that referenced this issue Sep 2, 2021
horo-t added a commit to horo-t/import-maps that referenced this issue Sep 2, 2021
The script.supports() method will be introduced by
whatwg/html#7008.
script.supports('importmap') must return true when supported.

Fixes WICG#171
horo-t added a commit to horo-t/import-maps that referenced this issue Sep 6, 2021
The HTMLScriptElement.supports() method was introduced by
whatwg/html#7008.
HTMLScriptElement.supports('importmap') must return true when supported.

Fixes WICG#171
domenic pushed a commit to horo-t/import-maps that referenced this issue Sep 9, 2021
The HTMLScriptElement.supports() method was introduced by
whatwg/html#7008.
HTMLScriptElement.supports('importmap') must return true when supported.

Fixes WICG#171
domenic pushed a commit that referenced this issue Sep 10, 2021
The HTMLScriptElement.supports() method was introduced by whatwg/html#7008. This makes HTMLScriptElement.supports('importmap') return true.

Fixes #171.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants