The JavaScript ES6 specification supports module imports aka ES6 modules.
The static imports are quite obvious to use and do already have quite a good browser support, but dynamic import is still lacking behind.
So it is reasonably possible that your code uses static modules (when these would be not supported the code would not even execute), but the browser may miss support for dynamic import. Thus, detecting whether dynamic loading works (before trying to actually load code) may be useful. And as browser detection is frowned upon, of course I'd like to use feature detection.
Use cases may be to show an error, fallback to some other data/default/loading algorithm, provide developers with the advantages of dynamic loading (of data e.g. in a lazy-mode style) in a module, while allowing a fallback to passing the data directly etc. Basically, all the usual use cases where feature detection may be used.
Now, as dynamic modules are imported with import('/modules/my-module.js')
one would obviously try to just detect whether the function is there, like this:
// this code does NOT work
if (import) {
console.log("dynamic import supported")
}
I guess, for every(?) other function this would work, but the problem seems to be: As import
is, respectively was, a reserved keyword in ECMAScript, and is now obviously also used for indicating the static import, it is not a real function. As MDN says it, it is "function-like".
import()
results in a syntax error, so this is not really usable and import("")
results in a Promise that rejects, which may be useful, but looks really hackish/like a workaround. Also, it requires an async context (await
etc.) just for feature-detecting, which is not really nice.typeeof import
also fails directly, causing a syntax error, because of the keyword ("unexpected token: keyword 'import'").
So what is the best way to reliably feature-detect that a browser does support dynamic ES6 modules?
Edit: As I see some answers, please note that the solution should of course be as generally usable as possible, i.e. e.g. CSPs may prevent the use of eval
and in PWAs you shall not assume you are always online, so just trying a request for some abitrary file may cause incorrect results.
The following code detects dynamic import support without false positives. The function actually loads a valid module from a data uri (so that it works even when offline).
The function hasDynamicImport
returns a Promise
, hence it requires either native or polyfilled Promise support. On the other hand, dynamic import returns a Promise. Hence there is no point in checking for dynamic import support, if Promise is not supported.
function hasDynamicImport() {
try {
return new Function("return import('data:text/javascript;base64,Cg==').then(r => true)")();
} catch(e) {
return Promise.resolve(false);
}
}
hasDynamicImport()
.then((support) => console.log('Dynamic import is ' + (support ? '' : 'not ') + 'supported'))
This has been tested on latest Chrome, Chrome 62 and IE 11 (with polyfilled Promise).
Three ways come to mind, all relying on getting a syntax error using import()
:
eval
(but runs afoul some CSPs)script
tagscript
tagsYou have the import()
use foo
or some such. That's an invalid module specifier unless it's in your import map, so shouldn't cause a network request. Use a catch
handler to catch the load error, and a try
/catch
around the import()
"call" just to catch any synchronous errors regarding the module specifier, to avoid cluttering up your error console. Note that on browsers that don't support it, I don't think you can avoid the syntax error in the console (at least, window.onerror
didn't for me on Legacy Edge).
eval
...since eval
isn't necessarily evil; e.g., if guaranteed to use your own content (but, again, CSPs may limit):
let supported = false;
try {
eval("try { import('foo').catch(() => {}); } catch (e) { }");
supported = true;
} catch (e) {
}
document.body.insertAdjacentHTML("beforeend", `Supported: ${supported}`);
script
let supported = false;
const script = document.createElement("script");
script.textContent = "try { import('foo').catch(() => { }); } catch (e) { } supported = true;";
document.body.appendChild(script);
document.body.insertAdjacentHTML("beforeend", `Supported: ${supported}`);
script
tags<script>
let supported = false;
</script>
<script>
try {
import("foo").catch(() => {});
} catch (e) {
}
supported = true;
</script>
<script>
document.body.insertAdjacentHTML("beforeend", `Supported: ${supported}`);
</script>
Those all silently report true
on Chrome, Chromium Edge, Firefox, etc.; and false
on Legacy Edge (with a syntax error).
During more research I've found this gist script, with this essential piece of JS for dynamic feature detection:
function supportsDynamicImport() {
try {
new Function('import("")');
return true;
} catch (err) {
return false;
}
}
document.body.textContent = `supports dynamic loading: ${supportsDynamicImport()}`;
All credit for this goes to @ebidel from GitHub!
Anyway, this has two problems:
true
altghough these browser do not actually support it)My own solution, it requires 1 extra request but without globals, without eval, and strict CSP compliant:
Into your HTML
<script type="module">
import('./isDynamic.js')
</script>
<script src="/main.js" type="module"></script>
isDynamic.js
let value
export const then = () => (value = value === Boolean(value))
main.js
import { then } from './isDynamic.js'
console.log(then())
Alternative
Without extra request, nor eval/globals (of course), just needing the DataURI support:
<script type="module">
import('data:text/javascript,let value;export const then = () => (value = value === Boolean(value))')
</script>
<script src="/main.js" type="module"></script>
import { then } from 'data:text/javascript,let value;export const then = () => (value = value === Boolean(value))'
console.log(then())
How it works? Pretty simple, since it's the same URL, called twice, it only invokes the dynamic module once... and since the dynamic import resolves the thenable objects, it resolves the then()
call alone.
Thanks to Guy Bedford for his idea about the { then }
export
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With