获得闭包编译器和Node.js发挥很好

有没有使用node.js和闭包编译器(简称CC)的项目?

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

我看到几个选项:

  1. 将整个应用程序编码为单个文件。 通过避免它解决了这个问题,但是对维护不利。
  2. 假设所有文件在执行之前都会被连接起来。 这又避免了这个问题,但是使得实现一个未编译的debugging模式变得更加困难。
  3. 我希望CC能够“理解”node.js require()函数,但是如果不编辑编译器本身,可能无法做到这一点。

我一直在使用Node的Closure编译器来完成一个我还没有发布的项目。 它需要一些工具,但是它有助于捕捉许多错误,并且具有相当短的编辑 – 重新启动testing周期。

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

下一步是为您要使用的Nodefunction创build一堆extern文件。 我公开发表了其中一些公开的内容:

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

尽pipe最终,我认为这应该是一个更多的社区驱动的东西,因为有很多的function来logging。 (这也很麻烦,因为一些Node函数有可选的中间参数而不是最后一个参数,这使得types注解变得复杂了。)我还没有开始这个动作,因为我们可能可以用Closure Complier做一些工作,使它不那么笨拙(见下文)。

假设您已经为Node名称空间http创build了externs文件。 在我的系统中,我决定随时需要http ,我将通过以下方式将其包含在内:

 var http = require('http'); 

虽然我不包括require()在我的代码中调用。 相反,我使用Closure Compiler的output-wrapper特性,在文件的开头预先指定了所有的require() ,在我当前的项目中声明为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之类的东西,因为编译器将它们识别为extern。 因为他们不是真正的外部人,他们必须像我所描述的一样。

最后,在讨论清单中讨论这个之后,我认为更好的解决scheme是为命名空间提供一个新的types注释,类似于:

 goog.scope(function() { /** @type {~NodeHttpNamesapce} */ var http = require('http'); // Use http throughout. }); 

在这种情况下,externs文件将定义NodeHttpNamespace ,使得Closure编译器能够使用externs文件对属性进行types检查。 这里的区别是你可以任意指定require()的返回值,因为http的types是这个特殊的名字空间types。 (为$标识一个“jQuery命名空间”是一个类似的问题。)这种方法将消除需要一致地为Node名称空间命名本地variables,并且不需要在plovrconfiguration中使用这个巨大的output-wrapper

但是这是一个题外话…一旦我有如上所述设置的东西,我有一个shell脚本:

  1. 使用plovr在RAW模式下构build一切。
  2. 在由plovr生成的文件上运行node

使用RAW模式会导致所有文件的大型连接(尽pipe它也会将大豆模板甚至CoffeeScript翻译成JavaScript)。 无可否认的是,这使得debugging变得非常痛苦,因为行号是无稽之谈,但是到目前为止,对于我来说工作已经足够好了。 所有由Closure编译器执行的检查都是值得的。

封闭编译器的svn HEAD似乎支持AMD

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

新的方法

  • 没有require()调用我自己的应用程序代码,只有Node模块
  • 我需要将服务器代码连接到单个文件,然后才能运行或编译它
  • 连接和编译使用简单的grunt脚本来完成

有趣的是,我甚至不必为require()调用添加一个extern。 Google Closure编译器可以自动理解。 我不得不为我使用的nodejs模块添加extern。

老办法

按照OP的要求,我将详细介绍用Google Closure Compiler编译node.js代码的方法。

我从bolinfest解决问题的方式受到启发,我的解决scheme使用相同的原则。 不同的是,我做了一个node.js脚本来完成一切,包括内联模块(bolinfest的解决scheme让GCC处理这个问题)。 这使得它更自动化,但也更脆弱。

我只是将代码注释添加到编译服务器代码的每一个步骤中。 看到这个提交: https : //github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a3

总结:

  1. 我开始与我的主要模块,我要传递给节点,当我想要运行它的JS文件。
    在我的情况下,这个文件是start.js 。
  2. 在这个文件中,使用正则expression式,我检测到所有require()调用,包括赋值部分。
    在start.js中,这匹配了一个require调用: var Server = require('./lib/server.js');
  3. 我根据文件名检索文件存在的path,以string的forms获取其内容,并删除内容中的module.exports分配。
  4. 然后,我将步骤2中的require调用replace为步骤3中的内容。除非它是一个核心node.js模块,否则我将它添加到稍后保存的核心模块列表中。
  5. 第3步可能会包含更多的require()调用,所以我重复第3步和第4步,直到所有的require()调用都消失了,剩下一个包含所有代码的巨大string。
  6. 如果所有的recursion都完成了,我使用REST API编译代码。
    你也可以使用离线编译器。
    我有每个核心node.js模块extern。 这个工具对于生成extern很有用 。
  7. 我预先删除core.js模块require调用编译的代码。

预编译的代码。
所有require呼叫都被删除。 我所有的代码都被压扁了。
http://pastebin.com/eC2rVMiN

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


为什么你不应该这样做:

  1. 它使用正则expression式(而不是parsing器或标记器)来检测require调用,内联和删除module.exports 。 这是脆弱的,因为它不包括所有的语法变化。
  2. 在内联时,所有模块代码都被添加到全局名称空间中。 这违背了Node.js的原则,每个文件都有自己的命名空间,如果你有两个不同的模块,并且有相同的全局variables,这将会导致错误。
  3. 它并没有提高代码的速度,因为V8还执行了很多代码优化,如内联和死代码移除。

为什么你应该:

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

在60秒内在Node.js上closures库。

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

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

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

简直没有什么用处。

至于closures的性能好处,我个人怀疑它实际上使你的程序更快。

当然,这是一个成本,debugging编译的JavaScript是一个噩梦