Reputation: 2635
Is this even possible? i see that on non-module script tags you can use document.currentScript
is there an equivalent for modules?
I've come across import.meta.url
which returns the module url. Is there something similar to get the script element itself?
Upvotes: 0
Views: 1507
Reputation: 155708
Yes and no.
When the script in-question is a JS module in its own .js
file that's directly loaded into a <script type="module" src="">
with the src=""
attribute:
import ... from 'anotherModule.js'
statement)<script>
elements where the src=""
attribute matches the import.meta.url
value.In all other cases, it isn't really possible - simply because the only real escape-hatch (currentScript
) is purposefully disabled for JS modules (not that it was ever really reliable in the first place: e.g. it doesn't work when used by DOM event-listener callbacks).
I just thought of a possible (ugly) workaround:
PerformanceObserver
in an inline non-module (global) script at the very top of the document.'element'
and 'resource'
event-types:Use 'element'
to look for any and all <script>
elements.
Use 'resource'
to log every HTTP request for script resources, including script modules loaded via indrect import
statements.
Unfortunately, while the PerformanceResourceTiming
object does have initiatorType
(which will just be a string value like 'script'
) it doesn't expose the actual DOM element that initiated the request, despite Chrome's DevTool's Network's "Initiator" tab showing the full chain (see fig below).
However you can use the DOM to inspect the raw source of every <script>
element and resource as it arrives and (using some ugly regex) attempt to parse every import
statement and so build the load-chain yourself by mapping every import.meta.url
to paths parsed via said regex.
And then expose that log table and mapping information via a window
extension property, e.g. window.myScriptModuleMap
, which could look like this after it's populated:
window.myScriptModuleMap = {
'scripts/myModule.js': {
importedBy: [
'scripts/anotherModule.js'
],
isSrcForScriptElement: null
},
'scripts/anotherModule.js': {
importedBy: [],
isSrcForScriptElement: /* a HTMLScriptElement obj */
}
};
So when you're inside a module, your script would use window.myScriptModuleMap
and walk the chain:
// scripts/myModule.js:
function getMyScriptElement() {
return getMyScriptElementInner( import.meta.url );
}
function getMyScriptElementInner( url ) {
const entry = window.myScriptModuleMap[ url ];
if( entry ) {
if( entry.isSrcForScriptElement ) return entry.isSrcForScriptElement;
else if( entry.importedBy.length === 1 ) {
return getMyScriptElementInner( entry.importedBy[0] );
}
else {
throw new Error( "TODO: Decide what to do when 2 or more modules share an import." );
}
}
else {
throw new Error( "No request log entry for \"" + url + "\"" );
}
}
Figure 1: The "Initiator chain" tab:
3:
Upvotes: 3