ClojureScript

ClojureScript 不是一座孤岛:集成 Node 模块

2017 年 7 月 12 日
António Nuno Monteiro

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

ClojureScript 从 2011 年首次发布以来就一直拥有与 JavaScript 的一流互操作性。与 Clojure 相似,拥抱主机始终是明确的目标。虽然以上内容从语法角度来说是正确的,但与外部 JavaScript 库的集成在历史上需要一些手动工作 [1] (手动组装 包或由 社区努力 引导的打包)。

迈向与外部 JavaScript 更好互动的第一步

2015 年 Google 暑期代码项目为 ClojureScript 成功地使与外部 JavaScript 模块的交互变得更容易。依赖于 Google Closure Compiler 当时新推出的理解大多数知名 JavaScript 模块格式并将其转换为 Closure 兼容 JavaScript 的功能 [2]Maria Geller 成功地将这些模块功能集成到 ClojureScript 中,以及 自定义预处理 步骤,允许使用诸如流行的 JavaScript 编译技术 Babel 等功能,轻松地在你的 ClojureScript 项目中使用 [3]

从那时起,Closure Compiler 团队在 2016 年添加了模块解析支持,这在其他方面使能够直接从node_modules 安装中使用模块,而无需为 Closure 提供手工制作的模块路径以进行转换。

与 NPM 依赖项的无缝交互

我们在 Maria 的模块工作基础上进行了构建,以解决这一新的 Closure 功能,ClojureScript 的下一个版本代表着另一个重要里程碑,它通过对 ClojureScript 如何与 NPM 生态系统交互的方式进行重大改进,使任意 JavaScript 模块对每个 ClojureScript 项目都可用。

从 ClojureScript 命名空间中获取 NPM 模块 自 ClojureScript 1.9.518 版本以来就已成为可能。但是,Node.js 支持多种模式来获取 CommonJS 模块,一个常见的问题是人们想要从 ClojureScript 中获取 "react-dom/server" 形式的模块,这对于:require 规范来说将不是一个有效的符号。

在此版本中,我们在命名空间形式中添加了对基于字符串的获取的支持,以解决上述问题。你现在可以从你的 ns 声明中轻松地获取这些类型的模块。

将这一切整合在一起的是:npm-deps 编译器标志。在其中,我们告诉编译器它应该知道哪些依赖项。ClojureScript 将负责安装这些依赖项并运行它们以通过 Closure Compiler 转换管道,包括我们在下面更详细地描述的优化。

一个实际的例子

给定一个如下所示的 build.clj 文件

(require '[cljs.build.api :as b])

(b/build "src"
  {:output-dir "out"
   :output-to "out/main.js"
   :optimizations :none
   :main 'example.core
   :install-deps true
   :npm-deps {:react "15.6.1"
              :react-dom "15.6.1"}})

你最简单的 src/example/core.cljs 文件可能如下所示

(ns example.core
  (:require [react :refer [createElement]]
            ["react-dom/server" :as ReactDOMServer :refer [renderToString]]))

(js/console.log (renderToString (createElement "div" nil "Hello World!")))

请注意,我们不必在任何地方声明 "react-dom/server"。我们可以直接获取它。ClojureScript 现在足够智能,可以找到这些 CommonJS 模块并将它们处理成 Google Closure Compiler 兼容的代码。

这是一件大事

使用 Google Closure 来使用 JavaScript 模块的影响是巨大的:ClojureScript 项目中使用的外部库不再只是追加到生成的包中,现在可以应用所有 Closure Compiler 的优化,包括死代码消除,以及在利用 代码拆分 的项目中跨模块代码移动。例如,在我们的测试中,React 在 Closure 的高级编译下比使用现有的流行 JavaScript 工具要小很多(大约 16%)[4]。此外,如果你有一个混合的 ClojureScript 和 JavaScript 代码库,你不仅可以现在无缝地使用代码的那些 JavaScript 部分(包括例如 JSX 转换!),还可以共享并将其供应商依赖项与 ClojureScript 部分使用的依赖项打包在一起。

在 Node.js 上也能用!

值得注意的是,ClojureScript 中的模块处理功能主要用于针对浏览器的项目,其中依赖项通常会捆绑在一起。但这并不意味着针对 Node.js 的项目也不能利用此功能。实际上,我们使其能够让你在针对 Node.js 时也能够从命名空间声明中无缝地获取本地 node_modules 安装中的 Node 模块。ClojureScript 将知道你正在获取 Node 模块,并生成一个 require 声明,与 Node.js 自身的加载 JavaScript 模块的功能集成在一起。

告别语

ClojureScript 经过将近 6 年的发展,已成为全球众多开发人员信赖的平台,我们希望继续实现与主机的完全互操作性。通过确保我们与那里庞大的 JavaScript 生态系统集成,我们认为这些在 ClojureScript 下一个版本中出现的新功能是确保 ClojureScript 长期可持续性的垫脚石。

我们希望你像我们一样享受这些新功能。感谢阅读!


1. 除了 Google Closure 库 自 ClojureScript 最初发布以来就深度集成。
2. Closure Compiler 需要执行优化的 JavaScript 子集,例如死代码消除(树摇)、变量名称重写、常量折叠和内联等。
3. Maria 在从事该项目期间还定期写博客,你可以在 此处 阅读。
4. 事实上,Reagent 团队已经在测试 其网站的版本 来使用 NPM 模块。他们还 对比了 它与以前的版本。