Colors: Light | Dark
Welcome! (login)

Livenode — Live-code your NodeJS application

Feb
27
2013

Livenode — Live-code your NodeJS application

I'm working on a NodeJS application for Kendo UI. If you've been following me for a while, you probably know that I'm not the biggest fan of NodeJS1—however, it seems that for this project Node is the best option, so I actually pushed for it.

A small annoyance that I'm trying to “fix” is the requirement to restart the server all the time in order to test changes. After some years with Common Lisp, I know there are better ways to do development than restarting the program — so here's Livenode, a module that allows to “live-code” a NodeJS application.

What's “Live Coding”?

In essence, it's a way to alter the program while it runs, without restarting it. The goal, however, is to end up with a program that works properly when we (re)start it. To be useful, it helps to have an editor that can be customized to send code to the server.

It's easier to show this in a screen-cast, so I made one. Before you watch it, let me warn you that I've been told that “anyone who's seen a Dracula movie is familiar with the accent”. Go figure how good am I at making screen-casts :-). Just ignore this aspect and check out the demo.

The challenge

The NodeJS module system (really, the require function) wraps all the code in a module into something like this:

(function (exports, require, module, __filename, __dirname) {
    // and here goes the module code
})(...);

It does that so we can have the “luxury” to have file-local variables and functions, and be selective about what we export. What this means, however, is that everything inside the module is “private” and in order to provide a facility to evaluate code in the environment of the module the only way is perhaps to export a function which takes a piece of code and evaluates it there, something like the following (and again, I'm adding the CommonJS wrapper, to show the whole picture):

(function (exports, require, module, __filename, __dirname) {
    // ... module code here
    exports.$__EVAL = function() { return eval(arguments[0]) };
})(...);

It might seem that's everything Livenode has to do to make my screen-cast work; but it's trickier than that. If the code that you evaluate is like var counter = 0; then the counter variable could not escape the $__EVAL function. There is no way in JS2 to introduce names in an outer scope. The counter would get created inside the $__EVAL function, but that wouldn't be of any use. What we need is to create a proper “file-local” variable, so that we can access it later from another function defined inside the module.

Solution: code rewriting

The only solution I could think of was to rewrite all code inside a module such that there are no free “file-local” variables; instead, the module environment would be stored in a local object. Here is an approximation of what happens:

var foo = something();
function getFoo() {
    return foo;
}
function incFoo() {
    foo++;
}
var $__CONTEXT = {};
$__CONTEXT.foo = something();
$__CONTEXT.getFoo = function getFoo() {
    return $__CONTEXT.foo;
};
$__CONTEXT.incFoo = function incFoo() {
    $__CONTEXT.foo++;
};
exports.$__EVAL = function() {
    eval(REWRITE(arguments[0]));
};

That would be the initial transformation — when the program starts, all modules will be silently rewritten like above before being evaluated. Later when we need to execute a single statement, we call the $__EVAL function of the appropriate module, which applies the same rewriting (but of course, without redefining $__CONTEXT).

Handling exports

For convenience, assignment to exports is treated in a special way. Here's why. Suppose you have the following code:

function foo() { return 1 }
exports.foo = foo;
$__CONTEXT.foo = function foo() { return 1 };
exports.foo = $__CONTEXT.foo;

Later you'll evaluate only the definition of foo, to make it return 2, for example:

function foo() { return 2 }
$__CONTEXT.foo = function foo() { return 2 };

If you evaluate only this definition, without evaluating the exports line again, then even though you've changed the local function, the module's exports will still point to the old definition. So, if exports would not be handled in a special way, after each change to an exported function you'd need to “export it again”, that is, to re-evaluate the line that assigns that function to exports (or, well, to restart the server, but that's what we want to avoid).

To make this more convenient, Livenode will transform assignments to exports like the following:

