Passing Arguments to External JavaScript Files

on in AJAX and Fetching Data
Last modified on

This post covers the topic of passing arguments to external JavaScript files. I want to say up front that there doesn’t seem to be a 100% bulletproof way of doing this. If there is, I don’t know about it (so please share if you know how).

Note that this ideas should not be applied to a production environment without proper load testing.

Bad solutions for libraries

Throughout my adventures, I came across several potential solutions that were clearly not right for me. One idea was to use server-side programming to read the argument and customize the response. For a library, this idea isn’t feasible because users would become tied to my server. Many developers choose to host libraries on their own servers in order to remove external dependencies.

Another idea I came across was assigning an id to the <script> element and passing the arguments as data-* attributes. The resulting <script> tag would look something like this:

<script id="helper" data-name="helper" src="helper.js"></script>

The script could then use the id to programmatically locate itself and parse the arguments. Given the previous <script> tag, the name could be retrieved like this:

const name = document.getElementById('helper').getAttribute('data-name');

Unfortunately, this won’t work for a library because the script would always require the same ID. This is essentially the same as the original problem, except conflicts would occur with the HTML ID, instead of the JavaScript $ variable.

Passing arguments via the query string

In my opinion, the most logical way to pass an argument to a script file is through the query string of its src attribute. Using the query string, the previous <script> tag would look like this:

<script src="helper.js?name=helper"></script>

The problem now becomes retrieving the src attribute from the <script> element. Without an id, there is no way of getting a direct handle on the element. However, you can get a handle on all of the page’s <script> elements using getElementsByTagName(). From there, it becomes a matter of determining exactly which script to access. Normally, during page load, the script that is currently executing is the last element returned by getElementsByTagName(). This means that the correct <script> element can be located using the following code:

let scripts = document.getElementsByTagName('script');
let script = scripts[scripts.length - 1];

This code relies on the fact that scripts are normally executed in the order in which they are encountered by the browser. Unfortunately, this approach cannot be applied to scripts which are loaded asynchronously because there is no guaranteed execution order. And, because a library can potentially be loaded asynchronously, this approach is not applicable to libraries.

Another idea is to loop through each <script> element and inspect it individually. The following loop iterates over each <script> element. By splitting the src attribute on the ? character, the script’s URL is separated from the query string. We can then skip over any scripts that don’t have a query string.

for (let i = 0, len = scripts.length; i < len; i++) {
    let src = scripts[i].getAttribute('src').split('?');
    let url = src[0];
    let args = src[1];

    if (!args) {
        continue;
    }
}

The problem isn’t quite solved yet though. If multiple scripts have a query string, how do we distinguish between them? One technique is to look for the specific named argument. The following code extracts the individual arguments and their values from the query string. Since our example is using the argument, we can search for that particular argument.

let argv = args.split('&');

for (let j = 0, argc = argv.length; j < argc; j++) {
    let pair = argv[j].split('=');
    let argName = pair[0];
    let argValue = pair[1];

    if (argName !== 'name') {
        continue;
    }
}

Our code now identifies all scripts that pass a name argument through the query string. Unfortunately, as a library developer you have no way of knowing if your code will be run on a page alongside another script that also uses a name argument. You can try using an argument name which is very unlikely to be duplicated, but that approach is not 100% reliable either.

You can add another level of checking by also comparing the script URL, which we already stored in the url variable. The problem with this approach is that the script name must be hard coded, and renaming the script file requires the script to be edited. This normally wouldn’t be too much of a hassle, but it is unsuitable for libraries. If only there were a way to get the script’s name from within the script itself.

As it turns out, there is a way to get the script’s name. Unfortunately, the technique is Mozilla specific. Firefox’s Error objects have a fileName property which contains the full URL, including the query string, of the file where the Error originated. The following expression provides the URL of the file in which it is run. Of course, this solution is not applicable to libraries.

Error().fileName

Summary

Passing arguments to external scripts can be tricky. If you own the script, then you can easily make many of the techniques shown here work for you. The real problem is faced by library developers, whose code can be deployed in countless environments. Libraries can utilize many of these techniques, but as I mentioned at the beginning of this post, none of them are 100% bulletproof.

Related posts