【问题标题】:Getting closure-compiler and Node.js to play nice让闭包编译器和 Node.js 玩得很好
【发布时间】:2011-11-27 17:47:14
【问题描述】:

有没有同时使用node.js和closure-compiler(简称CC)的项目?

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

我看到了几个选项:

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

【问题讨论】:

  • require 表示您正在使用 RequireJS 或其他形式的 AMD 加载程序。一些 AMD 类型的系统包含一个工具,可让您“扁平化”所有依赖项并创建包含所有必要代码的单个文件。这就是使用 Closure 需要做的事情——拥有一个包含所有代码的文件。
  • @StephenChung 我认为他试图在 node.js 代码而不是浏览器端代码上运行闭包。
  • 我知道 Michael Bolin 已经做了一些工作来让 Closure Library 与 Node.js 一起工作。我不知道这是否也扩展到使用编译器。
  • @StephenChung: Advanced Micro Devices?
  • @John:求链接?当我搜索“michael bolin node.js”时,这个讨论是第二个结果。

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


【解决方案1】:

在 60 秒内关闭 Node.js 上的库。

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

【讨论】:

  • 自我提醒:我真的需要检查一下。
  • 这似乎主要是为了让闭包库与 node.js 一起工作,与编译器无关
【解决方案2】:

我一直在将 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 脚本:

  1. 使用 plovr 在 RAW 模式下构建所有内容。
  2. 在 plovr 生成的文件上运行 node

使用RAW 模式会导致所有文件的大量串联(尽管它还负责将 Soy 模板甚至 CoffeeScript 转换为 JavaScript)。诚然,这使得调试很痛苦,因为行号是无意义的,但到目前为止对我来说已经足够好了。 Closure Compiler 执行的所有检查都值得。

【讨论】:

  • 感谢您的详细解答!你能链接你引用的讨论吗?
  • 更新了我的答案以链接到groups.google.com/forum/#!searchin/closure-compiler-discuss/…的讨论
  • 这在未来会变得更简单吗?似乎有很多箍。
  • @bukzor:也许当 Google Closure Compiler (GCC) 专门支持 node.js 时。问题出在 node.js 核心模块中,这让 GCC 感到困惑。其他require()--common_js_modules--transform_amd_modules 支持。但我怀疑它会很快得到支持,因为性能提升可能很小。 V8 执行许多与 GCC 相同的优化,例如死代码删除和内联。也许内存占用会小一点,但这不会产生实质性的影响。
  • 我设法使用 Google Closure 的 REST API 编译 node.js 代码。在编译之前,我自己做了非核心require() 的内联,向 API 发送了一个大的扁平文件,其中删除了 Node.js 核心模块并进行了外部化。然后在编译后,我阅读了 Node.js 核心要求。 --- 见github.com/blaise-io/xssnake/blob/master/build/server.jsgithub.com/blaise-io/xssnake/blob/master/build/lib/…
【解决方案3】:

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

节点社区的人不倾向于使用它。你不需要缩小 node.js 源代码,那太傻了。

缩小根本没有用处。

至于闭包的性能优势,我个人怀疑它是否真的能让你的程序更快。

当然也是有代价的,调试编译好的 JavaScript 简直就是一场噩梦

【讨论】:

  • 仅供参考:我想使用优化和静态分析方面,而不仅仅是缩小。
  • @Raynos,我不同意。闭包不仅仅是为了缩小。肯定有性能优势。 OP询问如何做到这一点,而不是他是否应该首先这样做。节点社区中没有人使用它并不意味着不应该使用它。
  • @StephenChung 加速的好处是微不足道的。如果性能很重要,请用 C++ 重写它,而不是通过 CC 运行代码。
  • 我还想指出,“选项 4”不是解决问题的方法。 “不”不能回答“如何?”
  • @StephenChung 听起来确实很有用
【解决方案4】:

闭包编译器的svn HEAD好像有support for AMD

【讨论】:

    【解决方案5】:

    我用一种更简单的方法替换了我的旧方法:

    新方法

    • 我自己的应用代码没有 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 负责)。这使它更加自动化,但也更加脆弱。

    我只是在编译服务器代码的每一步中都添加了代码 cmets。请参阅此提交: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 模块的外部。 This tool is useful for generating externs
    7. 我预先将删除的 core.js 模块 require 调用编译后的代码。

    预编译代码。
    所有require 调用都将被删除。我所有的代码都被扁平化了。
    http://pastebin.com/eC2rVMiN

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


    为什么你不应该这样做:

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

    为什么你应该:

    1. 因为当您拥有一致的代码时它确实有效。
    2. 当您启用详细警告时,它将检测您的服务器代码中的错误。

    【讨论】:

    • 我知道 smjs 有一个parser interface。 v8 是否公开了类似的内容?
    • 我找不到任何关于 v8 公开解析器的文档。但是,可以使用用 JS 编写的 JS 解析器,例如 EsprimaIt outputs thisa trivial module。随着代码被很好地切碎,你可以以坚如磐石的方式做我对正则表达式所做的事情。但是进行修改然后将代码重新组合在一起将是很多工作。 This output for one of my real modules probably illustrates that.
    • 似乎 esprima 是我链接的 api 的实现,它并没有真正考虑可逆性。在我自己的一个项目中,我完成了a second pass on the smjs ast to make it reversible。也许它有帮助?可能不是=/
    • 我上次没有注意到这一点:Esprima 使用 an external library called Escodegen 重写源代码。如您在code of their minify example 中所见,Escodegen 显然与 Esprima 兼容。 --- 对我来说,Python 解决方案与我的大脑不兼容,因为我对用 JS 做所有事情都是强迫症。但是,我可能会修改我的 hacky 库以同时使用 Esprima 和 Escodegen 来创建一个可靠的模块,该模块使用 Closure Compiler 编译 所有 node.js 项目,而不仅仅是我的。
    • 这似乎是正确的路线。我不能保证我会“使用”它,但我非常喜欢这个主意。
    猜你喜欢
    • 2014-03-18
    • 2010-09-05
    • 1970-01-01
    • 2011-09-14
    • 2011-09-17
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多