You're viewing all posts tagged with hacks

How I hack on node apps

splitw

First, there’s my ‘splitw’ command, which I use to run long-running process in a split screen.

Code on github

XRefresh

Then, there’s the XRefresh extension for Firefox. It requires a separate server. Check it out from github: https://github.com/YouWoTMA/xrefresh-server then create a symlink to the xrefresh-server (an executable python script).

I run it like this:

xrefresh-server -e ".git/" .

Supervisor

And also, there’s ‘supervisor’ node package; it runs js (or coffee) files and watches them automatically; when they change, it restarts the server.

Install it from npm:

npm install supervisor

Assumptions

I assume the app’s starting is always server.js (this is actually because of the webbynode hosting). And I assume there’s a git repo that I want to constantly work with as I hack.

I can still write the app in coffee-script, just have to make server.js look kinda like this:

require("coffee-script")
require('./app.coffee')

And then I have to pass -e "coffee|js" to supervisor so that it also watches .coffee files for changes (by default it only watches files with .js or .node extensions).

Putting them together

So here’s a simple command:

splitw 'supervisor -e "coffee|node|js" server.js' 'xrefresh-server -e ".git/" .' 'bash'

This splits the terminal to 3 horizontal panes, the first running the ‘supervisor’ node reloader, the second running xrefresh-server, and the last one just a normal shell so I can create git commits.

hack hack hack

I save that command to ~/bin/gonode and so when ever I want to hack on some node app,

~$ cd code/<app>
~/code/<app>$ vim server.js

Then hit ctrl-shift-t to open a new tab (it inherits the current working directory), and in it:

~/code/<app>$ gonode

And it will fire the server, the firefox refresher, and leave a pane for the shell.

The screen split kinda looks like this:

First hack with Arc

At the end of the arc tutorial there’s an example of a webapp:

(defop hello req (pr "hello world"))
(asv)

(asv) starts the http server (on port 8080 by default). defop defines a request handler (apparently).

You can save this file as “web.arc” for example, and run it from the command line with:

$ arc web.arc   

Note: arc here is a symlink to arc.sh from the Anarki repo.

This will start the web server and you can go to http://localhost:8080/hello

This is a good start for a webapp, but being spoiled by Flask and it’s auto-reloading of python modules, something doesn’t feel quite right.

I want to make changes to “web.arc”, save the file, and have the server automatically reload it.

I asked on the arc forum, and evanmurphy (the guy behind tryarc.org) suggested I could run the server in a thread (thread (asv)) and have the REPL available, this way I can always just (load "web.arc")

Now the problem is, I’m putting the (asv) inside the “web.arc” itself, so if I just reload it, it would start the server again, and that would be bad.

So after some googling, I found you can check if a symbol is bound using

(bound 'symbol)

So instead of:

(= t* (thread (asv)))

We can wrap it in a check:

(if (no:bound 't*)
  (= t* (thread (asv))))

This way we can reload the file without affecting the server thread.

I later expanded this to a more general macro that sets a symbol only once

(mac set-once (s v)
   `(if (no:bound ',s)
      (= ,s ,v)))

(set-once t* (thread:asv))

The second part of the problem is, I wanted the reload to happen automatically when I save the file. It didn’t look like there was any way to do that already, so I had to write one.

We basically want to watch the modification timestamp for changes, and reload the file if it changes. We’ll have to poll the file system for changes.

The function to get the modified timestamp is mtime, which you have to load form “lib/files.arc” (see below for more discussion about that).

My first attempt was kind of a kludge, but then I refactored it.

This function watches the return value of a function func and runs a function on-change when that value changes:

(def watch-fn (func on-change)
     (let init (func)
         (while (is init (func))
            (sleep 2))
     (on-change)))

Ideally I want to watch (mtime file-name) for changes, and have (load file-name) run when that value changes.

I’d have to call:

(watch-fn (fn () (mtime file-name)) (fn () (load file-name)))

There is a bit too much (fn () (...)) boilerplate, so we can macroize it:

; watch the value-exp expression for changes
; and run on-change-exp when it changes
(mac watch (value-exp on-change-exp)
     `(watch-fn (fn() ,value-exp) (fn() ,on-change-exp)))

Now we can:

(watch (mtime "web.arc") (load "web.arc"))

But we’re repeating “web.arc” here, so, one more layer:

(def watch-file (file-name deleg)
     (watch (mtime file-name) (deleg file-name)))

Now we can:

(watch-file "web.arc" load)

But we can still add one more layer:

(def auto-reload (file-name)
     (prn "w/auto-reloading " file-name)
     (watch-file file-name load))

And now we have:

(auto-reload "web.arc")

And it’s working! This feels so cool :D

Now, I mentioned something earlier about mtime being defined in “lib/files.arc”. This is not a problem if your webapp is in the same directory that arc is installed to. But in my case, it wasn’t, so I wanted a way to import that file from anywhere.

As I mentioned, the arc command I’ve used is actually a symlink to arc.sh from the Anarki repo. In that file, arc_dir is used as a variable to hold the directory where arc is installed. If we export it, it will be available to the arc runtime.

At first I was worried that if we export it it would pollute the command line environment, but it won’t. Executing .sh files that have export commands doesn’t actually affect the environment variables in your shell (at least not in bash). It would only pollute your environment if you source the file.

So with that aside, we can grab the environment variable ‘arc_dir’ and use it to build the full path to “lib/files.arc”.

Now, how do we get these environment variables? Well, looking at the definition of mtime, it just calls $.file-or-directory-modify-seconds, and you know what, file-or-directory-modify-seconds is a library function in MzScheme.

(def mtime (path)
  " Returns the modification time of the file or directory `path' in
    seconds since the epoch. "
  ($.file-or-directory-modify-seconds path))

In other words, since Arc is implemented in MzScheme, the standard library is available to Arc. Browsing a bit through it reveals getenv and build-path, so we can get the full path to “lib/files.arc” using:

($.build-path ($.getenv "arc_dir") "lib/files.arc")

Or generalize that as:

(def import (path) (load ($.build-path ($.getenv "arc_dir") path)))

Now we can (import "something.arc") from any where, and it will load “something.arc” relative to the directory where Arc is installed.

Now, this hack is a kludge, so be a bit careful with it if you ever decide to use it.