exports.foo = foo;
exports.__defineGetter__("foo", function() {
    return $__CONTEXT.foo;
}), exports.__defineSetter__("foo", function(v) {
    exports.__defineGetter__("foo", function() {
        return v;
    });
});

Seems like a lot of trouble, but it helps: by making it a getter, rather than assigning the computed value at the time the assignment is made, it allows you to redefine that thing in $__CONTEXT without bothering to re-export it. For correctness, it also defines a setter which makes it behave like an ordinary property.

Caveat emptor

Livenode is a tool meant to be used only for development. Do not enable it in production—for one thing, the code it generates is obviously slower; on the other hand, you don't want someone else to connect to your server and evaluate arbitrary statements.

Not 100% keeping semantics

The code rewriting should ideally keep the semantics intact, but I had to draw somewhere the line between convenience and correctness. For example, as you've seen in the previous section, it does this exports magic, so that you can redefine stuff without exporting it again. Strictly speaking, this doesn't always preserve code logic—but it works well in every practical case I can imagine. Here's an example where it would fail:

// module foo.js
var foo = "foo";
exports.foo = foo;
foo = "bar";

// normally:
require("foo").foo // → "foo"

// with Livenode:
require("foo").foo // → "bar"

I hope it's obvious why that happens.

Be wary about caching exports lookups

A related issue (not exactly an issue, just something to keep in mind) is that if you “cache” the exported values, your caches won't be updated when the value changes in the module. For example:

var foo = require("foo");
var my_bar = foo.bar;

   //... in some function...
   my_bar();

When you will change the value of bar in the "foo" module, the my_bar variable will continue to point to the previous value. This is exemplified in the screen-cast with the "handle" function.

exports[i] = stuff

Some people populate exports by iterating through some other object with a for/in, like this:

for (var i in stuff) {
    exports[i] = stuff[i];
}
// rarely seen, but this kind of for/in is valid syntax
for ($__CONTEXT.i in stuff) {
    exports[$__CONTEXT.i] = stuff[$__CONTEXT.i];
}

Note that in the transformation we don't do any special magic for exports this time. It would be buggy, because if we delay evaluation of $__CONTEXT.i (if we'd put it in a getter) then it'll only get the last value of the loop variable i. If you do this, you must remember to evaluate this loop each time you modify an exported function, so that other modules pick up your change.

Only for CommonJS

As you can see, Livenode is specifically designed around the NodeJS module system. This means that if your modules circumvent somehow the “standard” module system, you won't be able to evaluate statements at run-time into them. Ironically, I can't use it on some of my own code (for example UglifyJS 2.0 does not use CommonJS, instead it implements its own file loader).

The editor interface

You might have noticed in the screen-cast that I'm using Emacs. I've been with it for like 15 years and I know it fairly well, so I was able to program the interface into it without much effort—but Livenode itself doesn't have much to do with Emacs. It should be trivial to add support for Your Favorite Editor, assuming it's programmable. You need a JSON library3 and a WebSocket client library4. I'd love to see support for VIM or Sublime Text, but I don't know them well enough to write it myself—patches welcome, and please drop me a line if I can help!

The Emacs interface is pretty simple:

If you kill the server, Emacs disconnects and notices you in the minibuffer. You need to M-x livenode again to reconnect after you restart the server (it doesn't do it automatically for the time being).

Footnotes
1. In fact, I have nothing against NodeJS itself; my main gripe about developing an application on this platform has to do with the language (JavaScript) — manually writing code in continuation-passing style (necessary to make the async machinery work) is simply not a nice way to write code. But after a while you get used to the taste — and anyway, this post isn't about that.
2. In no language I know, actually.
3. It's included by default in Emacs.
4. In Emacs we're using this one: https://github.com/ahyatt/emacs-websocket. WebSockets aren't, strictly speaking, a required protocol, but I thought I'd go with them to make it possible to add support for Web-based editors such as CodeMirror or Ymacs.
No comments yet. Wanna add one? Please?

Add your comment

Fork me on Github