🔖ClojureScriptの開発環境について.

📝Google Closure Compiler

JavaScriptを最適化するためのコンパイラ.

Closure Compiler は JavaScript の最適化、トランスパイル、タイプチェックを行うツールです。これを使うと、コードを高パフォーマンスでサイズが縮小されたコードにコンパイルすることができます。この仕組みは Google のほとんどのウェブ フロントエンドで使われており、できる限りサイズが小さく高速なコードを提供しています。

🔖ClojureScriptはこの最適化のちからを十二分に利用しているらしい.

📝トランスコンパイラ

Externs

externsは, 高度なコンパイル中に名前を変更すべきでないシンボルの名前を Closure Compiler に伝える宣言. これをしないとファイル分割でライブラリを呼び出せない.

ヘッダファイルのようなもの.

^:export init

よくみかけるやつ. ^:exportはinit関数に対するmetadataであり, global変数としてGoogle Closureが呼べるようにするとか(by Linivng Clojure).

Debugs

js/console.log

(def log (.-log js/console))

💡Chrome/Firefox Devtools連携

  • GitHub - binaryage/cljs-devtools
    • A collection of Chrome DevTools enhancements for ClojureScript developers.
  • Source mapsの導入によってJavaScriptのデバッグが楽になる.
    • sourcemapはJavaScriptのコンパイルの変換前と変換後の対応関係を示すjsonデータ. これがないとJavaScriptからそれに対応するソースが辿れない.
    • sourcemapがあればChromeからソースがみれる.
    • ClojureScript - Source maps
  • GitHub - binaryage/dirac
    • ClojureScript用のChrome拡張, fork for Chrome Devtools.

JavaScript Objectの中身をみるには?

inspectだと, #object [Foo [Object Object]] みたいになって中身がみれない.

最も簡単な方法は, js/console.logでの表示.

helper functionsをつかう

(defn clone-js [jsobj]
  (.parse js/JSON (.stringify js/JSON jsobj)))

js->cljでは不十分.

ref. inspect by ‘new’ constructed JavaScript object’s at the ClojureScript REPL - Stack Overflow

Build Tools

🔧shadow-cljsを使うか、figwheelを使うか. lumoは2022はオワコン?

shadow-cljsとfigwheel-mainの違いはnpmに依存するかどうか. shadow-cljsはnpmを利用する i.e. よりJavaScriptとの連携や共存がしやすい. この観点でどちらのツールを選択するかは選んだほうがよさそう.

💡ClojureScript with Node.js

ClojureScriptのコンパイル結果を📝Node.jsで実行可能なものにする.

ClojureScriptはコンパイラなので, コンパイルオプションでNode.jsをターゲットにすることを指定することになるが, 大抵の場合はビルドツールをつかって指定することになる. このとき, Clojure(JVM)に依存するツールとそうでないものにわけられる.

🔧shadow-cljs

cljsをコンパイルしたりビルドするためのツールセット.

ライブラリの追加方法

shadow-cljs.ednの :dependencies に追加していく. インストールはshadow-cljsのコマンドを走らせる延長で走る.

ref. Dependencies: Shadow CLJS User’s Guide

shadow-cljsコマンドの基本的な使い方

npm pacakgeとしてインストールしたshadow-cljsコマンドの使い方まとめ.

プロジェクトの新規

$ npx create-cljs-project my-project
 
# すでにpackage.jsonがプロジェクトにあるとき
$ npm install --save-dev shadow-cljs
 
# もしくはglobal環境へ
$ npm install -g shadow-cljs

Basic Development Commands

# compile a build once and exit
$ shadow-cljs compile app
 
# compile and watch
$ shadow-cljs watch app
 
# connect to REPL for the build (available while watch is running)
$ shadow-cljs cljs-repl app
 
# connect to standalone node repl
$ shadow-cljs node-repl
 
# Running a release build optimized for production use.
$ shadow-cljs release app

Server Mode Commands

# running server-mode
$ shadow-cljs server
$ shadow-cljs clj-repl
 
$ shadow-cljs start
$ shadow-cljs stop
$ shadow-cljs restart

node.jsでつかう

ref. https://shadow-cljs.github.io/docs/UsersGuide.html#target-node

shadow-cljs.ednに設定を書く. :targetが :node-scriptになる.

{:source-paths ["src"]
 
 :dependencies []
 
 :builds {:app {:target    :node-script
                :output-to "target/helloworld.js"
                :main      demo.helloworld/main}}}

Tips

REPLのlocahostのport整理

server modeで立ち上げるといろいろでてくるので整理.

