Contents

Introduction

Oni Apollo is an open-source StratifiedJS implementation that contains everything you need for stratified programming in the browser (including IE6+ and mobile) or on the server (based on node.js). Apollo is written in JavaScript and hence requires no browser extensions or plugins.

In addition to the StratifiedJS language itself, Apollo implements a CommonJS-compatible, cross-domain capable module system and it comes with the Apollo Standard Module Library, a collection of modules for client and server-side stratified programming.

Apollo is targeted at development of large, complicated, dynamic, scalable web applications, although its compact size (the browser lib is ~20KB gzipped) means it works well for smaller scale applications too.

The client and server versions of Apollo are completely independent; you can run Apollo just in the browser or just on the server.

Try it right here

You can evalutate StratifiedJS right here in this inline Apollo console:

Try typing in

require('apollo:twitter').search('stratifiedjs');

This will return an object with recent tweets mentioning "stratifiedjs". Behind the scenes it asynchronously loads the twitter module and then performs a request to the twitter API. No need for callbacks - with StratifiedJS you can code with asynchronous code in a conventional, sequential style.

This is only scratching the surface though. StratifiedJS provides native constructs for coordinating and composing multiple simultaneous code paths in rich and modular ways. Find out more in the StratifiedJS documentation.

Downloading/Installing Apollo

Apollo is hosted on GitHub. You can download a zip file with the latest stable release (0.13) here:

Alternatively, you can clone the github repository and check out the stable branch or the development master branch:

> git clone git://github.com/onilabs/apollo.git
> cd apollo

Latest stable branch:
> git checkout 0.13

Development master branch:
> git checkout master

There's no need to run any buildscript - everything is already pre-built. You only need to rebuild (by running ./apollo ./src/build/buildscript.sjs) if you make modifications in the src/ directory.

Using Apollo in the browser

To use Oni Apollo in the browser, load the oni-apollo.js script in your app's HTML, and place your StratifiedJS code into <script type="text/sjs"> tags:

<html>
  <head>
    <script src="http://path/to/your/oni-apollo.js"></script>
    <script type="text/sjs">
      // Your SJS code here!
    </script>
  </head>
</html>

The oni-apollo.js script is located in the apollo/ directory. You can serve this file from any location on your webserver. No other files are required for client-side Apollo. However, if you want to use any modules from the Apollo Standard Module Library, then you need to serve the modules/ directory from your webserver as well (relative to the location of the oni-apollo.js script).

Apollo comes with a small webserver with which you can server up the apollo/ directory directly. See Using Apollo on the server, below.

Instead of hosting Apollo yourself, you can directly hotlink to one of the versions hosted at code.onilabs.com/apollo/:

On document load, Apollo scans the document for <script> elements with type set to "text/sjs", transform the StratifiedJS code it finds to 'base-level' JS (this sounds heavy, but it is actually very fast!), and executes it.

Examples of client-side SJS applications

Fork-A-Twitter-Client

Shows how to use the twitter @anywhere API to write a complete twitter client without server-side code.

The Module Browser

Creates on-the-fly documentation for SJS modules.

Using Apollo on the server

For server-side Apollo use, you need to have a recent version of nodejs installed and reacheble via your $PATH.

To execute StratifiedJS files or for a Read-Eval-Print Loop, run the apollo executable (located in the apollo/ root directory):

Usage: apollo [options] [script.sjs [arguments]]

Without a script.sjs argument you get dropped into a stratified REPL.

Options:
  -h, --help        display this help message
  -e, --eval STR    evaluate STR

Apollo also comes with a small webserver with which you can e.g. serve up the apollo/ directory for client-side use:

Usage: rocket [options]

Options:
  -h, --help         display this help message
      --port PORT    server port (default: 7070)
      --host IPADDR  server host (default: localhost; 
                                  use 'any' for INADDR_ANY)
      --root DIR     server root (default: /Users/alex/test4/apollo/)
      --cors         allow full cross-origin access (adds 
                     'Access-Control-Allow-Origin: *' headers)

You can run ./apollo and ./rocket straight from the apollo/ root directory, or you can install them using npm (see the package.json script).

The Module System

Apollo implements a CommonJS-like module systems, which allows you to load StratifiedJS code modules in the same way on both the server and the browser.

A module is a file with extension *.sjs containing StratifiedJS code which, on loading, will be evaluated in its own scope. Variables and functions defined within the module will not be seen by other modules or by top-level code unless explicitly exported.

You export symbols from a module by adding them to the variable exports:

// mymodule.sjs
var B = 123;
function add(a,b) { return a+b; }
exports.A = 456;
exports.calc = function(a,b) { alert(add(a,b)); };

In this module, B and add would be hidden from the outside world, whereas, A and calc would be visible.

To load a module, use the function require(), passing in a module identifier:

  // load mymodule.sjs from same directory as caller:
  var mymodule = require("./mymodule.sjs"); 

  // 'sjs' extension is optional:
  var mymodule = require("./mymodule");

  // load from absolute HTTP URL:
  var mymodule = require("http://my.server.com/mymodule");

  // load module foo directly from GitHub
  // (https://github.com/afri/testmodules, branch master):
  require("github:afri/testmodules/master/foo");

  // load a module from the Standard Library:
  var http = require('apollo:http');

  // load from file URL (works on server only):
  var mymodule = require("file:///Users/alex/mymodule");

  // load a module from nodejs path (server only):
  var mynodemodule = require("nodejs:mynodemodule");

  // load a built-in nodejs module (server only):
  var fs = require("nodejs:fs");
  var fs = require("fs");

Modules will be loaded once during the lifetime of the program; subsequent require calls to the same module will return the cached exports object. To get information about which modules are currently loaded, where they were required from, etc., you can inspect the require.modules object.

