Skip to content

Google Summer of Code 2015

Maria Geller (Neise) edited this page Jul 24, 2015 · 3 revisions

Project information

Abstract

This Google Summer of Code Project will focus on improving the integration of ClojureScript with the existing JavaScript ecosystem. The first part of this project will be to add CommonJS, AMD and ECMAScript 6 module support, meaning that JavaScript modules can be included and used in a ClojureScript project. An important part in solving this problem will be to get familiar with the Google Closure Compiler as it provides the functionality to process JavaScript modules. The second part of this project will focus on generating extern files for non-Closure compatible JavaScript libraries to simplify the use of JavaScript libraries in a ClojureScript project.

Synopsis

This project can be split up into two parts: module support and extern generation.

Module support

Currently, it is not possible to directly include a CommonJS, AMD or ECMAScript 6 JavaScript module into a ClojureScript project. To add support for JavaScript modules we can use functionality that is already provided by the Google Closure Compiler. The Google Closure Compiler offers the functionality to transform CommonJS and ECMAScript 6 modules to Google Closure libraries and to transform AMD modules to CommonJS modules. To choose the right compilation settings, we can extend the ClojureScript compiler options. The ClojureScript compiler already provides the compiler option :foreign-libs to include non-Closure compatible JavaScript libraries:

:foreign-libs [{:file "hello.js"
                :provides  ["my.hello"]}]

This option could be extended with an optional :module-type key which can either have the value :commonjs, :amd or :es6:

:foreign-libs [{:file "hello.js"
                :provides  ["my.hello"]
                :module-type :commonjs}]

Similar to other JavaScript libraries, a JavaScript module can be added to a ClojureScript namespace by requiring it with the name that has been specified as the value under the :provides key:

(ns hello.core
    (:require [my.hello]))

Once a module has been transformed into a Closure library, it can be added as a dependency with goog.require() which is supplied by Closure Library's dependency management system.

We also need to be able to map the namespace of the module, which will be generated by the Google Closure Compiler, and the namespace the user is using in the ClojureScript project. The following example shows how a simple CommonJS module might be transformed by the Google Closure Compiler.

var my = {};
my.hello = {
    greet: function(name) {
        return "Hello " + name;
    }
};

exports.hello = my.hello;

converts to:

goog.provide("module$hello");
var module$hello = {};
var my$$module$hello = {};
my$$module$hello.hello = {greet:function(name) {
  return "Hello " + name;
}};
module$hello.hello = my$$module$hello.hello;

In this example we would need to map module$hello to my.hello. Fortunately, the Google Closure Compiler provides the method toModuleName in the classes ProcessCommonJSModules and ProcessEs6Modules which we can use to get a module name for a JavaScript module:

(ProcessCommonJsModules/toModuleName "hello.js")
;; returns module$hello

Extern Generation

At the moment, to be able to use a non-Closure compatible JavaScript library in a ClojureScript project, we also need to provide an extern file if we want to make use of the advanced compilation option which is provided by the Google Closure Compiler. This extern file includes the symbols that we are calling from our ClojureScript code. Providing an extern file will stop the Google Closure Compiler from renaming calls to external symbols. In ClojureScript code, all calls to external JavaScript libraries have to be prefixed with js/:

(.greet js/my.hello "World!")

This makes it possible to identify calls to external libraries. The previous code can also be written as:

(def hello (.-hello js/my))
(.greet hello "World!")

This means we need to track that hello is a JavaScript object and need to be able to infer that resulting calls to it are external calls. This can be achieved by annotating the AST during the analyze phase. Using this information we could then generate the extern files during the compilation phase.

Community benefits

Module support

Until ECMAScript 6 there was no built-in support for modules in JavaScript. CommonJS and AMD are both module specifications that have been created due to the lack of a modules system in JavaScript. With Node.js and RequireJS those module systems have become quite popular and there are many libraries that target one of those or even both module specifications. Adding CommonJS, AMD and ECMAScript 6 support to ClojureScript would allow users to reuse JavaScript libraries that have been written with a specific module system in mind.

Extern generation

Currently, there are different efforts to help with the generation of extern files, such as lein-externs and CLJSJS. While those projects make it easier to include extern files for JavaScript libraries, it means that a user needs to be aware that an extern file is needed and also requires the user to choose the right tool for it. Adding the ability to the ClojureScript compiler to generate those extern files would lower the barrier for including external JavaScript libraries, especially for someone who is new to ClojureScript.

Deliverables

At the end of this project it should be possible to include CommonJS, AMD and ECMAScript 6 modules into a ClojureScript project. Furthermore, extern files for all observed method invocations and property accesses on external JavaScript libraries should be generated by the ClojureScript compiler.

Clone this wiki locally