Skip to content

Commit

Permalink
CLJS-3235: Support accessing a property of a library as a namespace i…
Browse files Browse the repository at this point in the history
…tself

add lib&sublib helper to handle foo$bar

change ana/node-module-dep? to handle foo$bar case

change ana/analyze-deps :js-dependency case to handle foo$bar

change handle-js-source so that we match node_modules against foo not foo$bar

change emit-global-export to select bar from foo$bar

change :node-js require case in load-libs to select bar from foo$bar

ana/dep-has-global-exports? needs to handle sublib pattern

ana/foreign? needs to handle sublib pattern

comp/load-libs foreign lib case needs to handle sublib pattern, same for global export emission

add test cases covering node and foreign lib require patterns
  • Loading branch information
swannodette committed Jul 20, 2020
1 parent dc6ae8c commit f5f9b79
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 45 deletions.
52 changes: 32 additions & 20 deletions src/main/clojure/cljs/analyzer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,14 @@
:suffix suffix
:macro-present? (not (nil? (get-expander (symbol (str prefix) (str suffix)) env)))})))

(defn lib&sublib
"If a library name has the form foo$bar, return a vector of the library and
the sublibrary property."
[lib]
(if-let [xs (re-matches #"(.*)\$(.*)" (str lib))]
(drop 1 xs)
[lib nil]))

(defn loaded-js-ns?
"Check if a JavaScript namespace has been loaded. JavaScript vars are
not currently checked."
Expand Down Expand Up @@ -830,18 +838,20 @@
(defn node-module-dep?
#?(:cljs {:tag boolean})
[module]
#?(:clj (contains?
(get-in @env/*compiler* [:node-module-index])
(str module))
#?(:clj (let [idx (get @env/*compiler* :node-module-index)]
(contains? idx (str (-> module lib&sublib first))))
:cljs (try
(and (= *target* "nodejs")
(boolean (js/require.resolve (str module))))
(boolean
(or (js/require.resolve (str module))
(js/require.resolve (-> module lib&sublib first)))))
(catch :default _
false))))

(defn dep-has-global-exports?
[module]
(let [global-exports (get-in @env/*compiler* [:js-dependency-index (str module) :global-exports])]
(let [[module _] (lib&sublib module)
global-exports (get-in @env/*compiler* [:js-dependency-index (str module) :global-exports])]
(or (contains? global-exports (symbol module))
(contains? global-exports (name module)))))

Expand Down Expand Up @@ -2598,7 +2608,7 @@
#?(:cljs {:tag boolean})
[dep]
(let [js-index (:js-dependency-index @env/*compiler*)]
(if-some [[_ {:keys [foreign]}] (find js-index (name dep))]
(if-some [[_ {:keys [foreign]}] (find js-index (name (-> dep lib&sublib first)))]
foreign
false)))

Expand All @@ -2624,20 +2634,22 @@
(node-module-dep? dep)
(js-module-exists? (name dep))
#?(:clj (deps/find-classpath-lib dep)))
(if (contains? (:js-dependency-index compiler) (name dep))
(let [dep-name (name dep)]
(when (string/starts-with? dep-name "goog.")
#?(:clj (let [js-lib (get-in compiler [:js-dependency-index dep-name])
ns (externs/analyze-goog-file (:file js-lib) (symbol dep-name))]
(swap! env/*compiler* update-in [::namespaces dep] merge ns)))))
#?(:clj (if-some [src (locate-src dep)]
(analyze-file src opts)
(throw
(error env
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)}))))
:cljs (throw
(error env
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))))
(let [idx (:js-dependency-index compiler)
dep (-> dep lib&sublib first)]
(if (contains? idx (name dep))
(let [dep-name (name dep)]
(when (string/starts-with? dep-name "goog.")
#?(:clj (let [js-lib (get idx dep-name)
ns (externs/analyze-goog-file (:file js-lib) (symbol dep-name))]
(swap! env/*compiler* update-in [::namespaces dep] merge ns)))))
#?(:clj (if-some [src (locate-src dep)]
(analyze-file src opts)
(throw
(error env
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)}))))
:cljs (throw
(error env
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)}))))))))))))

(defn missing-use? [lib sym cenv]
(let [js-lib (get-in cenv [:js-dependency-index (name lib)])]
Expand Down
5 changes: 4 additions & 1 deletion src/main/clojure/cljs/closure.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2937,7 +2937,10 @@
;; if :npm-deps option is false, node_modules/ dir shouldn't be indexed
(if (not (false? npm-deps))
(index-node-modules-dir)))
requires (set (mapcat deps/-requires js-sources))
requires (->> (mapcat deps/-requires js-sources)
;; fixup foo$default cases, foo is the lib, default is a property
(map #(-> % ana/lib&sublib first))
set)
;; Select Node files that are required by Cljs code,
;; and create list of all their dependencies
node-required (set/intersection (set (keys top-level)) requires)
Expand Down
60 changes: 36 additions & 24 deletions src/main/clojure/cljs/compiler.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -1251,18 +1251,28 @@
[{:keys [target val env]}]
(emit-wrap env (emits "(" target " = " val ")")))

(defn sublib-select
[sublib]
(when sublib
(let [xs (string/split sublib #"\.")]
(apply str
(map #(str "['" % "']") xs)))))

(defn emit-global-export [ns-name global-exports lib]
(emitln (munge ns-name) "."
(ana/munge-global-export lib)
" = goog.global"
;; Convert object dot access to bracket access
(->> (string/split (name (or (get global-exports (symbol lib))
(get global-exports (name lib))))
#"\.")
(map (fn [prop]
(str "[\"" prop "\"]")))
(apply str))
";"))
(let [[lib' sublib] (ana/lib&sublib lib)]
(emitln
(munge ns-name) "."
(ana/munge-global-export lib)
" = goog.global"
;; Convert object dot access to bracket access
(->> (string/split (name (or (get global-exports (symbol lib'))
(get global-exports (name lib'))))
#"\.")
(map (fn [prop]
(str "[\"" prop "\"]")))
(apply str))
(sublib-select sublib)
";")))

(defn load-libs
[libs seen reloads deps ns-name]
Expand All @@ -1288,18 +1298,19 @@
;; have handled it - David
(when (and (= :none optimizations)
(not (contains? options :modules)))
(if nodejs-rt
;; under node.js we load foreign libs globally
(let [ijs (get js-dependency-index (name lib))]
(emitln "cljs.core.load_file("
(-> (io/file (util/output-directory options)
(or (deps/-relative-path ijs)
(util/relative-name (:url ijs))))
(let [[lib _] (ana/lib&sublib lib)]
(if nodejs-rt
;; under node.js we load foreign libs globally
(let [ijs (get js-dependency-index (name lib))]
(emitln "cljs.core.load_file("
(-> (io/file (util/output-directory options)
(or (deps/-relative-path ijs)
(util/relative-name (:url ijs))))
str
escape-string
wrap-in-double-quotes)
");"))
(emitln "goog.require('" (munge lib) "');")))]
");"))
(emitln "goog.require('" (munge lib) "');"))))]
:cljs
[(and (ana/foreign-dep? lib)
(not (keyword-identical? optimizations :none)))
Expand All @@ -1317,11 +1328,12 @@
(when-not (= lib 'goog)
(emitln "goog.require('" (munge lib) "');"))))
(doseq [lib node-libs]
(emitln (munge ns-name) "."
(ana/munge-node-lib lib)
" = require('" lib "');"))
(let [[lib' sublib] (ana/lib&sublib lib)]
(emitln (munge ns-name) "."
(ana/munge-node-lib lib)
" = require('" lib' "')" (sublib-select sublib) ";")))
(doseq [lib global-exports-libs]
(let [{:keys [global-exports]} (get js-dependency-index (name lib))]
(let [{:keys [global-exports]} (get js-dependency-index (name (-> lib ana/lib&sublib first)))]
(emit-global-export ns-name global-exports lib)))
(when (-> libs meta :reload-all)
(emitln "if(!COMPILED) " loaded-libs " = cljs.core.into(" loaded-libs-temp ", " loaded-libs ");"))))
Expand Down
7 changes: 7 additions & 0 deletions src/test/cljs_build/cljs_3235/core.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(ns cljs-3235.core
(:require [some-foreign :refer [woz]]
[some-foreign$woz :as sf-woz]
[some-foreign$foz.boz :as sf-foz-boz]
[react-select :refer [foo bar]]
[react-select$default :as select]
[react-select$default.baz :as select-baz]))
10 changes: 10 additions & 0 deletions src/test/cljs_build/cljs_3235/foreign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
window.globalLib = {
woz: function() {

},
foz: {
boz: function() {

}
}
};
37 changes: 37 additions & 0 deletions src/test/clojure/cljs/build_api_tests.clj
Original file line number Diff line number Diff line change
Expand Up @@ -731,3 +731,40 @@
cenv (env/default-compiler-env)]
(test/delete-out-files out)
(build/build nil opts cenv)))

(deftest test-cljs-3235
(test/delete-node-modules)
(spit (io/file "package.json") "{}")
(testing "Test various require patterns for Node and foreign libraries"
(let [ws (atom [])
out (.getPath (io/file (test/tmp-dir) "cljs-3235-out"))
{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build"))
:opts {:main 'cljs-3235.core
:output-dir out
:optimizations :none
:target :nodejs
:install-deps true
:npm-deps {:react "15.6.1"
:react-dom "15.6.1"
:react-select "3.1.0"}
:foreign-libs [{:file (.getPath (io/file "src" "test" "cljs_build" "cljs_3235" "foreign.js"))
:provides ["some-foreign"]
:global-exports '{some-foreign globalLib}}]
:closure-warnings {:check-types :off
:non-standard-jsdoc :off}}}
cenv (env/default-compiler-env opts)]
(test/delete-out-files out)
(ana/with-warning-handlers [(collecting-warning-handler ws)]
(build/build (build/inputs (io/file inputs "cljs_3235/core.cljs")) opts cenv))
(is (.exists (io/file out "cljs_3235/core.js")))
(is (true? (boolean (re-find #"cljs_3235\.core\.node\$module\$react_select\$default = require\('react-select'\)\['default'\];"
(slurp (io/file out "cljs_3235/core.js"))))))
(is (true? (boolean (re-find #"cljs_3235\.core\.node\$module\$react_select\$default\$baz = require\('react-select'\)\['default'\]\['baz'\];"
(slurp (io/file out "cljs_3235/core.js"))))))
(is (true? (boolean (re-find #"cljs_3235\.core\.global\$module\$some_foreign\$woz = goog.global\[\"globalLib\"\]\['woz'\];"
(slurp (io/file out "cljs_3235/core.js"))))))
(is (true? (boolean (re-find #"cljs_3235\.core\.global\$module\$some_foreign\$foz\$boz = goog.global\[\"globalLib\"\]\['foz'\]\['boz'\];"
(slurp (io/file out "cljs_3235/core.js"))))))
(is (empty? @ws))))
(.delete (io/file "package.json"))
(test/delete-node-modules))

0 comments on commit f5f9b79

Please sign in to comment.