Apollo Standard Module Library

Apollo comes with a set of modules called the "Apollo Standard Module Library". Each release of Apollo is paired with a matching release of the Module Library. The APIs of all modules of the current stable release (0.13) are documented at onilabs.com/modules. For the documentation of the current development master branch see http://code.onilabs.com/apollo/unstable/doc/modules.html.

You can load modules from the Standard Library modules by using the apollo: scheme, e.g.:

  var http = require('apollo:http');

By default, the apollo: scheme resolves to

In the browser:

  http://[path where 'oni-apollo.js' was loaded from]/modules/

On the server:

  file://[path to 'apollo' executable]/modules/

You can override these locations; see "Module resolution", below.

Module resolution

require accepts module identifiers that are relative or absolute URLs. If the URL does not end contain an extension extension, require will append an '.sjs' extension automatically.

The way module identifiers are resolved can be customized through the require.hubs variable. This variable is an array of [prefix, replacement_string|loader_object] pairs. For a given module identifier, Apollo traverses this array in order, looking for prefix matches. Prefixes are replaced by replacement_strings until a loader_object is found. E.g. on the server, the prepopulated require.hubs array looks something like this:

[ [ 'apollo:', 'file:///Users/alex/apollo/modules/' ],
  [ 'github:', { src: [Function: github_src_loader] } ],
  [ 'http:', { src: [Function: http_src_loader] } ],
  [ 'https:', { src: [Function: http_src_loader] } ],
  [ 'file:', { src: [Function: file_src_loader] } ],
  [ 'nodejs:', { loader: [Function: nodejs_loader] } ] ]

A request to "apollo:http" will resolve to "file:///Users/alex/apollo/modules/http". This new URL matches the file: prefix, for which the require.hubs array contains a loader_object entry specifying that the source code should be loaded via the built-in file_src_loader function.

To map apollo: modules to a different location, you can replace the pre-populated entry in require.hubs, or just prepend a new pair, e.g.:

require.hubs.unshift(["apollo:",
                      "http://mydomain.com/apollo-mirror/"]);

// all modules addresses as 'apollo:' will now be loaded from
// the location above.

There is also a module-local require.alias variable, which performs prefix replacement similar to require.hubs, but is only applies to the current module:

require.alias.mymodules = "http://code.mydomain.com/modules/";
var mymodule = require("mymodules:mymodule");

There is also a facility for hooking external compilers (like CoffeeScript) into the require mechanism. See this Google Groups post for details.

Cross-domain module loading on browsers

The standard builtin module retrieval system is capable of cross-domain loading of modules (i.e. where the module's URL differs from the domain of the document performing the require), on both modern and legacy browsers.

For this mechanism to work on modern browsers, the webserver hosting the modules needs to be configured to send CORS access control headers.

For legacy webbrowsers that don't support CORS (IE6,7, Opera), require will attempt a JSONP-like request to the module URL appended with '!modp'. The file found at this location is expected to consist of a single JS function call module(sjs_module_src), where sjs_module_src is a string representation of the StratifiedJS module code. As an example, http://code.onilabs.com/apollo/0.13/modules/http.sjs!modp is the 'modp' version of http://code.onilabs.com/apollo/0.13/modules/http.sjs.

The modp version can be generated programatically on the serverside (we'll publish a tool for that at some point), or hand-coded.

Instead of utilizing this builtin CORS/modp retrieval mechanism, you can also implement your own loading scheme by hooking into the require.hubs variable described in the previous section. See also the section on loading GitHub modules, below.

Loading GitHub modules

Note: As of June 2012, loading modules from GitHub doesn't work in Apollo 0.12 and 0.13 any longer. It does, however, work in the current development master branch (see our Apollo GitHub repo) which will soon be released as version 0.14. We're not planning to restore the functionality for 0.12 and 0.13.

In Apollo 0.12 and greater, require.hubs is pre-configured to load modules with a 'github:' prefix directly from GitHub (much like the 'apollo:' prefix is configured to load from the canonical Standard Module Library location - see above). The syntax looks like this:

require("github:USER/REPO/BRANCH_OR_TAG/MODULE_PATH");

E.g. to load the module https://github.com/afri/testmodules/blob/master/foo.sjs and call its hello function, you could write:

var foo = require("github:afri/testmodules/master/foo");
foo.hello();

The GitHub module loading process works cross-browser and without any intermediate proxies. The browser talks directly to the GitHub API using JSONP-style requests.

The loading functionality also works transitively. I.e. if you load a module from GitHub that in turn references another module through a relative url (e.g. require('anothermodule')), it will load fine through this mechanism.

This GitHub functionality comes in pretty handy for those situations where you just want to quickly try out a 3rd party module, or if you want to share modules during development without constantly having to upload to a webserver. In fact it works so well, it can be used in (small-scale) production as well. E.g. the xlate Chrome extension uses it to pull in a sjs4chromeapps support script:

var tabutil = require('github:afri/sjs4chromeapps/master/tab-util');

Interaction with normal JS

You can develop in StratifiedJS just like you would in conventional JavaScript. StratifiedJS is just a superset of JavaScript, so most JavaScript programs should work just fine in StratifiedJS. You can also freely call JS code (such as your favorite UI libraries) from SJS and vice versa.

Note that SJS when called from JS might not return what you expect. If the SJS function suspends while being called from JS, it will return a continuation object; not it's actual return value. That's the expected behaviour: Normal JS code cannot suspend and wait for the actual return value - this is one of the reasons for having SJS in the first place! See this Google Groups post for a mechanism of getting normal JS code to wait for SJS functions.