ClojureScript内からnpmモジュールを呼び出す（npm-deps）


# tl;dr

project.clj にコンパイラ・オプション `:npm-deps` と `:install true` を追加。

こんな感じ。

```diff
diff --git a/project.clj b/project.clj
index 24f0146..065a9f6 100644
--- a/project.clj
+++ b/project.clj
@@ -25,6 +25,8 @@
                 :output-to "target/js/compiled/hello_aws_cli.js"
                 :output-dir "target/js/compiled/dev"
                 :target :nodejs
+                :npm-deps {:aws-sdk "^2.167.0"}
+                :install-deps true
                 :optimizations :none
                 :source-map-timestamp true}}
              {:id "prod"
```

---

ClojureScript を書き始めた cljs newbie がハマる大きな落とし穴、
それが「**ClojureScript コード内から npm なコードを呼び出す**」です。[^1]

[^1]: ハマったのは私だけかもしれませんが...😅

ちょっとググってみたところ、[CLJSJS](https://cljsjs.github.io/)を使うといいよ〜とか、そこにない場合は[自力で externs を書く必要があるよ](https://clojurescript.org/reference/packaging-foreign-deps)とか、いろいろ見つかるのですが、どれもうまく行かなかったり設定に戸惑ったりすること１週間...[^2]

[^2]: 最新の[AWS SDK for javascript](https://github.com/aws/aws-sdk-js)と[Alexa Skill Kit](https://github.com/amzn/alexa-skills-kit-js)を使いたかったのです

# npm-deps コンパイラ・オプションを使え！

そんな中、この記事を発見。
https://clojurescript.org/news/2017-07-12-clojurescript-is-not-an-island-integrating-node-modules

ほうほう！
早速試してみましょう。

```console
$ lein new figwheel-node hello-aws-cli
$ cd hello-aws-cli
```

project.clj に `:npm-deps {:aws-sdk "^2.167.0"}` と `:install true`を追加。

```diff
diff --git a/project.clj b/project.clj
index 24f0146..065a9f6 100644
--- a/project.clj
+++ b/project.clj
@@ -25,6 +25,8 @@
                 :output-to "target/js/compiled/hello_aws_cli.js"
                 :output-dir "target/js/compiled/dev"
                 :target :nodejs
+                :npm-deps {:aws-sdk "^2.167.0"}
+                :install-deps true
                 :optimizations :none
                 :source-map-timestamp true}}
              {:id "prod"
```

依存関係インストール + cljs コード → JS コードへのトランスパイル + REPL を開くために `lein figwheel`を実行。

```console
$ lein figwheel
Launching ClojureScript REPL for build: dev
Figwheel Controls:
          (stop-autobuild)                ;; stops Figwheel autobuilder
          (start-autobuild [id ...])      ;; starts autobuilder focused on optional ids
          (switch-to-build id ...)        ;; switches autobuilder to different build
          (reset-autobuild)               ;; stops, cleans, and starts autobuilder
          (reload-config)                 ;; reloads build config and resets autobuild
          (build-once [id ...])           ;; builds source one time
          (clean-builds [id ..])          ;; deletes compiled cljs target files
          (print-config [id ...])         ;; prints out build configurations
          (fig-status)                    ;; displays current state of system
          (figwheel.client/set-autoload false)    ;; will turn autoloading off
          (figwheel.client/set-repl-pprint false) ;; will turn pretty printing off
  Switch REPL build focus:
          :cljs/quit                      ;; allows you to switch REPL to another build
    Docs: (doc function-name-here)
    Exit: Control+C or :cljs/quit
 Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when Figwheel connects to your application
```

と出るので、別ターミナルを立ち上げて

```
$ node target/js/compiled/hello_aws_cli.js
Hello world!
Figwheel: Can't start Figwheel!! Please make sure ws is installed
 do -> 'npm install ws'
```

ん？？
`lein figwheel` でインストールされる依存関係は project.clj に記述されているものだけ（package.json で管理されているものはインストールしてくれない）みたいですね。

そこで、言われる通り npm install を実行したのち、再度実行してみると...

```console
$ npm install
....

$ lein figwheel
Hello world!
Figwheel: trying to open cljs reload socket
Figwheel: socket connection established
```

おっ、うまくいってそう。
もう片方のターミナルをみると、

```console
...
Prompt will show when Figwheel connects to your application
To quit, type: :cljs/quit
dev:cljs.user=> (+ 1 2)
3
dev:cljs.user=>
```

いい感じですね！これで REPL の準備ができました。
それでは AWS SDK を読み込めるか試してみましょう！

```console
dev:cljs.user=> (require '[cljs.nodejs :as nodejs])
nil
dev:cljs.user=> (def AWS (nodejs/require "aws-sdk"))
#'cljs.user/AWS
dev:cljs.user=> AWS
#js {:util #js {:environment "nodejs", :engine #object[engine], :userAgent #object[userAgent], :isBrowser #object[isBrowser], :isNode #object[isNode], :uriEscape #object[uriEscape], :uriEscapePath #object[uriEscapePath], :urlParse #object[urlParse], ... ; 以下略
```

おっ！やったね。

:npm-deps コンパイラオプションを使うと、驚くほど簡単に npm が使えていい感じですね！[^3]

[^3]: ただし、ClojureScript v1.9.518 以降 でしか使えないのでお気をつけください...

# 参考 URL

https://cljs.github.io/api/compiler-options/npm-deps
https://anmonteiro.com/2017/03/requiring-node-js-modules-from-clojurescript-namespaces/
https://clojurescript.org/news/2017-07-12-clojurescript-is-not-an-island-integrating-node-modules