ClojureScript

简化 JavaScript 预处理

2017 年 7 月 20 日
Juho Teperi

这是 抢先预览 系列的第四篇文章。

Closure 编译器可以处理 AMD、CommonJS 和 ES6 模块,从而为 Node 生态系统 提供广泛的支持。即使如此,Closure 也不直接支持一些流行的用例,例如 ReactJSX[1] Maria Geller 的 Google 暑期代码项目也基于 David Nolen 提出的设计方案解决了这个问题,并且从 1.7.48 版本 开始就存在一个预处理钩子。然而,原始设计使得与其他构建工具的集成变得困难,我们已经修改了这种方法来简化这种集成。

动机

使用 JSX 或其他语法扩展的 JavaScript 库通常与已处理的代码一起打包,因此它们无需任何额外操作即可使用。不过,在某些情况下,用户可能希望直接在项目中包含此类代码。例如,在将项目从 JavaScript 转换为 ClojureScript 时,重用现有代码比尝试一次性重写所有内容要容易得多。此外,构建复杂的用户界面是一个团队合作的过程,而 JSX 等工具可以通过为设计师提供熟悉的工具来使这个过程更加友好。通过允许 ClojureScript 访问 JSX 和其他流行的语法特性,ClojureScript 开发流程可以更加包容。

JavaScript 变换

在原始设计中,预处理是通过向 foreign-lib 映射 提供 :preprocess 来启用的。该值是针对 cljs.closure/js-transforms 多方法的关键字分派。用户可以实现新的多方法案例,例如使用 Java 的内置 Nashorn JavaScript 引擎来运行像 Babel 这样的 JavaScript 编译器。

然而,这种方法会给流行的 Clojure 和 ClojureScript 构建工具带来一些问题。提供预处理多方法的 Clojure 命名空间必须在运行 ClojureScript 编译器之前由用户加载。虽然这在显式构建脚本中是可行的,但由于 LeiningenBoot 将构建隔离到自己的类路径中,因此这种需求实际上并不实用。

有几种方法可以解决原始设计的问题

  1. 提供一个新的配置选项,用于在运行编译器之前列举要 require 的命名空间。

  2. 在多方法分派关键字和提供实现的命名空间之间建立一种关系。例如,如果关键字是带命名空间的,那么关键字的命名空间部分可以用来按需 require 该命名空间。

这两种解决方案都可以在构建工具中或直接在 ClojureScript 编译器中实现。从最终用户的角度来看,第一个选项似乎很绕,而第二个选项虽然强调了根本问题,但使用关键字来实现这种模式似乎并不符合惯例。如果我们简单地将关键字切换为符号,我们就可以与现有的先例保持一致。

预处理符号

ClojureScript 的下一个版本将支持符号作为 :preprocess 选项值的类型。使用完全限定的符号可以清楚地表明该值是指一个函数,并且符号的命名空间部分可以用来自动加载用户指定的命名空间。

cljsjs/babel-standalone 已经更新,并且提供了一种简单的方法,可以使用 Babel 与 ClojureScript 工具一起使用,遵循这种新的模式。

结论

熟悉 Clojure 理念的用户都知道,我们优先考虑简洁性,胜过其他大多数特性。但是,简洁性并不总是与易用性冲突,事实上,长期以来,简洁性一直是语言与宿主系统互操作性方面的一个优先事项。

虽然 Clojure 一直拥有与 Java 良好的集成,但对于 ClojureScript 来说,Google Closure 与主流 JavaScript 实践之间的摩擦使得这一承诺更难实现。

我们相信我们正在缩小差距,并且庞大的 JavaScript 库生态系统现在终于触手可及。


1. 还有一个 第三方编译器传递 用于 Closure 以支持 JSX