获得闭包编译器和Node.js发挥很好
有没有使用node.js和闭包编译器(简称CC)的项目?
CC的官方build议是一起编译应用程序的所有代码,但是当我编译一些包含require("./MyLib.js")
简单node.js代码时,该行直接放入输出中,在这方面没有任何意义。
我看到几个选项:
- 将整个应用程序编码为单个文件。 通过避免它解决了这个问题,但是对维护不利。
- 假设所有文件在执行之前都会被连接起来。 这又避免了这个问题,但是使得实现一个未编译的debugging模式变得更加困难。
- 我希望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脚本:
- 使用plovr在
RAW
模式下构build一切。 - 在由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
总结:
- 我开始与我的主要模块,我要传递给节点,当我想要运行它的JS文件。
在我的情况下,这个文件是start.js 。 - 在这个文件中,使用正则expression式,我检测到所有
require()
调用,包括赋值部分。
在start.js中,这匹配了一个require调用:var Server = require('./lib/server.js');
- 我根据文件名检索文件存在的path,以string的forms获取其内容,并删除内容中的module.exports分配。
- 然后,我将步骤2中的require调用replace为步骤3中的内容。除非它是一个核心node.js模块,否则我将它添加到稍后保存的核心模块列表中。
- 第3步可能会包含更多的
require()
调用,所以我重复第3步和第4步,直到所有的require()
调用都消失了,剩下一个包含所有代码的巨大string。 - 如果所有的recursion都完成了,我使用REST API编译代码。
你也可以使用离线编译器。
我有每个核心node.js模块extern。 这个工具对于生成extern很有用 。 - 我预先删除core.js模块
require
调用编译的代码。
预编译的代码。
所有require
呼叫都被删除。 我所有的代码都被压扁了。
http://pastebin.com/eC2rVMiN
后编译的代码。
Node.js核心require
调用已经手动添加。
http://pastebin.com/uB8CaejN
为什么你不应该这样做:
- 它使用正则expression式(而不是parsing器或标记器)来检测
require
调用,内联和删除module.exports
。 这是脆弱的,因为它不包括所有的语法变化。 - 在内联时,所有模块代码都被添加到全局名称空间中。 这违背了Node.js的原则,每个文件都有自己的命名空间,如果你有两个不同的模块,并且有相同的全局variables,这将会导致错误。
- 它并没有提高代码的速度,因为V8还执行了很多代码优化,如内联和死代码移除。
为什么你应该:
- 因为当你有一致的代码时它确实有效。
- 当您启用详细的警告时,它将检测您的服务器代码中的错误。
在60秒内在Node.js上closures库。
支持,请查看https://code.google.com/p/closure-library/wiki/NodeJS 。
选项4:不要使用闭包编译器。
节点社区中的人们不倾向于使用它。 你不需要缩小node.js源代码,这很愚蠢。
简直没有什么用处。
至于closures的性能好处,我个人怀疑它实际上使你的程序更快。
当然,这是一个成本,debugging编译的JavaScript是一个噩梦