简体   繁体   English

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

[英]Getting closure-compiler and Node.js to play nice

Are there any projects that used node.js and closure-compiler (CC for short) together? 是否有任何项目一起使用node.js和closure-compiler(简称CC)?

The official CC recommendation is to compile all code for an application together, but when I compile some simple node.js code which contains a require("./MyLib.js") , that line is put directly into the output, but it doesn't make any sense in that context. 官方的CC建议是一起编译应用程序的所有代码,但是当我编译一些包含require("./MyLib.js")简单node.js代码时,该行直接放入输出中,但它没有在这种背景下没有任何意义。

I see a few options: 我看到几个选项:

  1. Code the entire application as a single file. 将整个应用程序编码为单个文件。 This solves the problem by avoiding it, but is bad for maintenance. 这通过避免它来解决问题,但是对于维护是不利的。
  2. Assume that all files will be concatenated before execution. 假设在执行之前将连接所有文件。 Again this avoids the problem, but makes it harder to implement a un-compiled debug mode. 这又避免了这个问题,但却使实现未编译的调试模式变得更加困难。
  3. I'd like to get CC to "understand" the node.js require() function, but that probably can't be done without editing the compiler itself, can it? 我想让CC“理解”node.js require()函数,但如果不编辑编译器本身就可能无法完成,可以吗?

I have been using the Closure Compiler with Node for a project I haven't released yet. 我一直在使用Closure Compiler with Node来完成我尚未发布的项目。 It has taken a bit of tooling, but it has helped catch many errors and has a pretty short edit-restart-test cycle. 它采用了一些工具,但它有助于捕获许多错误并且具有非常短的编辑 - 重启 - 测试周期。

First, I use plovr (which is a project that I created and maintain) in order to use the Closure Compiler, Library, and Templates together. 首先,我使用plovr (这是我创建和维护的项目),以便一起使用Closure Compiler,Library和Templates。 I write my Node code in the style of the Closure Library, so each file defines its own class or collection of utilities (like goog.array ). 我以Closure Library的样式编写Node代码,因此每个文件都定义了自己的类或实用程序集合(如goog.array )。

The next step is to create a bunch of externs files for the Node functions you want to use. 下一步是为要使用的Node函数创建一组externs文件。 I published some of these publicly at: 我公开发表了一些这些内容:

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

Though ultimately, I think that this should be a more community driven thing because there are a lot of functions to document. 虽然最终,我认为这应该是一个更加社区驱动的东西,因为有很多功能需要记录。 (It's also annoying because some Node functions have optional middle arguments rather than last arguments, making the type annotations complicated.) I haven't started this movement myself because it's possible that we could do some work with the Closure Complier to make this less awkward (see below). (这也很烦人,因为一些Node函数有可选的中间参数而不是最后一个参数,使得类型注释变得复杂。)我自己没有开始这个动作,因为我们可以用Closure Complier做一些工作来减少它的尴尬(见下文)。

Say you have created the externs file for the Node namespace http . 假设您已为Node命名空间http创建了externs文件。 In my system, I have decided that anytime I need http , I will include it via: 在我的系统中,我已经决定,只要我需要http ,我将通过以下方式包含它:

var http = require('http');

Though I do not include that require() call in my code. 虽然我没有在我的代码中包含require()调用。 Instead, I use the output-wrapper feature of the Closure Compiler the prepend all of the require() s at the start of the file, which when declared in plovr, in my current project looks like this: 相反,我使用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%"
],

In this way, my library code never calls Node's require() , but the Compiler tolerates the uses of things like http in my code because the Compiler recognizes them as externs. 这样,我的库代码从不调用Node的require() ,但编译器容忍在我的代码中使用http这样的东西,因为编译器将它们识别为externs。 As they are not true externs, they have to be prepended as I described. 由于它们不是真正的外部因素,因此必须按照我的描述进行。

Ultimately, after talking about this on the discussion list , I think the better solution is to have a new type annotation for namespaces that would look something like: 最后,在讨论列表中讨论了这个之后,我认为更好的解决方案是为命名空间创建一个类型的新类型注释:

