April 2002 Draft
JavaScript 2.0
Core Language
Packages
previousupnext

Monday, March 4, 2002

Defining Packages

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 defined using the following syntax:

PackageDefinition 
   package Block
|  package PackageName Block
PackageName 
   Identifier
|  PackageName . Identifier

When a package is defined, it may, but is not required to, be given a PackageName, which is a series of dot-separated identifiers. It is implementation-defined what the restrictions, if any, are on naming packages to avoid clashes with other packages that may be present.

The Block contains the body of a package P. The Block is evaluated at the time package P is loaded. Any public top-level definitions are available to other packages that import package P. Any public class and interface member definitions are available to all other packages, regardless of whether they import package P. Top-level and class and interface definitions defined by P in another namespace N are available to other packages only if they use namespace N or qualify the access with namespace N.

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 are loaded when the JavaScript engine first starts up. When a package is loaded, its statements are evaluated in order, which may cause other packages to be loaded along the way when import directives are encountered. Circularities are not allowed in the graph of package imports.

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.

Javascript does not support package definition circularities (two packages A and B that each import the other), although an implementation may provide such a facility as an extension.

Referencing Packages

A package P can reference another package Q via an import directive:

ImportDirective 
   import ImportBinding IncludesExcludes
|  import ImportBinding , namespace ParenListExpression IncludesExcludes
ImportBinding 
   ImportSource
|  Identifier = ImportSource
ImportSource 
   String
|  PackageName
IncludesExcludes 
   «empty»
|  , exclude ( NamePatterns )
|  , include ( NamePatterns )
NamePatterns 
   «empty»
|  NamePatternList
NamePatternList 
   QualifiedIdentifier
|  NamePatternList , QualifiedIdentifier

An import directive may be preceded by attributes; however, all such attributes must evaluate to either true or false.

There are two ways an import directive can name a package to be imported:

If provided, ParenListExpression should be a list of namespaces provided by the package. These namespaces are used by the import statement. In order to resolve name conflicts between packages, IncludesExcludes provides finer-grain control over which names are imported. include or exclude clauses specify which sets of names are shared as top-level variables. If include is used, only the listed names are made accessible; if exclude is used, all names except the listed ones are made accessible. For example:

package My.P1 {
  explicit namespace N;

  N const a = "global a";
  N const b = "global b";
  N class C {

    static var x = 2;
  }
  N const c = new C(i:5);     // Initializes c.i to 5
  const x = "global x";
}

package My.P2 {
  import P = My.P1, namespace(N), exclude(N::b, x);  // Imports My.P1 and uses namespace N, excluding N::b and  x
  c;                          // OK; evaluates to the instance of class C
  N;                          // Error: N not found because it’s explicit
  P.N;                        // OK; evaluates to namespace N in package My.P1
  a;                          // OK; evaluates to "global a"
  b;                          // Error: N::b not found because it’s excluded
  P.b;                        // OK; evaluates to "global b"
  (P.N)::b;                   // Error: N::b not found because it’s excluded
  x;                          // Error: the global x not found because it’s excluded
  C.x;                        // OK; evaluates to 2
}

If no include or exclude clause is listed, the effect is the same as if exclude() were listed.

An import directive does the following:

If package P has a public top-level definition n and package Q imports P using import PkgP = P, then package Q can refer to n as either n or PkgP.n. The shorter form n is not available if it conflicts with some other n. To avoid polluting its top-level scope, package Q can import package P using either import PkgP = P, include() or import PkgP = P, exclude(n), in which case package Q can refer to n only as PkgP.n.

If package P has an explicit top-level definition n and package Q imports P, then package Q can refer to n only as PkgP.n.

If package P has a top-level definition n in namespace N and package Q imports P using import PkgP = P, then package Q can refer to n as either PkgP.N::n or N::n (in either of these the name N has to be accessible as well, which may require qualifying it if the accessibility of N is not public or using (PkgP.N) instead of N if the accessibility of N is explicit). Package Q can instead import P using import PkgP = P, namespace(N) to be able to refer to n as plain n, barring name collisions. Alternatively, package Q can execute import PkgP = P followed by use namespace(N) (or use namespace(PkgP.N)) to achieve the same effect.


Waldemar Horwat
Last modified Monday, March 4, 2002
previousupnext