ClojureScript

外部声明

本指南需要 ClojureScript 1.10.238 或更高版本,并假设您熟悉 快速入门.

本页介绍如何为不符合 Google Closure Compiler 规范的第三方 JavaScript 库编写外部声明 https://developers.google.com/closure/compiler/docs/limitations.

动机

许多有用的库无法通过 Google Closure Compiler 高级编译。因此,它们不能成为构建的一部分,被视为“外来”库。但 Closure 必须了解这些库的一些信息,否则可能会意外地重命名属性。不幸的是,这种意外的重命名通常在最不合适的时候才会显现出来,例如在生产环境中。

对于已经拥有成熟外部声明的库来说,这种错误很容易避免。然而,这种要求为采用更新或不太流行但同样有用的库带来了极大的阻力。随着外部声明推断功能的出现,ClojureScript 编译器现在可以自动生成缺失的外部声明,并且可以极大地帮助编写全面外部声明的过程。

外部声明推断

假设我们已经指定了一个外来库 some.fooLib。我们希望针对该库编写互操作代码,并确信编译器会自动生成正确的外部声明,或者会提醒我们必须额外提供的外部声明。

为了启用外部声明推断,我们在编译器配置中指定 :infer-externs true

创建一个包含以下内容的 build.edn 文件

{:main my-project.core
 :output-to "out/main.js"
 :output-dir "out"
 :optimizations :none
 :infer-externs true})

但是这还不够让编译器生成有关外部声明的警告。由于在该功能出现之前编写了大量的库,因此我们无法以全局方式启用该功能。相反,有一个新的文件级编译器标志 *warn-on-infer*,它类似于 Clojure 中的 *warn-on-reflection*。一旦设置,编译器会在文件剩余部分的任何时候发出警告,只要它无法确定点形式(无论是属性访问还是方法调用)中涉及的类型。

(ns my-project.core
  (:require [some.fooLib]))

(set! *warn-on-infer* true)

(defn wrap-baz [x]
  (.baz x))

上面的代码将触发警告消息

Cannot infer target type in expression (.baz x) ...

我们只需要用外来类型对 x 进行类型提示,以进行此互操作调用

(ns my-project.core
  (:require [some.fooLib]))

(set! *warn-on-infer* true)

(defn wrap-baz [^js/Foo.Bar x]
  (.baz x))

现在,编译器拥有足够的信息来自动生成所需的外部声明。运行构建时,您将在输出目录中看到一个新文件 inferred_externs.js。如果您检查其内容,它可能类似于以下内容

var Foo = {};
Foo.Bar = function() {};
Foo.Bar.prototype.baz = function() {};

现在,无需完整外部声明即可轻松集成外来 JavaScript 库,并且减少了错误的可能性。

在某些情况下,您可能仍然希望编写外部声明,或者您可能是使用成熟外部声明的流行 JavaScript 库的用户,并且希望进行更多验证。下一部分将介绍外部声明推断提供的另一个有用功能。

返回值类型

局部类型提示对于自动化编写外部声明的过程非常有用。但是,对于互操作性很强的代码,这会导致大量的类型提示,特别是对于常用函数的返回值。在这种情况下,最好提供外部声明文件。即使在这种情况下,ClojureScript 编译器也可以简化该过程

(ns my-project.core
  (:require [some.fooLib]))

(set! *warn-on-infer* true)

(defn my-fn [^js/Foo.Bar x]
  (let [z (.baz x)]
    (.-wozz z)))

假设我们的外部声明文件类似于以下内容

var Foo = {};
/**
 * @constructor
 */
Foo.Bar = function() {};
Foo.Bar.prototype.baz = function() {};
/**
 * @constructor
 */
Foo.Boo = function() {};
Foo.Boo.prototype.woz = function() {};

但是,这不足以知道 ClojureScript 程序中 z 的类型。ClojureScript 编译器将发出以下警告

WARNING: Adding extern to Object for property wozz due to ambiguous expression (. z -wozz) ...

我们需要在外部声明文件中添加返回值类型信息

var Foo = {};
/**
 * @constructor
 */
Foo.Bar = function() {};
/**
 * @return {Foo.Boo} <-- CHANGED
 */
Foo.Bar.prototype.baz = function() {};
/**
 * @constructor
 */
Foo.Boo = function() {};
Foo.Boo.prototype.woz = function() {};

修改源文件并重新运行构建将导致不同的警告

WARNING: Cannot resolve property wozz for inferred type js/Foo.Boo in expression (. z -wozz)

如我们所见,ClojureScript 使用返回值类型信息来澄清问题。

原作者:David Nolen