Toyokumo Tech Blog

トヨクモ株式会社の開発本部のブログです。

Spacemacsで始める Clojure 開発

こんにちは。開発本部の木下です。

先日ClojureアプリケーションをLSPで開発するための方法について書きました。

tech.toyokumo.co.jp

そこで本記事では上記のような Clojure アプリケーションをSpacemacsで開発するための設定について記載します。

なお、Spacemacs は現在のメインブランチである develop ブランチを使用していることを前提としています。

共通設定

Clojure アプリケーションを Spacemacs で開発する場合に必須で満たしてほしい要件は次のようなものです。

  • Clojure LSPが動作し、LSP が提供する機能を使うことができる
  • CIDERが動作し、REPL を使うことができる
  • clj-kondoによる静的解析が常にかかる
  • cljstyleによるフォーマットがファイル保存時に行われる

これらを実現しつつ、Clojure を書いていく上で必要な設定をしていきます。

前提として Clojure CLI、clj-kondo、cljstyle はインストールして実行可能にしておきます。

$ clojure -version
Clojure CLI version 1.11.1.1113
$ clj -version
Clojure CLI version 1.11.1.1113
$ clj-kondo --version
clj-kondo v2022.04.25
$ cljstyle version
mvxcvi/cljstyle 0.15.0

~/.spacemacs に次のように設定します。

(defun dotspacemacs/layers ()
  (setq-default
   ;; 次のレイヤーは最低限有効にします
   dotspacemacs-configuration-layers
   '(
     auto-completion
     lsp
     syntax-checking
     (clojure :variables
              clojure-enable-linters 'clj-kondo))

   ;; cljstyle を動作させるためのパッケージ cljstyle-mode をパッケージリストに加える
   dotspacemacs-additional-packages
   '((cljstyle-mode :location "~/projects/lib/cljstyle-mode/"))))

(defun dotspacemacs/init ()
  (setq-default
   ;; 編集中(挿入モードまたは dotspacemacs-editing-style が emacs の場合)にカッコのバランスが崩れるのを防いでくれます。
   ;; 有効にしておきましょう。
   dotspacemacs-smartparens-strict-mode t
   dotspacemacs-activate-smartparens-mode t
   dotspacemacs-smart-closing-parenthesis t))

(defun dotspacemacs/user-config ()
  ;; 忘れずに cljstyle-mode を読み込ませる
  (use-package cljstyle-mode)
  (use-package clojure-mode
    :init
    ;; clojure-mode で cljstyle-mode を有効化
    (add-hook 'clojure-mode-hook 'cljstyle-mode))
  ;; d$ など通常の vim のコマンドを実行したときにカッコのバランスを維持してくれます。
  ;; dotspacemacs-editing-style が vim または hybrid の場合は有効にしておきましょう。
  (spacemacs/toggle-evil-safe-lisp-structural-editing-on-register-hook-clojure-mode))

cljstyle の設定だけはデフォルトで用意されているやり方だけでは不可能なので上記のように個別で設定する必要があります。 cljstyle-modeを任意のディレクトリにクローンしてそこをdotspacemacs-additional-packages に指定してください。dotspacemacs-additional-packages の書き方には GitHub を直接参照させるやり方もありますが、筆者としてはローカルの適当なディレクトリにクローンして参照させるこのやり方が安定していると思っています。

これで基本的な設定は完了です。

プロジェクト固有の設定

いくつかのケースでプロジェクト固有の設定が必要になります。 CIDER のドキュメントでは設定値をまとめたページがないので個別のページを見ていく必要があって手間がかかります。 設定が必要なケースは様々あると思いますが、ここではイメージを持ってもらうことを目的として、弊社であった 2 つの実例をご紹介します。

ケース 1: alias を指定した REPL への接続

CIDER は deps.edn を使ったプロジェクトで cider-jack-in-clj すると通常は次のようなコマンドで REPL を立ち上げようとします。

/usr/local/bin/clojure \
-Sdeps '{:deps {nrepl/nrepl {:mvn/version "0.9.0"} cider/cider-nrepl {:mvn/version "0.28.3"}} :aliases {:cider/nrepl {:main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}}}' \
-M:cider/nrepl

CIDER を機能させるためにnREPLなどのライブラリを加えているのがわかります。 ここで問題となるのは alias を使って状況によって読み込みたいコードやライブラリ分けて管理している場合です。 下記の例では開発時だけ必要なディレクトリとライブラリを :dev alias として定義しており、REPL を立ち上げる時には alias を指定する必要があります。

{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}}
 :aliases {:dev {:extra-deps {lambdaisland/kaocha {:mvn/version "1.65.1029"}
                              lambdaisland/kaocha-cloverage {:mvn/version "1.0.75"}}
                 :extra-paths ["dev" "test"]}
           :test {:main-opts ["-m" "kaocha.runner"]}}}

上述したデフォルトの CIDER のコマンドでは alias は考慮されていませんから、このまま cider-jask-in-clj しただけではうまくいきません。

1 つ目の解決策は .dir-locals.el を用意することです。 .dir-locals.elEmacs で特定のディレクトリやそのサブディレクトリに対してローカル変数を設定する方法です。

;; .dir-locals.el
((clojure-mode . ((cider-clojure-cli-aliases . ":dev")
                  )))

cider-clojure-cli-aliases で alias を指定しています。この上で cider-jack-in-clj を実行するとコマンドに次のように alias が付与されます。

/usr/local/bin/clojure \
-Sdeps '{:deps {nrepl/nrepl {:mvn/version "0.9.0"} cider/cider-nrepl {:mvn/version "0.28.3"}} :aliases {:cider/nrepl {:main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}}}' \
-M:dev:cider/nrepl

2 つ目の解決策は REPL を立ち上げてそこに cider-connect-clj をすることです。 REPL を立ち上げるコマンドは上記のものを Makefile 等に記載しておいて実行してもいいですし、CIDER のドキュメントにあるようにalias を追加するのも良いです。

{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}}
 :aliases {:cider-clj {:extra-deps {cider/cider-nrepl {:mvn/version "0.28.3"}}
                       :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}
           :dev {:extra-deps {lambdaisland/kaocha {:mvn/version "1.65.1029"}
                              lambdaisland/kaocha-cloverage {:mvn/version "1.0.75"}}
                 :extra-paths ["dev" "test"]}
           :test {:main-opts ["-m" "kaocha.runner"]}}}

deps.edn に上記のように :cider-clj alias を追加し、clj -M:dev:cider-clj で REPL を立ち上げて cider-connect-clj で接続します。

ケース 2: shadow-cljs と deps.edn を組み合わせて使っている場合

モノレポなプロジェクトにおいてshadow-cljsを使って ClojureScript 開発をしている場合、shadow-cljs は :local/root を使ったローカルのモジュールの参照に対応していないためshadow-cljs と deps.edn を組み合わせた依存関係の管理をすることになります。

この場合は CIDER のドキュメントにそのままのやり方が書かれています。

docs.cider.mx

まとめ

ここまでの内容で Spacemacs を使って Clojure 開発を効率よく進められる設定ができるようになると思います。

Spacemacs は安定的で、一貫性があり、効率的でありながら指に優しいコーディング体験を提供してくれる非常に優れたエディタです。 Clojure だけでなく他の多くの言語の開発環境も提供してくれますから、何かの言語で一度慣れてしまえば長く連れ添うことのできる相棒になると思います。 興味があってまだ使ってみたことのない方はこれをきっかけに入門してみてはいかがでしょうか。

Pure Vimmer な方はこちらの記事を参考にしてください。

tech.toyokumo.co.jp


トヨクモでは好きなエディタを使って一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。