Bootstrap Javascript Library


Introduction

One of the major defects in Web client scripting is the inability to factor a script into components. Not only do most programming or scripting languages have this ability, but even CSS allows a large stylesheet to be broken into several smaller ones -- without requiring multiple documents to be updated -- only the original stylesheet needs to reference the new files. In marked contrast, the DOM offers no direct means to do this with scripts. Currently, a document author is expected to link each script into an HTML document via a <script> node within the <head>.

This poses a significant maintainance burden on the author, who is encouraged to perturb the file structure is little as possible rather than ongoingly factor it. One can expect script files to be owned by particular documents, and common functions to be duplicated between them. Or perhaps reusable components do live in a separate file -- but they must be linked in each document that directly or indirectly uses them -- it's up to the author to keep track of such implicit dependencies and specify them explicitly. If one of these libraries is again factored into smaller pieces, the author must update every document. And does a page depend on all of those new scripts, or just some? What about another page?

Web developers realized years ago that it's possible to work around this deficiency. Just use the DOM -- create a <script> element, set the type and src attributes, and insert it into the document head. Voila! The script runs -- problem solved! ...Or is it?

If you're intending to use a site's files in situ (e.g. for development), then you'll need to use relative pathnames -- and now we have a new problem. Suppose we have this directory tree:

htdocs/

The documents index.html and foo/bar.html both reference js/common.js directly, and common.js depends on utils.js. No problem -- we use dynamic import, right? At the top of common.js we write Bootstrap.import( "js/utils.js" ) -- simple enough. But that only works for index.html -- since the pathname is relative to the document, for bar.html the pathname needs to be "../js/utils.js". But how is common.js supposed to know which document it's running in? We can look at document.URL and count the path components below htdocs, but now we're doing more work than it seems should be necessary and asking for trouble besides -- this will break if there's another htdocs within the hierarchy, and anyway requires hard-coded knowledge that differs from the filesystem to the Web server.

Ideally, we'd like to supply pathnames relative to the calling script -- not the document. Also, all of the logic must be contained in the Bootstrap module -- no passing in extra data like the caller's (hard-coded) pathname, which is brittle (in the event the file moves, you have to update it) and ugly to boot. One method call, one parameter. That's it.

First, when a script is called dynamically, we need to know the caller's pathname so we can resolve the supplied pathname relative to it. We'll maintain a call stack for this purpose, and when one script is called we consult the top of the stack to determine the caller. However, this doesn't work at the top level, where a script that the document references (and was not called by us) is importing another script. In this case, the null call stack clues us in that we need to look elsewhere. It just so happens that the actual DOM structure of the document is constructed in a particular order: Each script is run immediately after the insertion of its <script> node, and prior to the insertion of subsequent nodes from the HTML stream. So the last <script> in the document head is the one currently running.

Unless, of course, it called us to import another script, which is the whole point. If we insert a script dynamically, the node does in fact appear in the head after the currently running script. So we distinguish by giving our inserted nodes a distinct class name. Now, the last <script> which doesn't have this class name is the currently running one (unless the document author spoofed us, in which case he deserves what he gets).

But it's not as simple as that -- scripts don't execute immediately upon insertion of a <script> node, while the script performing the insertion is still running (reentering the interpreter). Rather, they are queued until after the inserting script exits. If a caller imports multiple scripts, and the first of those also imports one of its own, then we need some way to run in between that grandchild script and the next child so we can adjust the stack. In fact, we need to run before and after each called script to accomplish this. Our solution: Instead of queueing one script at a time, we insert three -- a header, the called script, and a trailer. The header and trailer scripts have the same src attribute as bootstrap.js itself, so it's already loaded. We just have to check to see if we're running for the first time, and if not, whether we're in header or trailer mode so we can push or pop the stack.

Of course, it's not as simple as that, either. When we insert ourselves as a header or trailer, Safari actually DOES run us immediately! So before we do that we have to run a reentrancy test. We'll set a flag and insert ourselves in test mode. In this mode our script will just clear the flag and exit. After insertion, if the flag has been cleared then we're in Safari and must compensate. Otherwise, we're in Firefox or Camino, and the test run will occur after we exit. In the former case, all we have to do is make the URL unique by appending an ever-changing query.

The resulting system works in Safari and Firefox/Camino on Mac OS, both from the filesystem and from HTTP. In the future I'll be testing other browsers as well.