Browse files

CLJS-2061: Support ns :require for JS libs, allow strings along with …


The strategy employed by this patch is to defer processing JS modules until
after we have found the sources and computed the "missing modules" in NS
declarations. `cljs.analyzer/parse-ns` returns the extra `:missing-js-modules`
key, which it infers by looking at string declarations in dependencies.
  • Loading branch information...
anmonteiro authored and swannodette committed Jul 4, 2017
1 parent 76ffac3 commit dd5d86223f359e06434a152ad0ea5e0bee317459
@@ -0,0 +1 @@
@@ -0,0 +1,13 @@
# ClojureScript string-based requires demo
1. At the root of the ClojureScript repo, run `./script/bootstrap`
2. Switch into this directory: `cd samples/string-requires-npm-deps`
3. Build the project:
``` shell
$ java -cp `ls ../../lib/*.jar | paste -sd ":" -`:../../src/main/cljs:../../src/main/clojure:src clojure.main build.clj
4. run the generated JavaScript with `node out/main.js`
@@ -0,0 +1,13 @@
(require '[ :as b])
(b/build "src"
{:output-dir "out"
:output-to "out/main.js"
:optimizations :none
:verbose true
:target :nodejs
:compiler-stats true
:main 'foo.core
:npm-deps {:react "15.6.1"
:react-dom "15.6.1"}
:closure-warnings {:non-standard-jsdoc :off}})
@@ -0,0 +1,3 @@
"name": "string-requires-npm-deps"
@@ -0,0 +1,10 @@
(ns foo.core
(:require [react :refer [createElement]]
["react-dom/server" :as rds :refer [renderToString]]
(println "resolves single exports" create-react-class)
(println (renderToString (createElement "div" nil "Hello World!")))
@@ -30,7 +30,7 @@ md.on('package', function (pkg) {
if ( != null) {
pkgJson.provided = [ ];
pkgJson.provides = [ ];
if (pkg.main != null) {
@@ -49,16 +49,26 @@ md.on('end', function() {
for (var i = 0; i < pkgJsons.length; i++) {
var pkgJson = pkgJsons[i];
if (deps_files[pkgJson.main] != null && pkgJson.provided != null) {
deps_files[pkgJson.main].provides = pkgJson.provided;
if (deps_files[pkgJson.main] != null && pkgJson.provides != null) {
deps_files[pkgJson.main].provides = pkgJson.provides;
deps_files[pkgJson.file] = { file: pkgJson.file };
var values = [];
for (var key in deps_files) {
var dep = deps_files[key];
if (dep.provides == null && !/node_modules\/[^/]*?\/package.json$/.test(dep.file)) {
var match = dep.file.match(/node_modules\/(.*)\.js(on)*$/)
if (match != null){
dep.provides = [ match[1] ];
@@ -2106,13 +2106,13 @@
(str msg "; offending spec: " (pr-str spec)))
(defn basic-validate-ns-spec [env macros? spec]
(when-not (or (symbol? spec) (sequential? spec))
(when-not (or (symbol? spec) (string? spec) (sequential? spec))
(error env
(parse-ns-error-msg spec
"Only [lib.ns & options] and lib.ns specs supported in :require / :require-macros"))))
(when (sequential? spec)
(when-not (symbol? (first spec))
(when-not (or (symbol? (first spec)) (string? (first spec)))
(error env
(parse-ns-error-msg spec
@@ -2210,7 +2210,7 @@
(recur fs ret true)))))
(defn parse-require-spec [env macros? deps aliases spec]
(if (symbol? spec)
(if (or (symbol? spec) (string? spec))
(recur env macros? deps aliases [spec])
(basic-validate-ns-spec env macros? spec)
@@ -2222,7 +2222,11 @@
[lib js-module-provides] (if-some [js-module-name (get-in @env/*compiler* [:js-module-index (str lib)])]
[(symbol js-module-name) lib]
[lib nil])
{alias :as referred :refer renamed :rename :or {alias lib}} (apply hash-map opts)
{alias :as referred :refer renamed :rename
:or {alias (if (string? lib)
(symbol (munge lib))
(apply hash-map opts)
referred-without-renamed (seq (remove (set (keys renamed)) referred))
[rk uk renk] (if macros? [:require-macros :use-macros :rename-macros] [:require :use :rename])]
(when-not (or (symbol? alias) (nil? alias))
@@ -3565,7 +3569,14 @@
(= "cljc" (util/ext src)))
deps (merge (:uses ast) (:requires ast))]
deps (merge (:uses ast) (:requires ast))
missing-js-modules (into #{}
(filter (fn [[k v]]
(and (or (string? k) (string? v))
(not (js-module-exists? k)))))
(map val))
{:ns (or ns-name 'cljs.user)
:provides [ns-name]
@@ -3574,6 +3585,7 @@
(cond-> (conj (set (vals deps)) 'cljs.core)
(get-in @env/*compiler* [:options :emit-constants])
(conj constants-ns-sym)))
:missing-js-modules missing-js-modules
:file dest
:source-file (when rdr src)
:source-forms (when-not rdr src)
@@ -1977,13 +1977,14 @@
#(ensure-cljs-base-module % opts)))
(defn add-implicit-options
[{:keys [optimizations output-dir npm-deps]
[{:keys [optimizations output-dir npm-deps missing-js-modules]
:or {optimizations :none
output-dir "out"}
:as opts}]
(let [opts (cond-> (update opts :foreign-libs
(fn [libs]
(into (index-node-modules npm-deps opts)
(into (index-node-modules
(into missing-js-modules (keys npm-deps)) opts)
(expand-libs libs))))
(:closure-defines opts)
(assoc :closure-defines
@@ -2003,7 +2004,7 @@
:optimizations optimizations
:output-dir output-dir
:ups-libs libs
:ups-foreign-libs (into (index-node-modules (compute-upstream-npm-deps opts) opts)
:ups-foreign-libs (into (index-node-modules (keys (compute-upstream-npm-deps opts)) opts)
(expand-libs foreign-libs))
:ups-externs externs
:emit-constants emit-constants
@@ -2134,15 +2135,15 @@
(into [] (distinct (mapcat #(node-module-deps % opts) entries)))))
(defn index-node-modules
(when env/*compiler*
(:options @env/*compiler*))))
([npm-deps opts]
([modules opts]
(let [node-modules (io/file "node_modules")]
(if (and (not (empty? npm-deps)) (.exists node-modules) (.isDirectory node-modules))
(let [modules (map name (keys npm-deps))
(if (and (not (empty? modules)) (.exists node-modules) (.isDirectory node-modules))
(let [modules (into #{} (map name) modules)
deps-file (io/file (str (util/output-directory opts) File/separator
(util/mkdirs deps-file)
@@ -2252,15 +2253,21 @@
(env/with-compiler-env compiler-env
;; we want to warn about NPM dep conflicts before installing the modules
(check-npm-deps opts)
(maybe-install-node-deps! opts)
(let [compiler-stats (:compiler-stats opts)
static-fns? (or (and (= (:optimizations opts) :advanced)
(not (false? (:static-fns opts))))
(:static-fns opts)
all-opts (-> opts
(not (false? (:static-fns opts))))
(:static-fns opts)
sources (-find-sources source opts)
missing-js-modules (into #{}
(map :missing-js-modules)
all-opts (-> (assoc opts :missing-js-modules missing-js-modules)
(check-output-to opts)
(check-output-dir opts)
(check-source-map opts)
@@ -2275,7 +2282,7 @@
(assoc :target (:target opts))
(assoc :js-dependency-index (deps/js-dependency-index all-opts))
;; Save list of sources for cljs.analyzer/locate-src - Juho Teperi
(assoc :sources (-find-sources source all-opts))))
(assoc :sources sources)))
(binding [comp/*recompiled* (when-not (false? (:recompile-dependents opts))
(atom #{}))
ana/*cljs-static-fns* static-fns?
@@ -766,6 +766,18 @@
(catch Exception _))
(is (= ["cljs.core/aset, arguments must be an array, followed by numeric indices, followed by a value, got [object string number] instead (consider goog.object/set for object access)"] @ws)))))
(deftest cljs-2061
(let [test-cenv (atom
(merge @(e/default-compiler-env)
{:js-module-index {"react" "module$src$test$cljs$react"}}))
ast (e/with-compiler-env test-cenv
'[(ns test.cljs-2061
(:require ["react" :as react]
["react-dom/server" :refer [renderToString]]))]))]
(is (= (:missing-js-modules ast) #{"react-dom/server"}))
(is (= (:requires ast) '#{cljs.core module$src$test$cljs$react "react-dom/server"}))))
(binding [a/*cljs-ns* a/*cljs-ns*]

0 comments on commit dd5d862

Please sign in to comment.