shadow-cljs 是一个新开发的 ClojureScript 开发和编译工具.
以前编译主要是 lein-cljsbuild, boot-cljs, lein-figwheel,
现在新的工具 Lumo 和 shadow-cljs 也可以完成编译工作了.
特别是 shadow-cljs 的功能覆盖开发当中很多场景, 对 JavaScript 开发者更友好.
对于前端开发者来说, shadow-cljs 上手也非常简单, 不需要去管 JVM 的事情.
安装 shadow-cljs 非常简单, 通过 npm 的命令来就好了:
npm install -g shadow-cljs
比如你有一个 ClojureScript 项目, 命名空间叫 app
, 对应目录结构:
$ tree src/
src/
└── app
├── lib.cljs
└── main.cljs
如何编译
就像 Webpack 一样, 编译之前需要有一些配置, 源码在哪里, 编译到哪里, 之类的,
由于 ClojureScript 有着自己的依赖管理工具, 所以依赖也要写在这个文件里:
{:source-paths ["src/"]
:dependencies []
:builds {:app {:output-dir "target/"
:asset-path "."
:target :browser
:modules {:main {:entries [app.main]}}
:devtools {:after-load app.main/reload!}}}}
几个关键的参数大概的意思:
-
:target
表示编译目标, 这里选择:browser
, 生成代码用于在浏览器当中运行. -
:devtools
表示开发环境的设置, 这里我设置了热替换完成之后执行函数app.main/reload!
-
:asset-path
资源存储的路径, 相对于target/
, 也影响到网页上引用代码. 默认似乎是./
.
更多的参数可以到文档站点上查阅: http://doc.shadow-cljs.org/
配置完成之后可以用 shadow-cljs
命令来编译代码, 常用的子命令有:
shadow-cljs compile app # 每个文件直接编译到对应一个 js 文件
shadow-cljs release app # 编译, 进行代码合并和优化, 以及清除 dead code
其中的 app
就是配置里的 :app
, 也叫 build id. 也就是说会有多个 build id 可以配置.
开发过程中, 最常用的是 watch
命令, 它就像 webpack-dev-server,
ClojureScript 相比 js 来说每个函数副作用更少, 所以更适合进行热替换,
基于上边的配置, 在每次文件更新, 浏览器就会进行代码热替换, 然后会触发 app.main/reload!
函数:
shadow-cljs watch app # 启动编译器, 监视文件更新自动编译
ClojureScript 有一些基础的静态检查功能, 相当于加强的 lint 工具,
所以编译当中会检查代码代码并打印警告, 以及的浏览器当中弹出警告内容.
此外命令行工具还提供了其他一些开发当中用到的功能:
shadow-cljs cljs-repl app # 有 watch 服务的情况下, 再启动一个连接到浏览器的 REPL
shadow-cljs check app # 进行 release 之前可以做一些检查
shadow-cljs release app --debug # 生成 release 的代码, 同时生成 SourceMaps 等用于调试
更多的子命令可以查阅 http://doc.shadow-cljs.org/
编译目标
shadow-cljs 支持多个编译目标, 也就是对应 :target
的配置, 一般有:
-
:browser
运行在浏览器的代码 -
:node-script
运行在 Node.js 的代码 -
:node-library
可以被 Node.js JavaScript 代码调用的模块 -
:npm-module
遵循 CommonJS 语法的独立的 js 文件
我使用最多是 :browser
, 功能完善, 已经能够胜任目前网页应用的开发需求了,
而且 :browser
模式的打包也逐渐成熟了, 补上了一些 Webpack 中的常用功能.
在某些只能通过 Webpack 打包情况下, 可以使用 :npm-module
作为一种兼容模式, :npm-module
模式编译的代码符合 CommonJS 规范, 可以被 Webpack 用于打包(注意这样打包带上 ClojureScript 的 runtime 代码是挺大的).
:node-script
用于开发 Node.js 脚本, 这里热替换也是基本一致的配置.
至于 :node-library
我还没用过, 参考文档应该是暴露结构给 Node.js 脚本调用.
关于这些模式具体的用途, 我搜集了一些例子, 可以参考:
- https://github.com/minimal-xy...
- https://github.com/minimal-xy...
- https://github.com/minimal-xy...
- https://github.com/minimal-xy...
- https://github.com/minimal-xy...
配置项
除了上面的例子, shadow-cljs 的配置项还有不少, 我拿自己的脚手架配置作为例子:
{:source-paths ["src"]
:dependencies [[mvc-works/hsl "0.1.2"]
[mvc-works/shell-page "0.1.3"]
[mvc-works/verbosely "0.1.0-rc"]
[respo/ui "0.1.9"]
[respo/reel "0.2.0-alpha3"]
[respo "0.6.4"]]
:http {:host "localhost" :port 8081}
:open-file-command ["subl" ["%s:%s:%s" :file :line :column]]
:builds {:browser {:target :browser
:output-dir "target/browser"
:asset-path "/browser"
:modules {:main {:entries [app.main]
:depends-on #{:lib}}
:lib {:entries [respo.core respo.macros
respo.comp.inspect]}}
:devtools {:after-load app.main/reload!
:http-root "target"
:http-port 7000}
:release {:output-dir "dist/"
:module-hash-names true
:build-options {:manifest-name "cljs-manifest.json"}}}
:ssr {:target :node-script
:output-to "target/ssr.js"
:main app.render/main!
:devtools {:after-load app.render/main!}}}}
其中出现了些前面没有有道的配置, 我拎出来解释一下:
shadow-cljs 内置了一个 HTTP 服务器用于网页的调试,
需要在 :devtools
的配置当中添加 HTTP 相关的配置:
:devtools {:http-root "target"
:http-port 7000}
watch
模式当中遇到代码存在顺发错误, 浏览器上会有界面显示 warning,
shadow-cljs 支持点击代码打开编辑器对应的行, 通过配置打开文件的命令,
传输我用 Sublime Text 打开, 这个命令当中精确到行列:
:open-file-command ["subl" ["%s:%s:%s" :file :line :column]]
前端单页面应用倾向于生成代码到 vendor.js
和 main.js
两个文件,
shadow-cljs 支持将生成代码拆分为多个文件, 这里就拆分成了 main.js
和 lib.js
,
并且, 其中指定了 main
对 lib
的依赖, 以及 lib
包含哪些命名空间:
:modules {:main {:entries [app.main]
:depends-on #{:lib}}
:lib {:entries [respo.core respo.macros
respo.comp.inspect]}}
开发环境的配置, 除了常用的 :after-load
, 还有 :before-load
等:
:devtools {:after-load app.main/reload!}
注意 :release
的配置是写在 :browser
配置内部的, 表示覆盖重复的配置,
这是 shadow-cljs 提供的一个简写, 你也可以自己专门写一遍 :release
的配置.
比如说 :output-dir "dist/"
就覆盖了外面的配置 :output-dir "target/"
.:module-hash-names
声明对生成的文件名加上 MD5 方便放 CDN.
最后一行的配置是重命名 manifest.json
文件, 其中包含前面生成的带 MD5 的文件名,:
:release {:output-dir "dist/"
:module-hash-names true
:build-options {:manifest-name "cljs-manifest.json"}}}
:release
的配置可也支持别的配置, 比如这里的 8
表示 Hash 的长度,
manifest 文件除了 JSON, 也可以通过文件后缀支持生成 EDN 文件:
:release {:output-dir "dist/"
:module-hash-names 8
:build-options {:manifest-name "assets.edn"}}}
可以看很多随着 Webpack 而在前端广泛使用的功能, 在 shadow-cljs 当中做了不少的支持.
代码拆包以后, shadow-cljs 不好做异步加载, 这个是有些不足, 可以向官方反馈.
npm 模块
shadow-cljs 2.x 版本带来了在 :browser
编译目标的 npm 模块的支持, 注意写法:
(ns app.main
(:require ["hsl" :as hsl]))
(hsl 200 80 80)
:node-script
编译目标或者 :npm-module
当中也支持这样写:
(def hsl (js/require "hsl"))
(hsl 200 80 80)
因为 require
在 Node 当中直接是函数, 在前端也可以被 Webpack 进一步处理.
大部分 npm 模块都可以直接用到的 ClojureScript 项目当中.
除此之外, Lumo 和官方的 ClojureScript 编译器也改善了对 npm 模块支持.
小结
shadow-cljs 的文章已经做得比较完善, 可以访问 http://doc.shadow-cljs.org 查阅.
如果遇到问题或者想要反馈, 可以通过下面两个地址提交:
https://github.com/thheller/s...
https://clojureverse.org/c/pr...
英语够好的话甚至直接到聊天室上找到作者, 作者在欧洲, 注意时差:
https://clojurians.slack.com/...
2017 年秋天至今 shadow-cljs 作者都在很积极更新功能,
很多的 bug 都以非常快的速度修复了, 让 shadow-cljs 更加友好.