我一直在将 Closure Compiler 与 Node 一起用于我尚未发布的项目。它需要一些工具,但它帮助捕获了许多错误,并且编辑-重启-测试周期非常短。
首先,我使用plovr(这是我创建和维护的一个项目)以便一起使用闭包编译器、库和模板。我以 Closure 库的风格编写 Node 代码,因此每个文件都定义了自己的类或实用程序集合(如 goog.array)。
下一步是为要使用的 Node 函数创建一堆 externs 文件。我在以下位置公开发表了其中一些:
https://github.com/bolinfest/node-google-closure-latitude-experiment/tree/master/externs/node/v0.4.8
尽管最终,我认为这应该是一个更受社区驱动的事情,因为有很多功能需要记录。 (这也很烦人,因为一些 Node 函数有可选的中间参数而不是最后一个参数,这使得类型注释变得复杂。)我自己还没有开始这个动作,因为我们可以使用 Closure Complier 做一些工作来减少这种尴尬(见下文)。
假设您已经为 Node 命名空间 http 创建了 externs 文件。在我的系统中,我决定无论何时我需要http,我都会通过以下方式包含它:
var http = require('http');
虽然我的代码中没有包含 require() 调用。相反,我使用闭包编译器的output-wrapper 功能在文件开头添加所有require()s,当在plovr 中声明时,在我当前的项目中如下所示:
"output-wrapper": [
// Because the server code depends on goog.net.Cookies, which references the
// global variable "document" when instantiating goog.net.cookies, we must
// supply a dummy global object for document.
"var document = {};\n",
"var bee = require('beeline');\n",
"var crypto = require('crypto');\n",
"var fs = require('fs');\n",
"var http = require('http');\n",
"var https = require('https');\n",
"var mongodb = require('mongodb');\n",
"var nodePath = require('path');\n",
"var nodeUrl = require('url');\n",
"var querystring = require('querystring');\n",
"var SocketIo = require('socket.io');\n",
"%output%"
],
这样,我的库代码从不调用 Node 的 require(),但编译器允许在我的代码中使用 http 之类的东西,因为编译器将它们识别为外部。由于它们不是真正的外部变量,因此必须按照我的描述将它们放在前面。
最终,在谈到这个on the discussion list 之后,我认为更好的解决方案是为命名空间添加一个新的类型注释,如下所示:
goog.scope(function() {
/** @type {~NodeHttpNamesapce} */
var http = require('http');
// Use http throughout.
});
在这种情况下,外部文件将定义NodeHttpNamespace,以便闭包编译器能够使用外部文件对其属性进行类型检查。这里的不同之处在于,您可以随意命名require() 的返回值,因为http 的类型将是这个特殊的命名空间类型。 (为$ 标识一个“jQuery 命名空间”是一个类似的问题。)这种方法将消除为节点命名空间一致命名局部变量的需要,并且将消除在 plovr 配置中对那个巨大的 output-wrapper 的需要。
但那是题外话...一旦我按照上述方式进行设置,我就有一个 shell 脚本:
- 使用 plovr 在
RAW 模式下构建所有内容。
- 在 plovr 生成的文件上运行
node。
使用RAW 模式会导致所有文件的大量串联(尽管它还负责将 Soy 模板甚至 CoffeeScript 转换为 JavaScript)。诚然,这使得调试很痛苦,因为行号是无意义的,但到目前为止对我来说已经足够好了。 Closure Compiler 执行的所有检查都值得。