-
Notifications
You must be signed in to change notification settings - Fork 788
Google Summer of Code 2015
- Title: ClojureScript & Google Closure (Project Idea)
- Mentor: David Nolen (swannodette)
- Student: Maria Geller (mneise)
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.
This project can be split up into two parts: module support and extern generation.
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
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.
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.
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.
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.
- Rationale
- Quick Start
- Differences from Clojure
- [Usage of Google Closure](Google Closure)