介绍一下 shadow-cljs 的使用和体验 (ClojureScript 编译工具)

1,453 阅读5分钟
原文链接: clojure-china.org

仓库 github.com/thheller/sh…
npm www.npmjs.com/package/sha…

shadow-cljs 是一个 ClojureScript 编译器的封装,
和 lein-cljsbuild + lein-figwheel 或者 boot-cljs + lein-reload 功能类似.
它能够提供对于浏览器平台和 Node.js 平台代码编译, 优化, 热替换.
这个项目是最近才冒出来的, 近期做了不少的修改, 名字也定下来不久.

作者是 Thomas Heller twitter.com/thheller1 在 Clojurians 群里好像一直很活跃,
作者经常说: 我在等你们试用然后提 issue...

优点

shadow-cljs 相对于以往的方案有两个优点, 主要针对 JavaScript 开发者:

  • 通过 npm 安装
npm i shadow-cljs

系统里安装了 npm 的同学都可能很快安装上 shadow-cljs,
我建议在项目根路径安装, 然后通过 npm script 调用, 目前这是 js 社区常用的玩法.
JVM 被工具内部管理了, 不需要复杂的配置, 尽管功能会局限一点.
如果系统已经安装 JVM 会直接用, 没有的话估计要全局装一下 node-jre 模块.

  • 生成 CommonJS 代码

跟以往的编译器不一样的是, shadow-cljs 支持生成 CommonJS 格式的代码,
我们知道 ClojureScript 用了 Closure Compiler 的命名空间的, 跟 Node 不兼容,
而 shadow-cljs 做的是强行在代码当中插入了 CommonJS 的 require/exports 代码,
总体上已经能做到正常地放进 Webpack 打包, 以及用 node 命令直接运行编译结果.

用法

前几周更新比较频繁, 可能后面细节会调整, 这里基于 0.9.4 介绍.

  • 编译到 CommonJS

shadow-cljs 的配置文件是 EDN 文件, 大致结构是:

{:dependencies []
 :source-paths ["src"]
 :builds {:app {:target :npm-module
                :output-dir "compiled/"}}}

这里定义了一个编译策略叫做 app, 命令行当中就可以使用这个配置:

shadow-cljs --build app --once

这个命令就会把 src/ 目录下的 cljs 文件统一编译到 compiled/ 目录下面.
其中 :npm-module 这个选项设定了编译结果是 npm 模块用的文件.
除了 --once, 还有两个常用的命令:

shadow-cljs --build app --dev # 监视文件修改自动编译
shadow-cljs --build app --release # 使用 :advanced 模式编译优化代码

--dev 模式下, 文件类似增量编译, 可以被 Webpack 监听到,
通过这个方式, CommonJS 文件可以和 Webpack 比较好地配合使用.

可以翻阅我写的例子 github.com/minimal-xyz…
配合 Webpack 热替换的例子 github.com/minimal-xyz…
也可以看 Wiki github.com/thheller/sh…

  • 编译到浏览器

如果是编译到浏览器, 作者提供的 demo 是这样的:

{:dependencies []
 :source-paths ["src"]
 :builds {:app {:target :browser
                :output-dir "public/js"
                :asset-path "/js"
                :modules {:main [my.app]}}}}

如果要开启代码热替换, 需要配置 :devtools 选项激活热替换的代码:

{:target :browser
 ; ...
 :devtools {:before-load my.app/stop
            :after-load my.app/start}}

激活热替换之后, 还可以启动 REPL 用来发送 cljs 到浏览器执行.

:modules 配置用来指定程序启动的入口的文件, 前面 npm 模块里用不到这个,
其他还提供了一些相对高级的策略比如分包, 用来处理处理浏览器环境的细节,
具体看 Wiki github.com/thheller/sh…
不过我倾向还是用 Webpack 来处理奇怪的场景, 毕竟 js 这边套路比较多.

  • 编译到 Node.js 代码

大体上跟浏览器的配置很像, 但是提供了两个 :target 配置 :node-library:node-script.
:node-scriptnpm-module 的用法很像, 我没用过, 估计是对 Node.js API 做了处理,
跟浏览器配置差不多, 也支持热替换, 可以看我写的例子:
github.com/minimal-xyz…
关于编译的细节在 Wiki 上也有一些描述, 自己看啦, 估计作者文档没写完...

:advanced 模式

这个是 Closure Compiler 提供的编译选项, 用于代码优化.
以往的 ClojureScript 编译器也是支持的, 只是在这里针对 CommonJS 特殊处理了一下.
技术细节可以阅读 github.com/thheller/sh…

ClojureScript 代码编译结果比较大, 需要剔除无用的代码, 要开启 :advanced 模式
但是这是需要写 externs 文件指明如何保留不删除和混淆的代码的...
对于大多数人来说编写 externs 是一件很头疼的事情, 所以还是要想点办法,
目前作者也讲还在考虑当中, 看如何才能把这个过程简化一下.

对比

大致使用体验的话, 可能启动会快一点, 毕竟不像 Boot 那么多初始化的东西,
不过 shadow-cljs 不提供 compile warning 在页面弹提示, 可能会不习惯,
现在应该遇到编译出错会在 Console 里打印的. 其他开发体验应该还好.
我是说直接用 :browser 模式开发, 工作流程其实差不多,

如果是借助 Webpack 打包的话, 麻烦会多一点, 特别是 SourceMaps 基本废了.
用了 Webpack 的话, 相当于是经过两个不同的工具编译, 坑比较多的.
但是引入 Webpack 主要还是为了 npm 模块还有打包之类的事情的方便吧.

Node 这边体验好一点, 以前的 lein-figwheel 虽然功能强大, 但是配置太烦了,
shadow-cljs 是开箱即用, :devtools 直接配好热替换, 直接有 REPL,
另外 Lumo 虽然也能玩 Node, 我的感觉是启动太慢了,
shadow-cljs 这边 watching compiling 开起来, 然后就是 node 启动的速度了.
而且 Lumo 的做法, 热替换需要自己处理, --inspect 选项不能用, 不够好.

其他

总体感觉是开启了很多的可能性, 特别是对 js 开发者能友好很多.
之前配置 classpath 真是搞得烦死了, 现在完全不用管.
最近 cljs 社区似乎在接入 npm 方面做了一些努力, 感觉能舒心一点.

现在看看开发当中, 还有往 Clojars 上传模块这个过程离不开 Boot 或者 Lein,
不知道近期有没有新的方案出来, 那样的话我手头的 build.boot 就能删了.
有点盼头总是好的....