dean.edwards.name/weblog/2007/12/packages/

Organise Your Code With base2.Packages

A base2.Package provides a mechanism for bundling classes, constants and functions within a closure. You can define what symbols you want to export from the Package and you can define the symbols you want to import into the closure.

Here is the template for creating a base2 Package:

new function(_) { // create the closure
  // create the package object
  var MyPackage = new base2.Package(this, {
    name:    "MyPackage",
    version: "1.0",
    imports: "SomeOtherPackage",
    exports: "MY_CONSTANT,MyClass,myFunction"
  });
  
  // evaluate the imported namespace
  eval(this.imports); 
  
  // define package contents
  
  var MY_CONSTANT = 42;
  
  var MyClass = SomeOtherClass.extend({
    // class definition
  });
  
  function myFunction() {
    return "Hello!";
  };
  
  // evaluate the exported namespace (this initialises the Package)
  eval(this.exports);
};

By running the above code we will have created a package that lives on the base2 object:

base2.MyPackage              // => "[base2.MyPackage]"
base2.MyPackage.MY_CONSTANT  // => 42
base2.MyPackage.myFunction() // => "Hello!"

The base2.MyPackage namespace is initialised by the call eval(this.exports). You must make this call at the end of the closure that contains your package objects.

Referring to base2.MyPackage.MY_CONSTANT is a tad verbose so base2 Packages have a special property (namespace) which defines all of the symbols that the package exports. The namespace property for our example above looks like this:

print(base2.MyPackage.namespace);
// => var MyPackage=base2.MyPackage;var MY_CONSTANT=MyPackage.MY_CONSTANT;var MyClass=MyPackage.MyClass;var myFunction=MyPackage.myFunction;

We can now evaluate the namespace property to gain access to the Package properties as if they were defined in the current scope:

eval(base2.MyPackage.namespace);

myFunction()  // => "Hello!"
MY_CONSTANT   // => 42

You can eval the namespace property in the global scope to globalise the contents of your Package. Or you can perform the eval within a closure to keep the global namespace clean. This is the mechanism that allows us to import the namespaces of other packages into your own package. That is what the call to eval(this.imports) in the example above provides. The namespaces of the base2 and JavaScript packages are automatically imported into any package and you don’t need to define them in the imports property.

Here’s a fully fledged example:

new function(_) { // create the closure
  // create the package object
  var shapes = new base2.Package(this, {
    name:    "shapes",
    version: "1.0",
    exports: "PI,Circle,Rectangle"
  });
  
  // evaluate the imported namespace
  eval(this.imports); 
  
  var PI = 3.14;
  
  var Shape = Base.extend({
    constructor: function(x, y) {
      this.move(x, y);
    },
    x: 0,
    y: 0,
    getArea: Undefined,
    move: function(x, y) {
      this.x = Number(x);
      this.y = Number(y);
    }
  });
  
  var Circle = Shape.extend({
    constructor: function(x, y, radius) {
      this.base(x, y);
      this.radius = Number(radius);
    },
    radius: 0,
    getArea: function() {
      return PI * Math.pow(this.radius, 2);
    }
  });
  
  var Rectangle = Shape.extend({
    constructor: function(x, y, width, height) {
      this.base(x, y);
      this.width = Number(width);
      this.height = Number(height);
    },
    width: 0,
    height: 0,
    getArea: function() {
      return this.width * this.height;
    }
  });
  
  // evaluate the exported namespace (this initialises the Package)
  eval(this.exports);
};

Here’s another example that imports the Package we declared above:

new function(_) { // create the closure
  // create the package object
  var graphics = new base2.Package(this, {
    name:    "graphics",
    version: "1.0",
    imports: "shapes",
    exports: "Layout"
  });
  
  // evaluate the imported namespace
  eval(this.imports);
  
  // we can refer to the Rectangle class directly because we have imported the
  // shapes Package.
  var Layout = Rectangle.extend({
    // I don't know anything about graphics
  });
  
  // evaluate the exported namespace (this initialises the Package)
  eval(this.exports);
};

I hope that I’ve made a reasonable job of explaining how base2 Packages work. All of the base2 code is organised into packages. By importing and exporting namespaces you end up with more readable code. File sizes are smaller too.:-)

Comments (8)

Leave a comment

Thanks Dean,

As first glance I was a bit confused until I realized Packages were a way to add plugins to base2. Great work.

  • Comment by: Justin
  • Posted:

Seems like a very clean API – but isn’t eval evil?;)

I like “import Package as Alias” which I use as “var Alias = base2.Package”. base2 looks very impressive.

  • Comment by: daaku
  • Posted:

[…] His latest installment is on base2 packages: A base2.Package provides a mechanism for bundling classes, constants and functions within a closure. You can define what symbols you want to export from the Package and you can define the symbols you want to import into the closure. […]

[…] His latest installment is on base2 packages: A base2.Package provides a mechanism for bundling classes, constants and functions within a closure. You can define what symbols you want to export from the Package and you can define the symbols you want to import into the closure. […]

This is genius. Now that I’ve read up on ES4’s namespaces, I think I could appreciate some in ES3.

Great job on this one.

@daaku: eval has its valid uses, and this (importing into the local scope) is one of them.

  • Comment by: grey
  • Posted:

For the longest time I could not wrap my brain around your “new function(_) { … };” constructs used here, until I recently realized it was half of the “new function(_) { … }(this);” construct, meant to pass the global object to the scope in a _ variable (right?). I’d suggest either losing the _, add the (this), and/or mention what the _ argument is somewhere in the post.

@Johan – don’t break your brain trying to understand the new function(_) syntax. I use this to provide access to an undocumented feature of packer. Basically, variables defined in a closure with this signature will not be processed by packer. That’s all. No magic.:-)

  • Comment by: -dean
  • Posted:

[…] Array-Funktionen kann ich somit schon mal weglassen, ebenfalls können wir die schöne base2.Package-Funktion nutzen. Dies ermöglicht, dass alle Funktionen in einem eigenen Namespace sind. […]

Comments are closed.