af83

One Ring to rule them all, One Ring to find them, One Ring to bring them all and in the darkness bind them.

One Ring to rule them all, One Ring to find them, One Ring to bring them all and in the darkness bind them.

Here the "them" doesn't refer to other rings, but to Javascript modules/packages, server side or browser/client side. This article is about running the same code on nodejs (server) and on the browser (client), without having to change nodejs module codes.

If you know Javascript, then you already know it is a good language to express asynchronous treatments. The Javascript language have other strong points, but weak points as well. One of the often quoted advantage of JS, is that it can run on both server and client. You can then share your code as needed, which avoid you the pain to have two code bases to do the same thing (like forms validation) : once on the browser, once on the server.

My goal here is to be able to run my server side JS (written for nodejs) on the browser, without having to change the sources manually nor passing by a "compilation / transformation" process I would have to run before testing on the browser side. Of course I don't want to start a TCP server from the browser, but I'd like to be able to reuse general purpose libraries at ease.

The first problem comes from the fact that there is no isolation (or namespaces) between different modules on browser side, so if there are not wrapped in a function, then you'll end-up polluting your global namespace. The second problem comes from the fact that "require" and "exports" statements are not standard JS, but a proposal from CommonJS, and so won't be defined in the browser by default. Luckily, James Brantly wrote Yabble - Yet Another (CommonJS) Browser Loader, which can solve these two problems (Thanks James!). Here is how we can use it:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr" id="html">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Yabble demo</title>

    <script type="text/javascript" src="/yabble.js"></script>
    <script type="text/javascript">
      require.reset();

      require.useScriptTags(); // to avoid using XHR
      require.setModuleRoot('wrapped_js/');

      require.ensure(['A','B'], function() {
        require.run('A');
      });
    </script>
  </head>

  <body>
    <h1>Yabble demo</h1>
  </body>
</html>

Your server has to be able to serve the following JS files: "/wrapped_js/A.js" and "/wrapped_js/B.js". Yabble will fetch those two JS modules and then run A (which can require B, since it has been loaded). The fine part with the "require.ensure", is that it will load JS modules in parallel.

By default, Yabble uses XHR requests to load the JS modules, and then eval them. This is not the most convenient way of using it since you won't be able to debug them. By writting

require.useScriptTags();

we ask Yabble to retrieve the module as scripts files; but the modules code then need to be wrapped as:

require.define({'B': function(require, exports, module) {
  // code module B
}}, ['A']); // B depends of A

Here we say that 'B' depends of 'A', so Yabble will ensure A has been loaded before running B. If we know for sure all dependencies are loaded, we don't have to specify them though.

Yabble features a tool that can wrap the modules for us, but it's pretty static (and I don't want to run it every time I change my sources). So I wrote some code (part of nodetk) that automatically serve JS files: you just have to specify which modules / packages / static files you want to serve, and it will do so. It also wraps automatically JS module files on the fly.

Example of use (on server side):

var http = require("http"),
    bserver = require('nodetk/browser/server');

// Here we create a http server...
var server = http.createServer();
// ... and do whatever we want with it

// we use that previous server to serve JS modules
bserver.serve_modules(server, {
  modules: ['assert', 'sys'],
  packages: ['nodetk', 'rest-mongo'],
  additional_files: {
    '/tests.html': __dirname + '/tests.html',
    '/tests.js': __dirname + '/tests.js'
  }
})

server.listen(8080)

Now with this code, on client side, we can do any of the followings:

var sys = require('sys'),
    assert = require('assert'),
    utils = require('nodetk/utils'),
    callbacks = require('nodetk/orchestration/callbacks'),
    rest = require('rest-mongo/core');

nodetk also features a javascript file to initialize some variables such as __dirname or process.

For a complete working example, I encourage you to have a look at nodetk tests, which are running on both server and client side (except for modules which can not run on browser): http://github.com/AF83/nodetk/tree/master/src/nodetk/browser_tests/ .

Pierre

blog comments powered by Disqus