Modules and Namespaces

Ejscript provides a powerful module facility to group and control the visibility of a set of declarations. Ejscript modules are somewhat similar to packages in ActionScript or Java. Modules can contain classes, variables or functions and provide two key benefits:

Modules are created via the module block directive and are utilized (import) by the use module directive. Modules are optional. Your global does not need to be wrapped in a module directive. If you choose not to have a module directive, your code is implicitly added to the default module.

Under the hood, modules use the namespace facility to qualify declaration names. All names in Ejscript actually have two parts: A namespace qualifier and the primary name. The primary name is what JavaScript developers typically use when creating a variable or function. The namespace qualifier comes from the module or from an explicit namespace declaration. Even though all names have namespace qualifiers, standard ECMAScript code will run unaltered because Ejscript uses sensible defaults for namespace qualifiers if they are not provided.

Module Directive

The module directive groups and qualifies a set of declarations with a unique namespace qualifier so that they will not collide with other names.

module acme.rockets {
    var version = "1.0"
    class Apollo() {
        function launch() {
        }
    } 
}

This creates a module named acme.rockets and qualifies the version and Apollo declarations with the module name as a namespace qualifier. Thus the fully qualified names of these declarations are:

Ejscript applies the module qualifier automatically for you and you will rarely need to use explicit module or namespace qualifiers.

It is important that you choose a unique module name that won't clash with other modules. A reverse domain name is one safe option. For example: com.embedthis.appweb.

Source Filenames

Unlike Java or ActionScript, modules do not have to have any particular name or be defined in a special directory structure. A module name has no connection to the filename of the source file or files containing the module. You can choose any directory layout or file naming for your modules.

This is important because modules can be comprised of multiple source files that contribute code into a module. The Ejscript compiler will aggregate the code and combine it into a single module. Source files can also contain multiple module directives.

However, if you want the compiler and Ejscript loader to automatically locate modules, you need to observe the module search strategy conventions when you install your module into a repository.

Using Modules

To use a access a module and use it in your code, you need to add a require directive to your code.

require acme.rockets
rocket = new Apollo

The require directive adds the "acme.rockets" module namespace to the set of open namespaces and thus makes visible the declarations defined in the acme.rockets module.

If you import two modules and these both provide a declaration of the same name, you may need to use an explicit namespace qualifier. For example, if Module1 and Module2 both provide a variable named version, then the following code would select the appropriate variable:

require Module1
require Module2
print("Module1"::version)     /* Prints the version from Module1 */
print("Module2"::version)     /* Prints the version from Module2 */
print(version)                /* Prints Module2::version */

Without the explicit module qualifier, the set of open namespaces is searched in reverse order. Thus the most recently imported module will be searched first. Thus Module2::version will be printed. Note that the module name is used within quotes as a string literal namespace.

Module Dependencies

By utilizing the require directive, you create a dependency from your code to the requested module. Ejscript manages the dependencies of modules so that the Ejscript compiler and loader can load not just the module you specify, but also all the modules that it depends upon. This occurs transparently to you. In this manner, a tree graph of module dependencies is created. Note that you cannot have two mutually dependent modules.

A side benefit of using module dependencies is deterministic order of initialization. When one module depends on another, that module is fully loaded and initialized before the depending module. This greatly simplifies initialization issues for larger programs.

Module Files

If using the stand-alone compiler, the byte code for compiled modules can be saved in module files. These are similar to Java jar files and contain the complete module in a compiled form. Module files provide fast loading and execution of byte code as the code does not need to be parsed, compiled or generated before it can be run.

Typically the compiler will create a module file with the same name as the module name but with a .mod extension added. This means if we compiled the "acme.rockets" module, we would get a file "acme.rockets.mod".

home> ejsc rockets.es
home> ls -l acme.rockets*
-rw-r--r--  1 john  wheel  58 Feb 16 13:08 acme.rockets.mod

If the source file contained multiple module directives, then multiple mod files will be created.

Module files however, can contain multiple logical modules. This is useful when packaging an entire application as a single module file. You can instruct the compiler to merge all the modules into a single output file by using the --out switch.

home> ejsc --out acme.mod rockets.es dynamite.es
home> ls -l acme.rockets*
-rw-r--r--  1 john  wheel  58 Feb 16 13:17 acme.mod

In this case, the acme.rockets and the acme.dynamite modules are both saved into the one output file.

To run a module file, you can use the ejs shell command.

ejs acme.mod

Locating Modules

When the Ejscript compiler, VM or shell commands are used, they will search for modules according to the environment variable EJSPATH. This environment variable contains a set of directories to search to locate the required module files.

EJSPATH is similar to the system PATH variable. On Unix like systems, EJSPATH is a colon separated set of directories. On windows, it is semicolon delimited. If EJSPATH is not defined, it is set by default to point to the Ejscript system module repository. On Unix systems, this is usually located at /usr/lib/ejscript/modules.

The module search strategy convention is as follows. Given a module named "a.b.c", the Ejscript module loader will search for:

  1. File named a.b.c
  2. File named a/b/c
  3. File named a.b.c in EJSPATH
  4. File named a/b/c in EJSPATH
  5. File named c in EJSPATH

If the module has a native shared library extension of the same name, it will be loaded when the .mod file is loaded. If the module has any dependent modules, they will be loaded before loading the requested module.

Ejscript Module Repository

Ejscript stores the core language modules in the Ejscript module repository. This repository is a directory of modules where the modules are stored such that each portion of the module name is a directory. This equates to option #4 above in the module search strategy. For example, a module named com.embedthis.ejs.mod would be stored as "com/embedthis/ejs.mod" in the module repository.

Namespaces

Namespaces provide the machinery for Ejscript to qualify names and modules. A namespace is a qualifier for a variable, method or class. It prefixes the variable name and thus extends the name to include the namespace qualifier. This can be thought of as two-dimensional names.

Namespaces are primarily an internal mechanism. Ejscript uses them for modules and the public, private, internal visibility qualifiers. However, you may have the occasional requirement to explicitly use them.

Creating a Namespace

Namespaces are created via the "namespace" directive.

namespace fast
namespace slow

Namespaces are variables just like any other, they can be assigned and stored in other variables. In fact, namespace variables can themselves be qualified via other namespaces.

Using Namespaces

Once the namespaces are created, they can be used to qualify declarations. Consider the example:

function render() {
    /* Default render */
}
fast function render() {
    /* Fast implementation */
}
slow function render() {
    /* Slow and thorough implementation */
}
fast::render()
slow::render()

This will define three functions, all called render, but each is qualified with a different namespace. You can choose which declaration you want by qualifying the variable via "namespace::". However it is more useful to request a namespace be added to the set of open namespaces and have the resolution occur implicitly.

render()                /* Runs the default render() */
use namespace fast
render()                /* Now runs the fast render() */

You can also modify the default namespace used when declaring variables:

use default namespace public
var openToAll

The use default namespace directive modifies the default namespace for all subsequent directives in the enclosing block (or file). The default namespace is internal.

© Embedthis Software. All rights reserved.