javascript 让闭包编译器和 Node.js 发挥良好的作用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8287597/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Getting closure-compiler and Node.js to play nice
提问by bukzor
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 的官方建议是一起编译应用程序的所有代码,但是当我编译一些包含 的简单 node.js 代码时require("./MyLib.js")
,该行会直接放入输出中,但在该上下文中没有任何意义。
I see a few options:
我看到几个选项:
- Code the entire application as a single file. This solves the problem by avoiding it, but is bad for maintenance.
- 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.
- 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() 函数,但是如果不编辑编译器本身,这可能无法完成,是吗?
回答by bolinfest
I have been using the Closure Compiler with Node for a project I haven't released yet. It has taken a bit of tooling, but it has helped catch many errors and has a pretty short edit-restart-test cycle.
我一直在将 Closure Compiler 与 Node 一起用于我尚未发布的项目。它使用了一些工具,但它帮助捕获了许多错误,并且编辑-重新启动-测试周期非常短。
First, I use plovr(which is a project that I created and maintain) in order to use the Closure Compiler, Library, and Templates together. 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
).
首先,我使用plovr(这是我创建和维护的项目)以便一起使用 Closure Compiler、Library 和 Templates。我以 Closure 库的风格编写我的 Node 代码,因此每个文件都定义了自己的类或实用程序集合(如goog.array
)。
The next step is to create a bunch of externs files for the Node functions you want to use. I published some of these publicly at:
下一步是为要使用的 Node 函数创建一堆 externs 文件。我公开发表了其中一些:
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
. In my system, I have decided that anytime I need http
, I will include it via:
假设您已经为 Node 命名空间创建了 externs 文件http
。在我的系统中,我决定无论何时需要http
,我都会通过以下方式包含它:
var http = require('http');
Though I do not include that require()
call in my code. 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:
虽然我没有require()
在我的代码中包含那个调用。相反,我使用output-wrapper
Closure Compiler的特性,在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%"
],
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. As they are not true externs, they have to be prepended as I described.
这样,我的库代码从不调用 Node 的require()
,但编译器容忍使用http
我的代码中之类的东西,因为编译器将它们识别为外部。由于它们不是真正的外部人员,因此必须按照我的描述预先添加它们。
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. 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. (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.
在这种情况下,一个 externs 文件将定义NodeHttpNamespace
这样的 Closure Compiler 能够使用 externs 文件对其进行类型检查。这里的区别在于您可以命名require()
任何您想要的返回值,因为类型http
将是这种特殊的命名空间类型。(识别“jQuery 命名空间”$
是一个类似的问题。)这种方法将消除为节点命名空间一致命名局部变量的需要,并且将消除output-wrapper
plovr 配置中对那个巨人的需要。
But that was a digression...once I have things set up as described above, I have a shell script that:
但那是题外话……一旦我按照上面描述的方式进行了设置,我就有了一个 shell 脚本:
- Uses plovr to build everything in
RAW
mode. - Runs
node
on the file generated by plovr.
- 使用 plovr 在
RAW
mode 中构建所有内容。 node
在 plovr 生成的文件上运行。
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). 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.
使用RAW
mode 会导致所有文件的大量连接(尽管它还负责将 Soy 模板甚至 CoffeeScript 翻译成 JavaScript)。诚然,这让调试变得痛苦,因为行号是无意义的,但到目前为止对我来说已经足够好了。Closure Compiler 执行的所有检查都值得。
回答by Jauco
The svn HEAD of closure compiler seems to have support for AMD
闭包编译器的 svn HEAD 似乎支持 AMD
回答by Blaise
I replaced my old approach with a way simpler approach:
我用一种更简单的方法替换了我的旧方法:
New approach
新的方法
- No require() calls for my own app code, only for Node modules
- 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
- 我自己的应用程序代码没有 require() 调用,仅适用于 Node 模块
- 在运行或编译它之前,我需要将服务器代码连接到一个文件
- 连接和编译是使用一个简单的 grunt 脚本完成的
Funny thing is that I didn't even had to add an extern for the require()
calls. The Google Closure compiler understands that automagically. I did have to add externs for nodejs modules that I use.
有趣的是,我什至不必为require()
呼叫添加外部。Google Closure 编译器会自动理解这一点。我确实必须为我使用的 nodejs 模块添加 externs。
Old approach
旧方法
As requested by OP, I will elaborated on my way of compiling node.js code with Google Closure Compiler.
I was inspired by the way bolinfest solved the problem and my solution uses the same principle. 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). 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
To summarize:
- I start with my main module, the JS file that I pass to Node when I want to run it.
In my case, this file is start.js. - In this file, using a regular expression, I detect all
require()
calls, including the assignment part.
In start.js, this matches one require call:var Server = require('./lib/server.js');
- 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.
- 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.
- Step 3 will probably contain more
require()
call, so I repeat step 3 and 4 recursively until allrequire()
calls are gone and I'm left with one huge string containing all code. - If all recursion has completed, I compile the code using the REST API.
You could also use the offline compiler.
I have externs for every core node.js module. This tool is useful for generating externs. - I preprend the removed core.js module
require
calls to the compiled code.
Pre-Compiled code.
All require
calls are removed. All my code is flattened.
http://pastebin.com/eC2rVMiN
Post-Compiled code.
Node.js core require
calls have been prepended manually.
http://pastebin.com/uB8CaejN
Why you should not do it this way:
- It uses regular expressions (not a parser or tokenizer) for detecting
require
calls, inlining and removingmodule.exports
. This is fragile, as it does not cover all syntax variations. - 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.
- 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.
Why you should:
- Because it does work when you have consistent code.
- It will detect errors in your server code when you enable verbose warnings.
根据OP的要求,我将详细说明我使用Google Closure Compiler编译node.js代码的方式。
我的灵感来自 bolinfest 解决问题的方式,我的解决方案使用相同的原理。不同之处在于我制作了一个 node.js 脚本,它可以做所有事情,包括内联模块(bolinfest 的解决方案让 GCC 负责)。这使其更加自动化,但也更加脆弱。
我只是在编译服务器代码的每个步骤中添加了代码注释。看到这个提交:https: //github.com/blaise-io/xssnake/commit/da52219567b3941f13b8d94e36f743b0cbef44a3
总结一下:
- 我从我的主模块开始,即我想运行它时传递给 Node 的 JS 文件。
在我的例子中,这个文件是start.js。 - 在这个文件中,我使用正则表达式检测所有
require()
调用,包括赋值部分。
在 start.js 中,这匹配一个 require 调用:var Server = require('./lib/server.js');
- 我根据文件名检索文件所在的路径,将其内容作为字符串获取,并删除内容中的 module.exports 分配。
- 然后我将第 2 步中的 require 调用替换为第 3 步中的内容。除非它是核心 node.js 模块,否则我将其添加到我保存以备后用的核心模块列表中。
- 第 3 步可能包含更多
require()
调用,因此我递归地重复第 3 步和第 4 步,直到所有require()
调用都消失为止,只剩下一个包含所有代码的巨大字符串。 - 如果所有递归都已完成,我将使用 REST API 编译代码。
您也可以使用离线编译器。
我为每个核心 node.js 模块都有 extern。这个工具对于生成 externs 很有用。 - 我将已删除的 core.js 模块
require
调用预先添加到编译后的代码中。
预编译代码。删除
所有require
呼叫。我所有的代码都被扁平化了。
http://pastebin.com/eC2rVMiN
编译后的代码。
Node.js 核心require
调用已手动添加。
http://pastebin.com/uB8CaejN
为什么你不应该这样做:
- 它使用正则表达式(不是解析器或标记器)来检测
require
调用、内联和删除module.exports
. 这是脆弱的,因为它没有涵盖所有的语法变化。 - 内联时,所有模块代码都添加到全局命名空间中。这违反了 Node.js 的原则,其中每个文件都有自己的命名空间,如果您有两个具有相同全局变量的不同模块,这将导致错误。
- 它并没有太多地提高你的代码速度,因为 V8 还执行了很多代码优化,比如内联和死代码删除。
为什么你应该:
- 因为当您拥有一致的代码时它确实有效。
- 当您启用详细警告时,它将检测您的服务器代码中的错误。
回答by Daniel Steigerwald
Closure Library on Node.js in 60 seconds.
在 60 秒内关闭 Node.js 上的库。
It's supported, check https://code.google.com/p/closure-library/wiki/NodeJS.
它受支持,请查看https://code.google.com/p/closure-library/wiki/NodeJS。
回答by Raynos
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 是一场噩梦