In JavaScript running in a browser, to get the current script URI, one can write like in https://stackoverflow.com/a/57364525/3102264:
let currentPath = import.meta.url.substring(
0, import.meta.url.lastIndexOf("/"));
This works fine in a modern browser, however in an old browser it raises a SyntaxError
.
I would like to detect if import.meta
is supported in order to use location.href
when it is not available.
I tried using try catch like
let currentPath;
try {
currentPath = import.meta.url.substring(
0, import.meta.url.lastIndexOf("/"));
}
catch(e) {
currentPath = location.href.substring(
0, location.href.lastIndexOf("/"));
}
But this is not working, it still throws
Uncaught SyntaxError Unexpected token import
Is there a way to make conditional code depending on import.meta
support?
Note: using location.href is an acceptable fallback when served from same based url (even it did not solve what allow import.meta)
As noted in the comments, if you want a fallback for browsers not supporting import.meta
, it’s not going to be as simple as replacing import.meta.url
with location.href
. One will return the URI of the executing script, but the other will return the address of the page on which it runs, which may even be on a completely different domain.
That said, I managed to come up with a feature-detector that relies neither on user-agent sniffing, nor on dynamic import()
expressions:
const isImportMetaSupported = () => {
const execModule = (src) => {
const sc = document.createElement('script');
sc.type = 'module';
sc.textContent = src;
document.body.appendChild(sc);
sc.remove();
};
const js = (pieces, ...values) =>
String.raw({ raw: pieces }, ...values.map(x => JSON.stringify(x)));
const gensym = () => {
let ident;
do {
ident = `${2 * Math.random() / Number.EPSILON}$$`;
} while (ident in window);
return ident;
};
return new Promise((ok, ko) => {
const $ok = gensym();
window[$ok] = ok;
execModule(js`
window[${$ok}](true);
import.meta;
`);
execModule(js`
window[${$ok}](false);
delete window[${$ok}];
`);
setTimeout(
() => ko(new TypeError("modules unsupported?")), 1000);
});
};
(async () => {
const haveImportMeta = await isImportMetaSupported();
console.log(
`import.meta is ${haveImportMeta ? 'supported' : 'unsupported'}`);
/*
if (haveImportMeta)
[load a script that uses import.meta]
else
[load a script that uses a fallback solution]
*/
})()
How it works: the detector attempts to execute two scripts. If import.meta
is supported, the first script executes and resolves the promise with a true
value; if import.meta
is not supported, the first script triggers a syntax error and does not execute. The second script always executes and attempts to resolve the promise with a false
value; if the first script had already executed, this has no effect. I do use a few other advanced features, but those should be much easier to replace or polyfill than import.meta
itself.
It’s a bit finicky: it relies on module scripts being executed in the order they are injected, which I am not sure is actually guaranteed. If you worry about that, you can insert a couple of setTimeout
s and let sleepsort deal with it.