Create a gist now

Instantly share code, notes, and snippets.

Embed
Generate an externs.js file for arbitrary JS libraries for use in advanced Google Closure compilation, based on the ClojureScript code that uses the libraries.
(ns n01se.externs-for-cljs
(:require [clojure.java.io :as io]
[cljs.compiler :as comp]
[cljs.analyzer :as ana]))
(defn read-file [file]
(let [eof (Object.)]
(with-open [stream (clojure.lang.LineNumberingPushbackReader. (io/reader file))]
(vec (take-while #(not= % eof)
(repeatedly #(read stream false eof)))))))
(defn file-analysis [file]
(binding [ana/*cljs-ns* 'cljs.user
ana/*cljs-file* file]
(mapv #(ana/analyze (ana/empty-env) %) (read-file file))))
(defn flat-file-analysis [file]
(mapcat #(tree-seq :children :children %)
(file-analysis file)))
(defn get-vars-used [ffa]
(->> ffa
(filter #(and (= (:op %) :var) (-> % :info :ns)))
(map #(-> % :info :name))
distinct))
(defn var-defined? [sym]
(contains? (:defs (get @ana/namespaces (symbol (namespace sym))))
(symbol (name sym))))
(defn get-undefined-vars [ffa]
(remove var-defined? (get-vars-used ffa)))
(defn externs-for-var [sym]
(if (= "js" (namespace sym))
(format "var %s={};\n" (name sym))
(format "var %s={};\n%s.%s=function(){};\n"
(namespace sym) (namespace sym) (name sym))))
(defn get-interop-used [ffa]
(->> ffa
(filter #(and (= (:op %) :dot)))
(map #(or (:method %) (:field %)))
distinct))
(defn externs-for-interop [sym]
(format "DummyExternClass.%s=function(){};\n" sym))
(defn externs-for-cljs [file]
(swap! ana/namespaces empty)
(ana/analyze-file "cljs/core.cljs")
(let [ffa (flat-file-analysis file)]
(apply str
(concat
(map externs-for-var (get-undefined-vars ffa))
["var DummyExternClass={};\n"]
(map externs-for-interop (get-interop-used ffa))))))
;; (externs-for-cljs "/home/chouser/proj/cljs-example/src/main.cljs")
@cemerick

This comment has been minimized.

Show comment
Hide comment
@cemerick

cemerick Jun 17, 2013

  1. Does this really work? ;-)
  2. Why the hell doesn't gclosure just do this automatically‽
  1. Does this really work? ;-)
  2. Why the hell doesn't gclosure just do this automatically‽
@Chouser

This comment has been minimized.

Show comment
Hide comment
@Chouser

Chouser Jul 28, 2013

  1. It's sufficient for some input sources, but I've probably missed some cases.
  2. Maybe because in ClojureScript we can tell the difference between interop, vars, and locals more easily than in JavaScript? Not sure.
Owner

Chouser commented Jul 28, 2013

  1. It's sufficient for some input sources, but I've probably missed some cases.
  2. Maybe because in ClojureScript we can tell the difference between interop, vars, and locals more easily than in JavaScript? Not sure.
@ptaoussanis

This comment has been minimized.

Show comment
Hide comment
@ptaoussanis

ptaoussanis Aug 5, 2013

This is terrific - just saved me some serious headache. Much appreciated!

This is terrific - just saved me some serious headache. Much appreciated!

@swannodette

This comment has been minimized.

Show comment
Hide comment
@swannodette

swannodette Nov 19, 2013

It's probably worthwhile to tidy this up and get it into the compiler.

It's probably worthwhile to tidy this up and get it into the compiler.

@micha

This comment has been minimized.

Show comment
Hide comment
@micha

micha Nov 27, 2013

This is a nice example of using the CLJS analyzer, too. Thanks @Chouser.

micha commented Nov 27, 2013

This is a nice example of using the CLJS analyzer, too. Thanks @Chouser.

@dertseha

This comment has been minimized.

Show comment
Hide comment
@dertseha

dertseha Dec 6, 2013

I'm a bit lost here; Without knowing ClojureScript (just got it working on my Windows machine) - how do I run this script to generate the externs? (Also: it is meant to create externals for plain .js files, right?)

dertseha commented Dec 6, 2013

I'm a bit lost here; Without knowing ClojureScript (just got it working on my Windows machine) - how do I run this script to generate the externs? (Also: it is meant to create externals for plain .js files, right?)

@markjfisher

This comment has been minimized.

Show comment
Hide comment
@markjfisher

markjfisher Jun 6, 2014

What version of clojurescript was this written/work against? I've just run it on some code of my own and I'm getting:
ClassCastException cljs.analyzer$reify__4241 cannot be cast to clojure.lang.Atom clojure.core/swap! (core.clj:2160)

I'm using 0.0-2202.

Also, for @dertseha there's a blog on using this at http://www.dotkam.com/2013/07/15/clojurescript-use-any-javascript-library/

What version of clojurescript was this written/work against? I've just run it on some code of my own and I'm getting:
ClassCastException cljs.analyzer$reify__4241 cannot be cast to clojure.lang.Atom clojure.core/swap! (core.clj:2160)

I'm using 0.0-2202.

Also, for @dertseha there's a blog on using this at http://www.dotkam.com/2013/07/15/clojurescript-use-any-javascript-library/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment