Michael J. Forster
Getting Started with Google Closure… and Lisp!

At Shared Logic, we use Google’s Closure Tools and Common Lisp to write and deploy rich web applications. In this article I demonstrate our approach by writing the Hello, World! described in Google’s Getting Started with the Closure Library.

First, I create a project directory and download the Closure Library:

mkdir -p ~/src/mjf/grok-google-closure-lisp/closure
cd ~/src/mjf/grok-google-closure-lisp/closure

svn checkout http://closure-library.googlecode.com/svn/trunk/ \
    closure-library

By convention and regardless of web server infrastructure, we serve static resources from a www sub-directory tree. Here, I link in the Javascript root of the Closure Library:

mkdir -p ~/src/mjf/grok-google-closure-lisp/www/js
cd ~/src/mjf/grok-google-closure-lisp/www/js
ln -s ../../closure/closure-library/closure/goog .

Second, I create a Lisp project using Zach Beane’s Quicklisp and Quickproject. I will use Hunchentoot to serve the application, CL-WHO to generate the HTML, and Parenscript to generate the Javascript:

CL-USER> (ql:quickload "quickproject")
...
=> ("quickproject")

CL-USER> (quickproject:make-project
          "~/src/mjf/grok-google-closure-lisp/"
          :depends-on '(hunchentoot cl-who
          parenscript))
=> "grok-google-closure-lisp"

CL-USER> (ql:quickload "grok-google-closure-lisp")
...
=> ("grok-google-closure-lisp")

CL-USER> (in-package #:grok-google-closure-lisp)
=> #<PACKAGE "GROK-GOOGLE-CLOSURE-LISP">

Third, I edit the grok-google-closure-lisp.lisp file to tweak some Hunchentoot settings and define variables and utility functions to manage the Hunchentoot acceptor and the dispatch table. I add a folder dispatcher to Hunchentoot’s DISPATCH-TABLE to serve static Javascript files:

;;;; grok-google-closure-lisp.lisp

(in-package #:grok-google-closure-lisp)

;;; "grok-google-closure-lisp" goes here. Hacks and glory await!

(setf hunchentoot:*catch-errors-p* nil) ; T for production
(setf hunchentoot:*show-lisp-errors-p* t)
(setf hunchentoot:*show-lisp-backtraces-p* t)

(defparameter *project-pathname*
  (merge-pathnames "src/mjf/grok-google-closure-lisp/"
                   (user-homedir-pathname)))

(defparameter *http-port* 4242)

(defvar *my-acceptor* nil)

(defun start ()
  (unless *my-acceptor*
    (push (hunchentoot:create-folder-dispatcher-and-handler
           "/js/"
          (merge-pathnames "www/js/"
                           *project-pathname*))
      hunchentoot:*dispatch-table*)
    (setf *my-acceptor*
          (hunchentoot:start (make-instance
                              'hunchentoot:easy-acceptor
                              :port *http-port*)))))

(defun stop ()
  (when *my-acceptor*
    (hunchentoot:stop *my-acceptor*)
    (setf hunchentoot:*dispatch-table*
          (last hunchentoot:*dispatch-table*))
    (setf *my-acceptor* nil)))

Fourth, I create and edit a hello.lisp file to define Hunchentoot easy handlers corresponding to the hello.js and hello.html from the Google example. I modify the script URLs according to our convention:

;;;; hello.lisp

(in-package #:grok-google-closure-lisp)

(hunchentoot:define-easy-handler (hello-js :uri "/hello.js") ()
  (setf (hunchentoot:content-type*) "text/javascript")
  (ps:ps
    (ps:chain goog (require "goog.dom"))

    (defun say-hi ()
      (let ((new-header
             (ps:chain goog dom
                       (create-dom "h1"
                                   (ps:create
                                    :style "background-color:")
                                    "Hello world!"))))
        (ps:chain goog dom
                  (append-child (ps:@ document body)
                                new-header))))))

(hunchentoot:define-easy-handler (hello :uri "/hello") ()
  (cl-who:with-html-output-to-string (*standard-output* nil :prologue t)
    (:html
     (:head
      (:script :src "/js/goog/base.js")
      (:script :src "/hello.js"))
     (:body :onload (ps:ps-inline (say-hi))))))

Fifth, I add hello.lisp to the components list of the system definition in grok-google-closure-lisp.asd, reload the project, and start the Hunchentoot acceptor:

GROK-GOOGLE-CLOSURE-LISP> (ql:quickload "grok-google-closure-lisp")
...
=> ("grok-google-closure-lisp")

GROK-GOOGLE-CLOSURE-LISP> (start)

Finally, I can browse the application URL http://localhost:4242/hello.

Yes, the Parenscript definition is a few lines longer than hello.js, and, yes, I could shorten it with Lisp macros. However, I have a better plan.

The Closure API, too, is more verbose than that of JQuery, Prototype, YUI, and other Javascript libraries. However, the latter are libraries only, and their use of terse naming conventions and minifiers to control code growth and its impact on performance is limited. The Closure Library is intended for use with the Closure Compiler, which provides greater optimisation than minification alone, including dead code elimination.

As Google does with it’s compiler, I will trade some terseness now for much greater gain later using Lisp’s magic in creating embedded domain specific languages. More to come.

  1. michaeljforster posted this