Helma Logo
main list history
previous version  overview  next version

Version 33 by bard on 30. May 2009, 12:37

The javascript import features in *Helma NG* try to loosely imitate the *semantics of python's import statement|http://www.effbot.org/zone/import-confusion.htm* in order to allow applications to be written in a truly modular way. The magic of python's import statement is that a script never has to do anything special in order to avoid name clashes with other scripts, because every script lives in its one top level scope by default.

To sum it up:

# You don't have to use any special Javascript syntax to denote a namespace. Every script is by its nature a separate scope, assigned to a namespace by the script that imported it.
# It's virtually impossible to produce a name collision, as each script lives in its own scope.
# Since scripts live in their own scope, imported scripts are only visible to the scripts that imported them.

=== Implementation

Helma NG implements this by using a <a href="http://www.mozilla.org/rhino/scopes.html#sharingscopes">separate top-level scope</a> for each loaded script file, using a shared top-level scope as prototype. A simplistic implementation of most of the behaviour described above in the Rhino shell may look like this:

  // set reference to global object
  var __global__ = __global__ || this;
  
  function include(script, as) {
    var scope = new Object();
    scope.__parent__ = null;
    scope.__proto__ = __global__;
    // scope is now a top level scope that inherits
    // from the global, shared top level scope
    scope.load(script);
    this[as] = scope;
  }

Of course, Helma NG also takes care of script reloading and stuff, and the shared module scope is really a per-thread scope that uses a really-shared scope for the really-shared stuff.

=== Example

Suppose there is a Javascript file called <code>helma/database.js</code> with the following content:

  function getConnection(...) {
      ....
  }
  
  function Connection(...) {
      ...
  }
  
  function Query(...) {
      ...
  }

Then consider the following statements in another script file:

<dl>
<dt><b>import("helma.databasehelma/database")</b></dt>
<dd>This makes the functions defined in helma/database.js available in the current scope as
  helma.database.getConnection()
  helma.database.Connection()
  helma.database.Query()
</dd>
<dt><b>var db = require("helma.databasehelma/database")</b></dt>
<dd>This makes the functions defined in helma/database.js available in the current scope as
  db.getConnection()
  db.Connection()
  db.Query()
</dd>
<dt><b>include("helma.database")</b></dt>
<dd>This makes the functions defined in helma/database.js available in the current scope as
  getConnection()
  Query()
</dd>
</dl>

Note that the functions in helma/database.js do not care in which namespace they are loaded. They only see what is defined in their local file, and in the global scope shared by all modules. The scope that loaded the module is absolutely invisible to the loaded module! Also note that the getConnection() function in the last example can still access the Connection() constructor although it's not included in the importFromModule(). That's the power of closures!

=== Further Reading

There is a newer article about the *background and history|ng/background and history* of the Helma NG module system as well as a more up-to-date *technical article|ng/modules and scopes*.

     removed
     added