goog.scope(function() {

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

    // Use http throughout.

});

In this scenario, an externs file would define the NodeHttpNamespace such that the Closure Compiler would be able to typecheck properties on it using the externs file. 在这种情况下,externs文件将定义NodeHttpNamespace ,以便Closure Compiler能够使用externs文件对其进行类型检查。 The difference here is that you could name the return value of require() whatever you wanted because the type of http would be this special namespace type. 这里的不同之处在于,无论您想要什么,都可以将require()的返回值命名为,因为http的类型将是这种特殊的命名空间类型。 (Identifying a "jQuery namespace" for $ is a similar issue.) This approach would eliminate the need to name your local variables for Node namespaces consistently, and would eliminate the need for that giant output-wrapper in the plovr config. (为$确定一个“jQuery名称空间”是一个类似的问题。)这种方法将不再需要为Node命名空间命名本地变量,并且不需要在plovr配置中使用那个巨大的output-wrapper

But that was a digression...once I have things set up as described above, I have a shell script that: 但这是一个题外话......一旦我按照上面所述设置了东西,我就有了一个shell脚本:

  1. Uses plovr to build everything in RAW mode. 使用plovr在RAW模式下构建所有内容。
  2. Runs node on the file generated by plovr. 在plovr生成的文件上运行node

Using RAW mode results in a large concatenation of all the files (though it also takes care of translating Soy templates and even CoffeeScript to JavaScript). 使用RAW模式会导致所有文件的大量连接(尽管它还负责将Soy模板甚至CoffeeScript转换为JavaScript)。 Admittedly, this makes debugging a pain because the line numbers are nonsense, but has been working well enough for me so far. 不可否认,这使得调试变得很痛苦,因为行号是无稽之谈,但到目前为止我一直运作良好。 All of the checks performed by the Closure Compiler have made it worth it. Closure Compiler执行的所有检查都使它值得。

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

I replaced my old approach with a way simpler approach: 我用一种更简单的方法取代了我的旧方法:

New approach 新的方法

  • No require() calls for my own app code, only for Node modules 没有require()调用我自己的应用程序代码,仅用于Node模块
  • I need to concatenate server code to a single file before I can run or compile it 在运行或编译之前,我需要将服务器代码连接到单个文件
  • Concatenating and compiling is done using a simple grunt script 使用简单的grunt脚本完成连接和编译

Funny thing is that I didn't even had to add an extern for the require() calls. 有趣的是,我甚至不必为require()调用添加extern。 The Google Closure compiler understands that automagically. Google Closure编译器自动理解这一点。 I did have to add externs for nodejs modules that I use. 我确实必须为我使用的nodejs模块添加externs。

Old approach 老方法

As requested by OP, I will elaborated on my way of compiling node.js code with Google Closure Compiler. 根据OP的要求,我将详细阐述使用Google Closure Compiler编译node.js代码的方法。

