Samuel
Samuel

Reputation: 2635

Get the currently executing script element inside a type="module" script

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

Answers (1)

Dai
Dai

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:

    • (This is assuming the module is not loaded indirectly via an import ... from 'anotherModule.js' statement)
    • Then you can traverse the DOM and look for <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:

  1. Instantiate a new PerformanceObserver in an inline non-module (global) script at the very top of the document.
  2. Use it to immediately start monitoring for '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 */
        }
    };
    
  1. 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: enter image description here


Upvotes: 3

Related Questions