Ian Bicking: the old part of his blog

Javascript packaging thoughts

Looking at some slides on JS 2, I think it mostly looks pretty good -- the type stuff seems complex, but at least not terribly intrusive. There's one thing I don't like:

package org.mozilla.venkman {
  class Debugger { . . . }
  class Inspector { . . . }
  class StackWalker { . . . }
}

I think package names as a whole just don't make sense; when I do x = 1 then 1 doesn't know it is named x, the same should apply to packages. Package names bother me in Python, but even more in this explicit form (with a package declaration).

Like Python, all scripts should be modules (in Python terminology), automatically. That is, you don't choose whether or not you are creating a module, you just are, always.

Unlike Python, packages shouldn't get names. Dealing with locating packages, resolving conflicts or dependencies, etc., is just tedious and unnecessary. Instead, use something like:

venkman = import('venkman.js')

This loads the given script by URI (relative to the script itself) as a module, and returns that module. Mostly this is just dead simple and obvious, and naturally safe. Maybe it should be syntax, as import * is still a reasonable operation, especially for backward compatibility. Or maybe syntax like var * = import('venkman.js') to unpack an object (in this case a module) into the local namespace.

Like in Python, all modules should know where they are located, similar to __file__. Maybe a __uri__ variable. All relative imports are relative to this location. This way you can do simple factoring without worrying about "name", just relative structure of your package. Unlike in Python, there's no __name__.

There still needs to be a way to include external libraries (which wouldn't necessarily be accessible through relative paths). This is a similar distinction of C's #include "foo.c" and #include <foo.c>. Maybe import('venkman.js', 'venkman.mozilla.org'), where the second argument is the name of the framework or package (venkman.js is the name of the module). I suppose org.mozilla.venkman looks nice as a package name to people who program Java; but venkman.mozilla.org looks nice to people who surf the web.

This second form of importing requires some way to find the libraries (which is just as true for explicitly declared packages, or maybe even worse). I imagine some routine like loadPackage(filename, packageName). This could return a package or null -- if null then the next loader would be tried. So, an example:

registerLoader(function (filename, packageName) {
    try {
        return import('/js-lib/' + packageName + '/' + filename);
    } catch (e if e isinstance ImportError) {
        return null;
    }
});

This would give a global search path of /js-lib/. Some of these kinds of loaders should be easy to create (implemented as standard functions), perhaps declaratively through a <link>, since way to find scripts is intrinsic to the site, not the script. Whether loading the same script/URI twice produces the same module, I'm not sure -- probably. Some languages distinguish between the two (e.g., require vs. include in PHP).

Packages could install local loaders. So you could package your library with all its dependencies, and register a loader for that, coming after all the other loaders.

Maybe in addition to importing by package, version should also be allowed. So, import(filename[, packageName[, version]]). Though it would have to be a version specification, like >=1.0,<2.0`, not just a single version... and that's relatively hard to do, since there's no generic way in plain HTTP to list the contents of a directory and do a search. I'd be inclined to leave that out, maybe allowing for:

venkman = import('venkman.js', 'venkman.mozilla.org');
if (venkman.__version__ < '1.0' || venkman.__version__ >= '2.0') {
    venkman = import('./packages/venkman-1.0/venkman.js');
}

Or some externally-provided search path, e.g.:

{
    // fancyDebugger is picky about the version of venkman it uses...
    let loader = registerLoader(
        pathImporter('./packages/venkman-1.0'));
    fancyDebugger = import('fancyDebugger.js');
    deregisterLoader(loader);
}

Which reminds me that JS2 should include encapsulated initialization/finalization. This would be another kind of anti-lesson from Python -- we've taken too long to add this (with in Python 2.5), and Javascript shouldn't put it off. It's easier to build Javascript function inline and pass them around, but it's still not used a great deal, in part because it requires a functional style that is unfamiliar to most programmers. Maybe it should learn from Ruby's block arguments, though I'm not sure if the specifics translate well. Maybe:

withLoader(pathImporter('./packages/venkman-1.0'), {{
    fancyDebugger = import('fancyDebugger.js');
}})

That specific syntax doesn't really appeal to me, but there must be something like it that could work. The way JS2 uses let feels similar, but doesn't actually work in the same way.

Created 29 May '06