I was inspired by the way bolinfest solved the problem and my solution uses the same principle. 我受到了bolinfest解决问题的方式的启发,我的解决方案使用了同样的原则。 The difference is that I made one node.js script that does everything, including inlining modules (bolinfest's solution lets GCC take care of that). 不同之处在于我创建了一个执行所有操作的node.js脚本,包括内联模块(bolinfest的解决方案让GCC负责处理)。 This makes it more automated, but also more fragile. 这使它更加自动化,但也更脆弱。

I just added code comments to every step I take to compile server code. 我刚刚为编译服务器代码的每一步添加了代码注释。 See this commit: https://github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a3 看到这个提交: https//github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a3

To summarize: 总结一下:

  1. I start with my main module, the JS file that I pass to Node when I want to run it. 我从我的主模块开始,当我想运行它时,我传递给Node的JS文件。
    In my case, this file is start.js . 在我的例子中,这个文件是 start.js
  2. In this file, using a regular expression, I detect all require() calls, including the assignment part. 在此文件中,使用正则表达式,我检测所有 require()调用,包括赋值部分。
    In start.js, this matches one require call: var Server = require('./lib/server.js'); 在start.js中,这匹配一个require调用: var Server = require('./lib/server.js');
  3. I retrieve the path where the file exists based on the file name, fetch its contents as a string, and remove module.exports assignments within the contents. 我根据文件名检索文件所在的路径,将其内容作为字符串获取,并删除内容中的module.exports赋值。
  4. Then I replace the require call from step 2 with the contents from step 3. Unless it is a core node.js module, then I add it to a list of core modules that I save for later. 然后我将步骤2中的require调用替换为步骤3中的内容。除非它是核心node.js模块,然后我将其添加到我稍后保存的核心模块列表中。
  5. Step 3 will probably contain more require() call, so I repeat step 3 and 4 recursively until all require() calls are gone and I'm left with one huge string containing all code. 第3步可能包含更多的 require()调用,所以我递归地重复步骤3和4,直到所有的 require()调用都消失了,我留下了一个包含所有代码的巨大字符串。
  6. If all recursion has completed, I compile the code using the REST API. 如果所有递归都已完成,我使用REST API编译代码。
    You could also use the offline compiler. 您也可以使用脱机编译器。
    I have externs for every core node.js module. 我有每个核心node.js模块的externs。 This tool is useful for generating externs . 此工具可用于生成外部
  7. I preprend the removed core.js module require calls to the compiled code. 我预先发布了已删除的core.js模块, require调用已编译的代码。

Pre-Compiled code. 预编译代码。
All require calls are removed. 所有 require呼叫都被删除。 All my code is flattened. 我的所有代码都被夷为平地。
http://pastebin.com/eC2rVMiN http://pastebin.com/eC2rVMiN

Post-Compiled code. 编译后的代码。
Node.js core require calls have been prepended manually. Node.js核心 require手动预先调用。
http://pastebin.com/uB8CaejN http://pastebin.com/uB8CaejN


Why you should not do it this way: 为什么你不应该这样做:

  1. It uses regular expressions (not a parser or tokenizer) for detecting require calls, inlining and removing module.exports . 它使用正则表达式(不是解析器或标记器)来检测 require调用,内联和删除 module.exports This is fragile, as it does not cover all syntax variations. 这很脆弱,因为它不包括所有语法变体。
  2. When inlining, all module code is added to the global namespace. 内联时,所有模块代码都会添加到全局命名空间中。 This is against the principles of Node.js, where every file has its own namespace, and this will cause errors if you have two different modules with the same global variables. 这违反了Node.js的原则,其中每个文件都有自己的命名空间,如果您有两个具有相同全局变量的不同模块,这将导致错误。
  3. It does not improve the speed of your code that much, since V8 also performs a lot of code optimizations like inlining and dead code removal. 它不会提高代码的速度,因为V8还执行了大量的代码优化,如内联和死代码删除。

Why you should: 为什么你应该:

  1. Because it does work when you have consistent code. 因为它在您具有一致的代码时确实有效。
  2. It will detect errors in your server code when you enable verbose warnings. 启用详细警告时,它将检测服务器代码中的错误。

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

It's supported, check https://code.google.com/p/closure-library/wiki/NodeJS . 它受支持,请访问https://code.google.com/p/closure-library/wiki/NodeJS

Option 4: Don't use closure compiler. 选项4:不要使用闭包编译器。

People in the node community don't tend to use it. 节点社区中的人不倾向于使用它。 You don't need to minify node.js source code, that's silly. 你不需要缩小node.js源代码,这很愚蠢。

There's simply no good use for minification. 对缩小没有好处。

As for the performance benefits of closure, I personally doubt it actually makes your programs faster. 至于关闭的性能优势,我个人怀疑它实际上使您的程序更快。

And of course there's a cost, debugging compiled JavaScript is a nightmare 当然还有成本,调试编译的JavaScript是一场噩梦

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM