March 1999 Draft
JavaScript 2.0
Packages
previousupnext

Wednesday, March 24, 1999

Overview

Packages are an abstraction mechanism for grouping and distributing related code. Packages are designed to be linked at run time to allow a program to take advantage of packages written elsewhere or provided by the embedding environment. JavaScript 2.0 offers a number of facilities to make packages robust for dynamic linking:

A package is a file (or analogous container) of JavaScript 2.0 code. There is no specific JavaScript statement that introduces or names a package -- every file is presumed to be a package. A package itself has no name, but it has a specific URI by which other packages can import it.

A package P typically starts with import statements that import other packages used by package P. A package that is meant to be used by other packages typically has one or more version declarations that declare versions available for export.

Package Loading

A package's body is described by the Program grammar nonterminal. A package is loaded (its body is evaluated) when the package is first imported or invoked directly (if, for example, the package is on an HTML web page). Some standard packages may also be loaded when the JavaScript engine first starts up.

Two attempts to load the same package in the same environment result in sharing of that package. What constitutes an environment is necessarily application-dependent. However, if package P1 loads packages P2 and P3, both of which load package P4, then P4 is loaded only once and thereafter its code and data is shared by P2 and P3.

When a package is loaded, all of its statements are evaluated in order, which may cause other packages to be loaded along the way when import statements are encountered. A package's symbols are available for export to other packages only after the package's body has been successfully evaluated. Unlike in Java, circularities are not allowed in the graph of package imports.

To create packages A and B that access each others' symbols, we need to instead define a hidden package C that consists of all of the code that would have gone into A and B. Package C should define versions verA and verB and tag the symbols it exports with either verA or verB to indicate whether these symbols belong in package A or B. Package A should then be empty except for a directive (or several directives if there are multiple versions of A and verA) that reexports C's symbols tagged with verA. Similarly, package B should reexport C's symbols tagged with verB. To make this work we need a reexport directive. Is this really necessary? Also, do we want a mechanism for hiding package C from general view so that users can only use it through A or B?

Exports

We can export a symbol in a package by giving it public Visibility.

Imports

To import symbols from a package we use the import statement:

ImportStatement 
   import ImportList ;
|  import ImportList Block
|  import ImportList Block else CodeStatement
ImportList 
   ImportItem , ... , ImportItem
ImportItem 
   [[protectedIdentifier =NonAssignmentExpression [: Version]

The first form of the import statement (without a Block) imports symbols into the current lexical scope. The second and third forms import symbols into the lexical scope of the Block. If the imports are unsuccessful, the first two forms of the import statement throw an exception, while the last form executes the CodeStatement after the else keyword.

An import statement can import one or more packages separated by commas. Each ImportItem specifies one package to be imported. The NonAssignmentExpression should evaluate to a string that contains a URI where the package may be found. If present, Version indicates the version of the package's exports to be imported; if not present, Version defaults to version 1.

An ImportItem can introduce a name for the imported package if the NonAssignmentExpression is preceded by Identifier =. Identifier becomes bound (either in the current lexical scope or in the Block's scope) to the imported package as a whole. Individual symbols can be extracted from the package by using Identifier with the :: operator. For example, if package at URI P has public symbols a and b, then after the statement

import x=P;

P's symbols can be referenced as either a, b, x::a, or x::b.

If an ImportItem contains the keyword protected, then the imported symbols can only be accessed using the :: operator. If we were to import package P using

import protected x=P;

then we'd have to access P's symbols using either x::a or x::b.

If two imports in the same scope import packages with clashing symbols, then neither symbol is accessible unless qualified using the :: operator. If an imported symbol clashes with a symbol declared in the same scope, then the declared symbol shadows the imported symbol. Scope rules 3 and 4 apply here as well, so the following code is illegal because a is referenced and then redefined:

import x=P;
var y=a;     //
References P's a
const a=17;  //
Redefines a in same scope

Version names cannot be imported.

Discussion

Package Names

Do we want to use URIs to locate packages, or do we want to invent our own, separate mechanism to do this?

Visibilities

Should we make private illegal outside a class rather than making it equivalent to package?

Should we introduce a local Visibility prefix that explicitly means that the declaration is visible locally? This wouldn't provide any additional functionality but it would provide a convenient name for talking about the four kinds of visibility prefixes.

What should the default visibilities be? The current defaults are loosely modeled after Java:

Definition Location Default visibility
Package top level package (equivalent to local in this case)
Inside a statement outside a function or class   local
Function or method code's top level local
Inside a statement inside a function or method   local
Class declaration block's top level package
Inside a statement inside a class declaration block   local

Waldemar Horwat
Last modified Wednesday, March 24, 1999
previousupnext