让closure-compiler和Node.js发挥得很好

时间:2011-11-27 17:47:14

标签: javascript node.js require google-closure-compiler

是否有任何项目一起使用node.js和closure-compiler(简称CC)?

官方的CC建议是一起编译应用程序的所有代码,但是当我编译一些包含require("./MyLib.js")的简单node.js代码时,该行直接放入输出中,但它没有在这种背景下有任何意义。

我看到了几个选项:

  1. 将整个应用程序编码为单个文件。这可以通过避免它来解决问题,但不利于维护。
  2. 假设在执行之前将连接所有文件。这又避免了这个问题,但却使实现未编译的调试模式变得更加困难。
  3. 我想让CC“理解”node.js require()函数,但如果不编辑编译器本身就可能无法完成,可以吗?

5 个答案:

答案 0 :(得分:50)

我一直在使用Closure Compiler with Node来完成尚未发布的项目。它已经采用了一些工具,但它有助于捕获许多错误并且具有非常短的编辑 - 重启 - 测试周期。

首先,我使用plovr(这是我创建和维护的项目),以便一起使用Closure编译器,库和模板。我以Closure Library的样式编写Node代码,因此每个文件都定义了自己的类或实用程序集合(如goog.array)。

下一步是为要使用的Node函数创建一组externs文件。我在以下公开发表了其中一些内容:

https://github.com/bolinfest/node-google-closure-latitude-experiment/tree/master/externs/node/v0.4.8

虽然最终,我认为这应该是一个更加社区驱动的东西,因为有很多功能需要记录。 (这也很烦人,因为一些Node函数有可选的中间参数而不是最后一个参数,使得类型注释变得复杂。)我自己没有开始这个动作,因为我们可以用Closure Complier做一些工作来减少它的尴尬(见下文)。

假设您已为节点名称空间http创建了外部文件。在我的系统中,我已经决定,只要我需要http,我将通过以下方式包含它:

var http = require('http');

虽然我的代码中没有包含require()调用。相反,我使用Closure Compiler的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这样的东西,因为编译器将它们识别为externs。因为它们不是真正的外在因素,所以必须按照我的描述进行前提。

最后,在谈到这个on the discussion list之后,我认为更好的解决方案是为命名空间创建一个类型的新类型注释:

goog.scope(function() {

    /** @type {~NodeHttpNamesapce} */
    var http = require('http');

    // Use http throughout.

});

在这种情况下,externs文件将定义NodeHttpNamespace,以便Closure Compiler能够使用externs文件对其进行类型检查。这里的区别在于您可以将require()的返回值命名为您想要的任何内容,因为http的类型将是此特殊命名空间类型。 (为$标识“jQuery命名空间”是一个类似的问题。)这种方法可以消除为一致地为Node命名空间命名局部变量的需要,并且不需要在那里使用那个巨型output-wrapper。 plovr config。

但这是一个题外话......一旦我按照上面所述设置了东西,我就有了一个shell脚本:

  1. 使用plovr以RAW模式构建所有内容。
  2. 在plovr生成的文件上运行node
  3. 使用RAW模式会导致所有文件的大量连接(尽管它还负责将Soy模板甚至CoffeeScript转换为JavaScript)。不可否认,这使得调试变得很痛苦,因为行号是无稽之谈,但到目前为止我一直运作良好。 Closure Compiler执行的所有检查都使它值得。

答案 1 :(得分:6)

闭包编译器的svn HEAD似乎有support for AMD

答案 2 :(得分:3)

我用一种更简单的方法取代了我的旧方法:

新方法

  • 没有require()调用我自己的应用程序代码,仅适用于Node模块
  • 在运行或编译之前,我需要将服务器代码连接到单个文件
  • 使用a simple grunt script
  • 完成连接和编译

有趣的是,我甚至不必为require()电话添加外部电话。 Google Closure编译器自动理解这一点。我确实必须add externs for nodejs modules that I use.

旧方法

<击>

<击>

根据OP的要求,我将详细阐述使用Google Closure Compiler编译node.js代码的方法。

我受到了bolinfest解决问题的方式的启发,我的解决方案使用了同样的原则。不同之处在于我创建了一个执行所有操作的node.js脚本,包括内联模块(bolinfest的解决方案让GCC负责处理)。这使它更加自动化,但也更脆弱。

我刚刚为编译服务器代码的每一步添加了代码注释。请参阅此提交:https://github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a3

总结:

  1. 我从我的主模块开始,当我想运行它时,我传递给Node的JS文件 就我而言,此文件为start.js
  2. 在此文件中,使用正则表达式,我会检测所有require()个调用,包括分配部分 在start.js中,这匹配一个需要调用:var Server = require('./lib/server.js');
  3. 我根据文件名检索文件所在的路径,将其内容作为字符串获取,并删除内容中的module.exports赋值。
  4. 然后我将步骤2中的require调用替换为步骤3中的内容。除非它是核心node.js模块,否则我将其添加到我稍后保存的核心模块列表中。
  5. 第3步可能会包含更多require()个调用,所以我会递归重复第3步和第4步,直到所有require()调用都消失为止,我留下一个包含所有代码的大字符串。
  6. 如果所有递归都已完成,我使用REST API编译代码 您也可以使用离线编译器 我有每个核心node.js模块的externs。 This tool is useful for generating externs
  7. 我预先删除了已删除的core.js模块require对已编译代码的调用。

  8. 预编译代码。
    所有require来电都已删除。我的所有代码都被夷为平地 http://pastebin.com/eC2rVMiN

    编译后的代码。
    Node.js核心require调用已手动添加 http://pastebin.com/uB8CaejN


    为什么你不应该这样做:

    1. 它使用正则表达式(不是解析器或标记器)来检测require个调用,内联和删除module.exports。这很脆弱,因为它不包括所有语法变体。
    2. 内联时,所有模块代码都会添加到全局命名空间中。这违反了Node.js的原则,其中每个文件都有自己的命名空间,如果你有两个不同的模块具有相同的全局变量,这将导致错误。
    3. 它不会大大提高代码的速度,因为V8还执行了许多代码优化,如内联和死代码删除。
    4. 为什么你应该:

      1. 因为当您拥有一致的代码时它会起作用。
      2. 启用详细警告时,它将检测服务器代码中的错误。
      3. <击>

答案 3 :(得分:2)

Node.js上的Closure Library在60秒内完成。

支持,请检查https://code.google.com/p/closure-library/wiki/NodeJS

答案 4 :(得分:-22)

选项4:不要使用闭包编译器。

节点社区中的人不倾向于使用它。你不需要缩小node.js源代码,这很愚蠢。

根本没有用于缩小的好处。

关于关闭的性能优势,我个人怀疑它实际上会使你的程序更快。

当然还有成本,调试编译的JavaScript是一场噩梦