shadow-cljs - HTTP server available at http://localhost:3000
shadow-cljs - HTTP server available at http://localhost:8080
shadow-cljs - server version: 2.18.0 running at http://localhost:9630
shadow-cljs - nREPL server started on port 38105
  • localhost:3000は shadow-cljs.ednで自分で設定, devtools用.
  • localhost:8080は shadow-cljs.ednで自分で設定, Web表示確認用.
  • localhost:9630はshadow-cljsのデバッグツールが並ぶ.
  • localhost:38105はnREPLようでCIDERから接続する.

例えばcider-connectをするときはnREPLのための38105にアクセスする.

3つのREPLの整理(clj-repl/node-repl/browser-repl)

shadow-cljsの起動オプションで3つを抑えておく.

  • clj-repl(server-mode)
  • node-repl
  • browser-repl
$ shadow-cljs server
$ shadow-cljs clj-repl

clj-repl(server-mode) は 開発用サーバを立ち上げるときにつかう. clojurescriptを長時間Loopさせている.

$ shadow-cljs node-repl
$ shadow-cljs browser-repl

node-repl はnode.jsで起動するREPL. これはJavaで動くClojureのREPLと同じ.

browser-repl は node-replと機能が変わらないもののBrowserの JavaScriptコンソールと接続することができるモード.

Emacs CIDERでつかう

cider-jack-in-cljsですべてできる. プロンプトでshadow-cljsを選択.

もしくは,

  • shadow-cljs watch app
  • cider-connect-clj
  • watch で起動して表示されたportを入力(nREPL server start on port x)
  • shadowを選択
  • アプリケーションを選択.

ここでの注意点は, shadow-cljs.ednのdependenciesにcider-nreplを追加すること. これがないとevalできない.

{
 :dependencies
 [[cider/cider-nrepl "0.28.4"]]}

ref. https://docs.cider.mx/cider/cljs/shadow-cljs.html

deps.ednとshadow-cljsを共存させるには?

defaultではnode.jsの管理ツールであるpackage.jsonを利用するがやはりClojureをつかってると LeiningenClojure CLI と共存させたいところだ.

ref. tools.deps / deps.edn

shadow-cljs inspect with tap

🔧clojure.core/tapと連携できる. shadow-cljsのブラウザに Inspect Stream/Inspect Latestというタブかあり, ここに tap>で追加した要素が表示される.

JavaScript LibraryをClojureScriptから呼び出す

shadow-cljsはnode_modules配下のjsファイルを直接読める.

// node_modules/dev/calculator.js
var calculator = {
    add: function (a, b) {
        return a + b;
    },
    subtract: function (a, b) {
        return a - b;
    }
};
 
module.exports = calculator;
(ns dev.playground
  (:require ["dev/calculator" :refer [add]]))
 
(add 1 2)

Typescriptだとtscコマンドでコンパイルしたものを配置する必要がある.


shadow-cljsではdefaultでは🔖ES6の記法を認識する(!= commonJS). すなわち, module.exportsではなく, export class.

export class HelloWorld {
  constructor(name) {
    this.name = name;
  }
 
  greet() {
    return `Hello, ${this.name}!`;
  }
}

The previously used runtime disappeared. Will attempt to pick a new one when available but your state might be gone.

shadow-cljsのheartbeat. 処理が15秒以上かかるとこうなる.

Promiseをつかって非同期で処理させたり, cliからshadow-cljs を立ち上げてcider-connectすることで回避.

References

Format

エディタ連携

💡ClojureScript with CIDER

Emacs CIDER連携.

M-x cider-jack-in-cljsでjack-in or M-x cider-connect-cljsでconnect.


shadow-cljsを利用するには Select ClojureScript REPL type: のプロンプトでshadowを選択する. さらにshadow-cljs.ednで定義したbuild nameを選択すると接続完了. もしくは .dir-locals.el に設定を書いておくことでプロンプト入力は省略できる.

((nil . ((cider-default-cljs-repl . shadow)
         (cider-shadow-default-options . "<your-build-name-here>"))))

jack-inによって ClojureScriptのREPLがnREPLと連携するためのツールが自動で組み込まれる.

したがってコンソールからcljで起動してEmacsからcider-connectする場合はこれらの依存パッケージをdeps.ednに記入する必要がある.


shadow-cljsの場合, jack-in-cljsをすることでshadow-cljs.ednがおいてあればshadow-cljsと解釈されて自動でserverが立ち上がってconnectまでしてくれる. もしくは shadow-cljs server コマンドでサーバを立ち上げM-x cider-connectで接続できる.


Topics

バイナリデータはjs/console.logを使わないとsegfault

デコードが必要なデータを取得したあとそのままREPLにprnとかで出力しようとするとセグメンテーションフォルトが発生する.

この場合, js/console.logのprint出力ならば問題ない. データを特定してデコードする.