Clojure 自 2007 年诞生以来,其工具链经历了从 Leiningen 一统天下到 Clojure CLI(tools.deps)双轨并行的长期演进。理解这两种主流工具的差异与选型原则,是每位 Clojure 开发者必须掌握的基础技能。本文将深入对比两者的项目结构、依赖管理、构建生命周期及 IDE 集成,帮助你选择最适合团队场景的工具链。
1. 工具链演进时间线
| 年份 | 里程碑 | 说明 |
|---|---|---|
| 2009 | Leiningen 1.0 | 首个广泛使用的 Clojure 构建工具 |
| 2011 | Leiningen 2.0 | profiles、plugins、模板等现代功能 |
| 2014 | Boot 工具链 | 与 Leiningen 竞争的可编程构建工具 |
| 2018 | Clojure CLI (tools.deps.alpha) | 官方推出的轻量级依赖管理 |
| 2020 | deps.edn 成为主流 | 生态迁移,Clojure CLI 内置 |
| 2023 | tools.build + Clojure CLI | 官方推荐的完整构建工具链 |
当前推荐:新项目首选 Clojure CLI + deps.edn,旧项目可逐步迁移。Boot 基本不再维护。
2. Leiningen:传统而成熟的构建工具
2.1 项目结构
my-project/
├── project.clj # 项目配置(类似 Maven pom.xml)
├── src/
│ └── my_project/
│ └── core.clj
├── test/
│ └── my_project/
│ └── core_test.clj
├── resources/ # 静态资源
├── dev-resources/ # 开发环境资源
└── target/ # 编译输出
2.2 project.clj 核心配置
(defproject myapp "1.0.0-SNAPSHOT"
:description "我的 Clojure 应用"
:url "https://example.com"
:license {:name "EPL-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
;; 依赖管理(类比 Maven)
:dependencies [[org.clojure/clojure "1.11.1"]
[ring/ring-core "1.10.0"]
[compojure "1.7.0"]
[org.clojure/java.jdbc "0.7.12"]]
;; 开发环境额外依赖
:profiles {:dev {:dependencies [[org.clojure/tools.namespace "1.4.4"]]
:source-paths ["dev"]}
:uberjar {:aot :all
:jvm-opts ["-Dclojure.compiler.direct-linking=true"]}}
;; 插件扩展
:plugins [[lein-ring "0.12.6"]]
;; 主入口
:main ^:skip-aot myapp.core
;; 测试配置
:test-selectors {:default (complement :slow)
:slow :slow})
2.3 Leiningen 常用命令
| 命令 | 功能 |
|---|---|
lein new app myapp | 创建新应用项目 |
lein new template mylib | 创建库项目 |
lein deps | 下载依赖 |
lein repl | 启动 REPL |
lein test | 运行测试 |
lein uberjar | 构建独立可执行 JAR |
lein run | 运行项目 main 函数 |
2.4 Leiningen 的优缺点
优点:
- 生态极其成熟,社区支持完善
- 丰富的插件体系(lein-ring、lein-midje 等)
- 配置文件结构清晰,约定优于配置
缺点:
- 配置格式是 Clojure 代码(DSL),学习成本略高
- 构建定制需编写插件,不够灵活
- 启动速度受插件加载影响
3. Clojure CLI + deps.edn:现代轻量级方案
3.1 deps.edn 项目结构
my-project/
├── deps.edn # 依赖与别名配置
├── build.clj # 构建脚本(可选)
├── src/
│ └── my_project/
│ └── core.clj
├── test/
│ └── my_project/
│ └── core_test.clj
└── resources/
3.2 deps.edn 核心配置
;; deps.edn — 纯数据(EDN),非代码
{:paths ["src" "resources"]
;; Maven 依赖
:deps {org.clojure/clojure {:mvn/version "1.11.1"}
ring/ring-core {:mvn/version "1.10.0"}
metosin/reitit {:mvn/version "0.7.0"}}
;; 别名(功能模块化)
:aliases
{:dev {:extra-paths ["dev"]
:extra-deps {org.clojure/tools.namespace {:mvn/version "1.4.4"}}}
:test {:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}
:build {:deps {io.github.clojure/tools.build {:git/tag "v0.9.6" :git/sha "8e78bcc"}}
:ns-default build}
:uber {:replace-deps {com.github.seancorfield/depstar {:mvn/version "2.1.303"}}
:exec-fn hf.depstar/uberjar
:exec-args {:aot true
:main-class myapp.core
:jar "target/myapp.jar"}}}}
3.3 Clojure CLI 常用命令
| 命令 | 功能 |
|---|---|
clojure -Tnew create :name myapp | 创建新项目 |
clojure -M:dev | 以 dev 别名启动 REPL |
clojure -M:test | 运行测试 |
clojure -T:build jar | 构建 JAR |
clojure -T:uber uber | 构建 uberjar |
clojure -X:dep-tree | 查看依赖树 |
3.4 工具安装与版本管理
# macOS(Homebrew)
brew install clojure/tools/clojure
# Linux(官方安装脚本)
curl -L -O https://github.com/clojure/brew-install/releases/latest/download/linux-install.sh
chmod +x linux-install.sh
sudo ./linux-install.sh
# 验证安装
clojure --version
# => Clojure CLI version 1.11.1.1435
4. Leiningen vs deps.edn 深度对比
| 维度 | Leiningen | Clojure CLI + deps.edn |
|---|---|---|
| 配置格式 | Clojure DSL(project.clj) | 纯数据 EDN(deps.edn) |
| 依赖解析 | 本地仓库 + Central | Maven + git deps 混合 |
| 插件机制 | 闭包宏定义 | Git 仓库直接引用(tools) |
| 组合方式 | profiles 继承 | aliases 叠加 |
| 启动速度 | 较慢(插件加载) | 更快 |
| IDE 支持 | IntelliJ/Cursive 原生 | VS Code/Calva 原生 |
| 生态成熟度 | 极高(10+ 年积累) | 快速增长 |
| AOT 编译 | lein compile | clojure -T:build compile |
| uberjar | lein uberjar | clojure -T:uber uber |
| 适用场景 | 传统企业项目 | 新项目、函数式偏好团队 |
5. tools.build:官方构建工具
5.1 build.cljs 构建脚本
;; build.clj
(ns build
(:require [clojure.tools.build.api :as b]))
(def lib 'com.example/myapp)
(def version "1.0.0")
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def uber-file (format "target/%s-%s.jar" (name lib) version))
(defn clean [_]
(b/delete {:path "target"}))
(defn jar [_]
(b/write-pom {:class-dir class-dir
:lib lib
:version version
:basis basis
:src-dirs ["src"]})
(b/copy-dir {:src-dirs ["src" "resources"]
:target-dir class-dir})
(b/jar {:class-dir class-dir
:jar-file (format "target/%s-%s-lib.jar" (name lib) version)}))
(defn uber [_]
(clean nil)
(b/copy-dir {:src-dirs ["src" "resources"]
:target-dir class-dir})
(b/compile-clj {:basis basis
:src-dirs ["src"]
:class-dir class-dir})
(b/uber {:class-dir class-dir
:uber-file uber-file
:basis basis
:main 'myapp.core}))
5.2 构建命令
# 清理
clojure -T:build clean
# 构建库 JAR
clojure -T:build jar
# 构建可执行 uberjar
clojure -T:build uber
# 运行
java -jar target/myapp-1.0.0.jar
6. REPL 驱动开发:IDE 集成
6.1 Emacs + CIDER(传统最强)
;; .emacs 或 init.el 配置
(require 'cider)
;; 连接 Clojure CLI REPL
(setq cider-clojure-cli-aliases ":dev:test")
;; jack-in 快捷键
;; C-c M-j — 启动 REPL
;; C-c C-e — 求值当前表达式
;; C-c C-k — 加载整个文件
;; C-c C-d C-d — 文档查看
CIDER 特性:
- 完整的调试器(断点、单步跟踪)
- 代码补全与文档实时显示
- 测试运行与覆盖率查看
- Refactoring 工具(重命名、提取函数等)
- nREPL 协议支持
6.2 VS Code + Calva(现代推荐)
// settings.json
{
"calva.replConnectSequences": [
{
"name": "My Project REPL",
"projectType": "deps.edn",
"cljsType": "none",
"menuSelections": {
"cljAliases": ["dev", "test"]
}
}
]
}
Calva 快捷键:
Ctrl+Alt+C Ctrl+Alt+J— jack-in 启动 REPLCtrl+Enter— 求值当前 top-level 表达式Alt+Enter— 求值当前光标表达式
6.3 IntelliJ IDEA + Cursive(Java 团队过渡首选)
Settings → Plugins → Marketplace → 搜索 "Cursive" → Install
新建项目:File → New → Project → Clojure → deps.edn
Cursive 优势:
- 与 IntelliJ 完全一致的操作习惯
- 强大的代码导航与重构
- 与 Java 代码无缝跳转
- structural editing(paredit)
6.4 REPL 工作流最佳实践
;; 1. 启动 REPL 和命名空间
(require '[myapp.core :as core])
(in-ns 'myapp.core)
;; 2. 开发函数原型
(defn calculate [x y]
(* x y))
;; 3. 测试
(calculate 3 4) ; => 12
;; 4. 修改后重新加载文件
;; C-c C-k(Emacs)或 Reload 按钮(Calva)
;; 5. 使用 tools.namespace 自动重载
(require '[clojure.tools.namespace.repl :refer [refresh]])
(refresh)
7. 生产环境打包与部署
7.1 Uberjar 部署
# 构建
clojure -T:build uber
# 部署
scp target/myapp-1.0.0.jar server:/opt/app/
ssh server << 'EOF'
cd /opt/app
java -Xmx2g -jar myapp-1.0.0.jar
EOF
7.2 Docker 容器化
FROM clojure:temurin-17-tools-deps-alpine AS builder
WORKDIR /app
COPY deps.edn build.clj ./
COPY src ./src
COPY resources ./resources
RUN clojure -T:build uber
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xmx2g", "-Dclojure.compiler.direct-linking=true", "-jar", "app.jar"]
7.3 GraalVM Native Image(极致启动速度)
# 1. 安装 GraalVM
sdk install java 21.0.2-graalce
sdk use java 21.0.2-graalce
# 2. 使用 org.graalvm.buildtools 构建 native image
clojure -T:build native-image
# 3. 结果:秒级启动、低内存占用
./target/myapp # 启动时间 < 50ms
8. 总结
Leiningen 和 Clojure CLI 是 Clojure 生态的两根支柱。新项目推荐 deps.edn + tools.build,享受纯数据配置和更快的启动速度;维护老项目可以继续使用 Leiningen,其插件生态依然丰富。无论选择哪种工具链,REPL 驱动开发都是 Clojure 最核心的生产力优势。
| 团队背景 | 推荐工具链 |
|---|---|
| 纯 Clojure 新团队 | Clojure CLI + deps.edn + Calva/Emacs |
| Java 背景过渡团队 | Clojure CLI + Cursive(IntelliJ) |
| 传统 Clojure 老项目 | 继续使用 Leiningen,逐步迁移 |
| 极端性能要求 | deps.edn + GraalVM native-image |
延伸阅读可参考 Clojure 宏编程深度解析 了解 Leiningen 插件的内部宏机制,以及 Clojure 现代 Web 全栈开发 中 Ring/Leiningen 的整合方案。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。