Passing Arguments to External JavaScript Files

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).

As I mentioned in a previous post, I recently began developing my own JavaScript library named helper.js.  Developing a library can be tricky because, as the library author, you never know exactly what environment your code will be executing in.  Therefore, your library should have as little of an effect on its surrounding environment as possible.  In its current state, helper.js stores all of its functionality in the variable, “$”.  Obviously this can conflict with user code, or other libraries such as jQuery.  Instead of relying on “$”, I had the idea to pass the the desired variable name as a parameter to the <script> element that includes the library.  The remainder of this post retraces my attempts at accomplishing this.

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:

var 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 it’s “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:

var scripts = document.getElementsByTagName("script");
var 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 (var i = 0, len = scripts.length; i < len; i++) {
  var src = scripts[i].getAttribute("src").split("?");
  var url = src[0];
  var args = src[1];

  if (!args)
    continue;
  // do work here
}

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 “name” argument, we can search for that particular argument.

var argv = args.split("&");

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

  if (argName !== "name")
    continue;
  // do work here
}

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.  After writing this article, I have decided not to pass arguments to helper.js at this time.


Comments are closed.