Skip to content

Commit f5f9b79

Browse files
committed
CLJS-3235: Support accessing a property of a library as a namespace itself
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
1 parent dc6ae8c commit f5f9b79

File tree

6 files changed

+126
-45
lines changed

6 files changed

+126
-45
lines changed

src/main/clojure/cljs/analyzer.cljc

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,14 @@
800800
:suffix suffix
801801
:macro-present? (not (nil? (get-expander (symbol (str prefix) (str suffix)) env)))})))
802802

803+
(defn lib&sublib
804+
"If a library name has the form foo$bar, return a vector of the library and
805+
the sublibrary property."
806+
[lib]
807+
(if-let [xs (re-matches #"(.*)\$(.*)" (str lib))]
808+
(drop 1 xs)
809+
[lib nil]))
810+
803811
(defn loaded-js-ns?
804812
"Check if a JavaScript namespace has been loaded. JavaScript vars are
805813
not currently checked."
@@ -830,18 +838,20 @@
830838
(defn node-module-dep?
831839
#?(:cljs {:tag boolean})
832840
[module]
833-
#?(:clj (contains?
834-
(get-in @env/*compiler* [:node-module-index])
835-
(str module))
841+
#?(:clj (let [idx (get @env/*compiler* :node-module-index)]
842+
(contains? idx (str (-> module lib&sublib first))))
836843
:cljs (try
837844
(and (= *target* "nodejs")
838-
(boolean (js/require.resolve (str module))))
845+
(boolean
846+
(or (js/require.resolve (str module))
847+
(js/require.resolve (-> module lib&sublib first)))))
839848
(catch :default _
840849
false))))
841850

842851
(defn dep-has-global-exports?
843852
[module]
844-
(let [global-exports (get-in @env/*compiler* [:js-dependency-index (str module) :global-exports])]
853+
(let [[module _] (lib&sublib module)
854+
global-exports (get-in @env/*compiler* [:js-dependency-index (str module) :global-exports])]
845855
(or (contains? global-exports (symbol module))
846856
(contains? global-exports (name module)))))
847857

@@ -2598,7 +2608,7 @@
25982608
#?(:cljs {:tag boolean})
25992609
[dep]
26002610
(let [js-index (:js-dependency-index @env/*compiler*)]
2601-
(if-some [[_ {:keys [foreign]}] (find js-index (name dep))]
2611+
(if-some [[_ {:keys [foreign]}] (find js-index (name (-> dep lib&sublib first)))]
26022612
foreign
26032613
false)))
26042614

@@ -2624,20 +2634,22 @@
26242634
(node-module-dep? dep)
26252635
(js-module-exists? (name dep))
26262636
#?(:clj (deps/find-classpath-lib dep)))
2627-
(if (contains? (:js-dependency-index compiler) (name dep))
2628-
(let [dep-name (name dep)]
2629-
(when (string/starts-with? dep-name "goog.")
2630-
#?(:clj (let [js-lib (get-in compiler [:js-dependency-index dep-name])
2631-
ns (externs/analyze-goog-file (:file js-lib) (symbol dep-name))]
2632-
(swap! env/*compiler* update-in [::namespaces dep] merge ns)))))
2633-
#?(:clj (if-some [src (locate-src dep)]
2634-
(analyze-file src opts)
2635-
(throw
2636-
(error env
2637-
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)}))))
2638-
:cljs (throw
2639-
(error env
2640-
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))))
2637+
(let [idx (:js-dependency-index compiler)
2638+
dep (-> dep lib&sublib first)]
2639+
(if (contains? idx (name dep))
2640+
(let [dep-name (name dep)]
2641+
(when (string/starts-with? dep-name "goog.")
2642+
#?(:clj (let [js-lib (get idx dep-name)
2643+
ns (externs/analyze-goog-file (:file js-lib) (symbol dep-name))]
2644+
(swap! env/*compiler* update-in [::namespaces dep] merge ns)))))
2645+
#?(:clj (if-some [src (locate-src dep)]
2646+
(analyze-file src opts)
2647+
(throw
2648+
(error env
2649+
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)}))))
2650+
:cljs (throw
2651+
(error env
2652+
(error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)}))))))))))))
26412653

26422654
(defn missing-use? [lib sym cenv]
26432655
(let [js-lib (get-in cenv [:js-dependency-index (name lib)])]

src/main/clojure/cljs/closure.clj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2937,7 +2937,10 @@
29372937
;; if :npm-deps option is false, node_modules/ dir shouldn't be indexed
29382938
(if (not (false? npm-deps))
29392939
(index-node-modules-dir)))
2940-
requires (set (mapcat deps/-requires js-sources))
2940+
requires (->> (mapcat deps/-requires js-sources)
2941+
;; fixup foo$default cases, foo is the lib, default is a property
2942+
(map #(-> % ana/lib&sublib first))
2943+
set)
29412944
;; Select Node files that are required by Cljs code,
29422945
;; and create list of all their dependencies
29432946
node-required (set/intersection (set (keys top-level)) requires)

src/main/clojure/cljs/compiler.cljc

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,18 +1251,28 @@
12511251
[{:keys [target val env]}]
12521252
(emit-wrap env (emits "(" target " = " val ")")))
12531253

1254+
(defn sublib-select
1255+
[sublib]
1256+
(when sublib
1257+
(let [xs (string/split sublib #"\.")]
1258+
(apply str
1259+
(map #(str "['" % "']") xs)))))
1260+
12541261
(defn emit-global-export [ns-name global-exports lib]
1255-
(emitln (munge ns-name) "."
1256-
(ana/munge-global-export lib)
1257-
" = goog.global"
1258-
;; Convert object dot access to bracket access
1259-
(->> (string/split (name (or (get global-exports (symbol lib))
1260-
(get global-exports (name lib))))
1261-
#"\.")
1262-
(map (fn [prop]
1263-
(str "[\"" prop "\"]")))
1264-
(apply str))
1265-
";"))
1262+
(let [[lib' sublib] (ana/lib&sublib lib)]
1263+
(emitln
1264+
(munge ns-name) "."
1265+
(ana/munge-global-export lib)
1266+
" = goog.global"
1267+
;; Convert object dot access to bracket access
1268+
(->> (string/split (name (or (get global-exports (symbol lib'))
1269+
(get global-exports (name lib'))))
1270+
#"\.")
1271+
(map (fn [prop]
1272+
(str "[\"" prop "\"]")))
1273+
(apply str))
1274+
(sublib-select sublib)
1275+
";")))
12661276

12671277
(defn load-libs
12681278
[libs seen reloads deps ns-name]
@@ -1288,18 +1298,19 @@
12881298
;; have handled it - David
12891299
(when (and (= :none optimizations)
12901300
(not (contains? options :modules)))
1291-
(if nodejs-rt
1292-
;; under node.js we load foreign libs globally
1293-
(let [ijs (get js-dependency-index (name lib))]
1294-
(emitln "cljs.core.load_file("
1295-
(-> (io/file (util/output-directory options)
1296-
(or (deps/-relative-path ijs)
1297-
(util/relative-name (:url ijs))))
1301+
(let [[lib _] (ana/lib&sublib lib)]
1302+
(if nodejs-rt
1303+
;; under node.js we load foreign libs globally
1304+
(let [ijs (get js-dependency-index (name lib))]
1305+
(emitln "cljs.core.load_file("
1306+
(-> (io/file (util/output-directory options)
1307+
(or (deps/-relative-path ijs)
1308+
(util/relative-name (:url ijs))))
12981309
str
12991310
escape-string
13001311
wrap-in-double-quotes)
1301-
");"))
1302-
(emitln "goog.require('" (munge lib) "');")))]
1312+
");"))
1313+
(emitln "goog.require('" (munge lib) "');"))))]
13031314
:cljs
13041315
[(and (ana/foreign-dep? lib)
13051316
(not (keyword-identical? optimizations :none)))
@@ -1317,11 +1328,12 @@
13171328
(when-not (= lib 'goog)
13181329
(emitln "goog.require('" (munge lib) "');"))))
13191330
(doseq [lib node-libs]
1320-
(emitln (munge ns-name) "."
1321-
(ana/munge-node-lib lib)
1322-
" = require('" lib "');"))
1331+
(let [[lib' sublib] (ana/lib&sublib lib)]
1332+
(emitln (munge ns-name) "."
1333+
(ana/munge-node-lib lib)
1334+
" = require('" lib' "')" (sublib-select sublib) ";")))
13231335
(doseq [lib global-exports-libs]
1324-
(let [{:keys [global-exports]} (get js-dependency-index (name lib))]
1336+
(let [{:keys [global-exports]} (get js-dependency-index (name (-> lib ana/lib&sublib first)))]
13251337
(emit-global-export ns-name global-exports lib)))
13261338
(when (-> libs meta :reload-all)
13271339
(emitln "if(!COMPILED) " loaded-libs " = cljs.core.into(" loaded-libs-temp ", " loaded-libs ");"))))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(ns cljs-3235.core
2+
(:require [some-foreign :refer [woz]]
3+
[some-foreign$woz :as sf-woz]
4+
[some-foreign$foz.boz :as sf-foz-boz]
5+
[react-select :refer [foo bar]]
6+
[react-select$default :as select]
7+
[react-select$default.baz :as select-baz]))
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
window.globalLib = {
2+
woz: function() {
3+
4+
},
5+
foz: {
6+
boz: function() {
7+
8+
}
9+
}
10+
};

src/test/clojure/cljs/build_api_tests.clj

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,3 +731,40 @@
731731
cenv (env/default-compiler-env)]
732732
(test/delete-out-files out)
733733
(build/build nil opts cenv)))
734+
735+
(deftest test-cljs-3235
736+
(test/delete-node-modules)
737+
(spit (io/file "package.json") "{}")
738+
(testing "Test various require patterns for Node and foreign libraries"
739+
(let [ws (atom [])
740+
out (.getPath (io/file (test/tmp-dir) "cljs-3235-out"))
741+
{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build"))
742+
:opts {:main 'cljs-3235.core
743+
:output-dir out
744+
:optimizations :none
745+
:target :nodejs
746+
:install-deps true
747+
:npm-deps {:react "15.6.1"
748+
:react-dom "15.6.1"
749+
:react-select "3.1.0"}
750+
:foreign-libs [{:file (.getPath (io/file "src" "test" "cljs_build" "cljs_3235" "foreign.js"))
751+
:provides ["some-foreign"]
752+
:global-exports '{some-foreign globalLib}}]
753+
:closure-warnings {:check-types :off
754+
:non-standard-jsdoc :off}}}
755+
cenv (env/default-compiler-env opts)]
756+
(test/delete-out-files out)
757+
(ana/with-warning-handlers [(collecting-warning-handler ws)]
758+
(build/build (build/inputs (io/file inputs "cljs_3235/core.cljs")) opts cenv))
759+
(is (.exists (io/file out "cljs_3235/core.js")))
760+
(is (true? (boolean (re-find #"cljs_3235\.core\.node\$module\$react_select\$default = require\('react-select'\)\['default'\];"
761+
(slurp (io/file out "cljs_3235/core.js"))))))
762+
(is (true? (boolean (re-find #"cljs_3235\.core\.node\$module\$react_select\$default\$baz = require\('react-select'\)\['default'\]\['baz'\];"
763+
(slurp (io/file out "cljs_3235/core.js"))))))
764+
(is (true? (boolean (re-find #"cljs_3235\.core\.global\$module\$some_foreign\$woz = goog.global\[\"globalLib\"\]\['woz'\];"
765+
(slurp (io/file out "cljs_3235/core.js"))))))
766+
(is (true? (boolean (re-find #"cljs_3235\.core\.global\$module\$some_foreign\$foz\$boz = goog.global\[\"globalLib\"\]\['foz'\]\['boz'\];"
767+
(slurp (io/file out "cljs_3235/core.js"))))))
768+
(is (empty? @ws))))
769+
(.delete (io/file "package.json"))
770+
(test/delete-node-modules))

0 commit comments

Comments
 (0)