Bootstrapping a Synergy Web Application

A Synergy web application is just another xjs package. This structure of an xjs package resembles a Perl CPAN package or a Ruby Gem. In the case of the blog app I'm working on slowly, the directory structure looks like the following

blog/
  bin/
  lib/
    bootstrap.js
    app/
      actions/
        admin/
          articles.js
          sessions.js
          comments.js
        articles.js
        comments.js
      config/
        development.js
        environment.js
        production.js
        test.js
      models/
        Article.js
        Comment.js
      public/
        img/
        css/
        js/
      views/
        admin/
          articles.js
          comments.js
          layout.js
          sessions.js
        articles.js
        comments.js
        layout.js
  test/
  tmp/
  var/
    development.log

I have xjs packages named JSON, Logger, PostgreSQL, Assert, Synergy, etc. All of these packages have a similar structure to the the blog app package shown above. Some have src/, most don't have temp/ or var/ directories, but all have at least a lib/ directory.

It takes only two commands to get the blog up and running.

$ cd ~/svn/blog
$ xws
environment = development
port = 3000
2008-05-02 17:57:36.122::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
2008-05-02 17:57:36.124::INFO:  jetty-6.1.5
2008-05-02 17:57:36.153::INFO:  Started SocketConnector@0.0.0.0:3000

The xws program starts the web server: a Jetty embedded Java program. The xws server looks for and executes the lib/bootstrap.js file. This is our chance to bootstrap the blog application.

There is no magic in the bootstrapping. The developer is in complete control of which files load and in which order. Too much magic is a bad thing and looking back at the history of Rails with various debacles of components, lib, plugins, engines, plugin loading order and excluding packages, it is clear that developers need control. The good news is that the absence of magic does not necessarily mean it is hard work to bootstrap the app.

For the blog the bootstrap.js file can be as simple as

require('Synergy');
require(__DIR__ + '/app/config/environment.js');
require(__DIR__ + '/app/config/' + ENVIRONMENT + '.js');
require(__DIR__ + '/app/models/*.js');
require(__DIR__ + '/app/views/**/*.js');
require(__DIR__ + '/app/actions/**/*.js');

The first line loads the Synergy web framework which in turn loads other packages on which Synergy depends. If a developer wanted to load a subset of the Synergy-related packages, he could instead specify each package with his own require() line and clip together a web framework of his liking.

The __DIR__ macro is expanded by the xjs JavaScript core interpreter to the full path to the directory containing the file. For the app.js file, on my development computer, it expands to "/Users/peter/svn/blog/lib" including the quotation marks. This gives us a nice way to load the files specific to the package.

The ENVIRONMENT global variable is set up by Synergy and is used in app.js to load the appropriate environment-specific file.

The ** and * wildcard require statements load the models, views and actions files.

So even with no framework magic, it only takes six lines to bootstrap the blog. By writing these few lines (or more likely having them generated when the blog package is generated) we have complete control. If we need to control the loading order more specifically it is not a series of confusing hoop jumps that you may have had to do with some other framework. Sometimes less magic is simpler.

Comments

Have something to write? Comment on this article.

Kris Kowal May 6, 2008

It would be really cool if we could share ideas and converge with our API's. modules.js supports a "require" method just like yours, and it provides a "moduleUrl" and a "modulesUrl" variable in module scope from which your "DIR" variable with my "relative" and "resolve" routines in http.js. In Python, that'd be dirname(file) in any module. I also support a special case in my "require" function so that the module URL passed to "require", if it begins with a ".", is effectively resolved relative to "DIR".

modules.js doesn't support a glob syntax in "require", and probably can't since it's infeasible to do so with XHR without server-side DAV-like support, but that's a really neat feature. You might consider support an additional "..." syntax in your glob for searching up the directory hierarchy for a file like a scope-chain lookup.

require(".../base.js");

Another probable distinction is that my "require" function returns a singeleton module object. I suspect that yours does something more like my "include" function, either copying the names exported by another module (by being attributed to the module object, "this" or "module", during its evaluation) or inserting the module object onto the scope chain. I do the former. I don't imagine that you simply eval the requested module once since you do have to insert the DIR var in its scope chain.

I'm thinking that, to help cross-portability, I could create a name like moduleDir and change moduleUrl and modulesUrl to moduleFile and modulesFile respectively. Alternately, I could go as far as bending to Python style file and dir. I want to avoid using UPPER_CASE and even lower_case if I can help it. I've managed to dispense with that silliness in my polymorphic method names, like "string" instead of "str" or "toString".

Do your modules have a private local scope? modules.js modules get a four-scope chain:

  • window
  • module
  • module scope {"include": ..., ...}
  • anonymous function enclosure

This makes it so "var" locals are private to the module and names imported from another module with "include" get added to the module scope instead of the module so they are also private. "included" names can also be exported with a "publish" function in the module scope.

Peter Michaux May 6, 2008

Hi Kris,

My require function takes a string argument and searches the file system for a matching file(s) and/or module(s). I do just eval the required files in the global context.

I haven't added any particular scoping rules. The JavaScript is just plain old JavaScript. When Rhino gets the ECMAScript 2.0 packages then that will bubble its way into my project naturally if various xjs modules start to use them.

I've never been inclined to add infrastructure to JavaScript to simulate larger programming concepts like packages or classes. When I have a choice, I try to keep things as simple as I can.

Kris Kowal May 7, 2008

I concur that it is great to "Keep It Simple". That being said, solving complex problems without a simple, exercised, and crystalline foundation tends to force user code to be repetitive and complex. To that end, I've tried to contain complexity and provide simplicity. This reduces the amount of boilerplate that modules need to be robust and simple.

In any case, I would love to change my code to accommodate easier portability. If other people are explicating their dependencies, I'd like to offer the joy of having their code "just work" in so far as I can on the platform that I'm building. If we can converge on our API, we could potentially merge our developer communities.

What excites me about your project is that modules written for your system are very, very nearly the same as those written for modules.js. Take a look at the top of this one:

https://cixar.com/svns/javascript/trunk/src/widget/console.js

That means that a little bit of work in a very contained section of your code base at any time in the indefinite future could very easily advance the state of the art without requiring a mass edit of all of your community's code-if we converge on a common API before we have a huge community!

I don't know about you, but I'm positively giddy with the possibilities for sharing. This could be the beginning of the next open source success story.

Peter Michaux May 7, 2008

Kris, I've posted a new article where you can download my alpha modules. I would like to stay with a module system and directory structure like I've been using. I think that is one of the things I do have "right". Eventually I'd like to see the module system beefed up to the functionality of CPAN.

Have something to write? Comment on this article.