阻止 Google Closure Compiler 重命名设置对象
我试图让 Google Closure 编译器在作为设置或数据传递给函数时不重命名对象。通过查看 jQuery 中存在的注释,我认为这会起作用:
/** @param {Object.<string,*>} data */
window.hello = function(data) {
alert(data.hello);
};
hello({ hello: "World" });
但是,它最终是这样的:
window.a = function(b) {
alert(b.a)
};
hello({a:"World"});
The ajax
function find 这里 有这个注释,它似乎可以工作。那么,为什么不会呢?如果数据是来自外部源或设置对象的返回值,我希望能够告诉编译器不要触及它,使用 this["escape"]
技巧是侵入性的我认为是这样的。
这是一个更好的示例
function ajax(success) {
// do AJAX call
$.ajax({ success: success });
}
ajax(function(data) {
alert(data.Success);
});
输出:
$.b({c:function(a){alert(a.a)}});
success
已重命名为 c
,Success
(大写 S)已重命名为 a< /代码>。
我现在使用 jQuery 1.6 externs 文件< /a> 并获得以下输出:
$.ajax({success:function(a){alert(a.a)}});
它还会生成一条警告,指出属性 Success
未定义,正如我所期望的那样,但它无法将 Success
重命名为简单的a
,那仍然会破坏我的代码。我查看了 ajax
的注释,发现了这个类型表达式 {Object.
,我相应地注释了我的代码,然后重新编译。仍然没有工作...
I'm trying to get the Google Closure Compiler to not rename objects when passed as settings or data to a function. By looking at the annotations present in jQuery, I thought this would work:
/** @param {Object.<string,*>} data */
window.hello = function(data) {
alert(data.hello);
};
hello({ hello: "World" });
However, it ends up like this:
window.a = function(b) {
alert(b.a)
};
hello({a:"World"});
The ajax
function found here has this annotation and it appears to work. So, why won't this? If data is the return value from an external source or a settings object I'd like to be able to tell the compiler to not touch it, using the this["escape"]
trick is to intrusive for something like this in my opinion.
Here's a better example
function ajax(success) {
// do AJAX call
$.ajax({ success: success });
}
ajax(function(data) {
alert(data.Success);
});
Output:
$.b({c:function(a){alert(a.a)}});
success
has been renamed to c
and Success
(with a capital S) has been renamed to a
.
I now compile the same code with the jQuery 1.6 externs file and get the following output:
$.ajax({success:function(a){alert(a.a)}});
It also produces a warning that the property Success
is not defined, as I would expect, but it cannot rename Success
to simply a
, that will still break my code. I look at the annotation present for the ajax
and I find this type expression {Object.<string,*>=}
, I annotate my code accordingly, and recompile. Still not working...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
由于您的重点似乎是源而不是输出,因此您关注的似乎是 DRY(不要重复自己)。这是另一种 DRY 解决方案。
您可以使用
--create_name_map_files
运行闭包编译器。这样做会发出一个名为_props_map.out
的文件。您可以让发送 JSON 的服务器端调用(ASP.Net MVC 或其他任何形式)在发送 JSON 时使用这些映射,这样它们实际上会发送缩小的 JSON,利用闭包编译器执行的重命名。通过这种方式,您可以更改控制器和脚本上的变量或属性的名称,添加更多内容等,并且缩小从脚本一直返回到控制器输出。您的所有源代码(包括控制器)仍然是非缩小且易于阅读的。Since your focus seems to be on the source rather than the output, it seems like what you're focused on is DRY (Don't Repeat Yourself). Here's an alternative DRY solution.
You can run the Closure Compiler with
--create_name_map_files
. Doing so emits a file named_props_map.out
. You can have your JSON-emitting server-side calls (ASP.Net MVC or whatever it might be) use these maps when emitting their JSON, so they're actually emitting minified JSON that leverages the renames the Closure Compiler performed. This way you can change the name of a variable or property on your Controller and your scripts, add more, etc, and the minification carries through from the scripts all the way back to the Controller output. All of your source, including the Controller, continues to be non-minified and easy to read.我认为您真正想要做的是阻止它重命名从服务器上的 AJAX 控制器返回的对象上的属性名称,这显然会中断调用。
所以当你打电话时
你希望它只留下消息,对吗?
如果是这样,那就按照您之前提到的方式完成,但它会很好地编译为输出中的 .Message 。上面的内容变为:
现在缩小为:
通过使用
r['Message']
而不是r.Message
,可以防止缩小器重命名属性。这称为导出方法,正如您在闭包编译器文档中注意到的那样,该方法优于外部方法。也就是说,如果你使用 externs 方法来执行此操作,你会让 Google 的一些人生气。他们甚至在标题上添加了一个名为“no”的 ID:http://code.google.com/closure/compiler/docs /api-tutorial3.html#no
也就是说,你也可以使用 externs 方法来做到这一点,这就是它的奇怪之处:
externs.js
test.js
C:\java\closure>java -jar编译器.jar --externs externs.js --js jquery-1.6.js --js test.js --compilation_level ADVANCED_OPTIMIZATIONS --js_output_file output.js
结果如下:
I think what you're really trying to do is stop it from renaming property names on the object coming back from an AJAX controller on the server, which obviously would break the call.
So when you call
You want it to leave Message alone, correct?
If so that's done by the way you mentioned earlier, but it's compiled nicely to .Message in the output. The above becomes:
Minifies now to:
By using
r['Message']
instead ofr.Message
, you prevent the property rename by the minifier. That's called the export method, which as you'll notice in the Closure Compiler documentation is preferred over externs. That is, if you use the externs method to do this instead, you're going to make several people at Google angry. They even put an ID on the heading named, "no":http://code.google.com/closure/compiler/docs/api-tutorial3.html#no
That said, you can also do this using the externs method, and here it is in all its weirdness:
externs.js
test.js
C:\java\closure>java -jar compiler.jar --externs externs.js --js jquery-1.6.js --js test.js --compilation_level ADVANCED_OPTIMIZATIONS --js_output_file output.js
And out comes:
不幸的是,在各处执行
data["hello"]
是推荐的(也是官方的)防止变量重命名的闭包方法。我完全同意你的观点,我一点也不喜欢这个。然而,所有其他解决方案都会给您带来次优的编译结果,或者可能会在模糊的情况下中断——如果您愿意接受次优的结果,那么为什么首先要使用闭包编译器呢?
然而,从服务器返回的数据实际上是您需要处理的全部内容,因为您应该能够安全地允许 Closure 重命名程序中的其他所有内容。随着时间的推移,我发现最好编写包装器来克隆从服务器返回的数据。换句话说:
这样,任何未损坏的对象在从 Ajax 反序列化后只会短暂存在。然后它被克隆到一个可以通过 Closure 编译/优化的对象中。使用此克隆程序中的所有内容,您将获得 Closure 优化的全部好处。
我还发现,拥有这样一个“处理”函数来立即处理通过 Ajax 从服务器传入的所有内容非常有用——除了克隆对象之外,您还可以在其中放置后处理代码以及验证、纠错和安全检查等。在许多网络应用程序中,您已经拥有这样的功能来首先对返回的数据进行此类检查 - 您永远信任从服务器返回的数据,现在您也这样做吗? ?
Unfortunately, doing
data["hello"]
all over the place is the recommended (and official) Closure way of preventing variable renaming.I agree totally with you that I do not like this a single bit. However, all other solutions will give you sub-optimal results with the compilation or may break in obscure situations -- and if you're willing to live with sub-optimal results, then why use the Closure Compiler in the first place?
However, data returned from a server is really all you need to handle, because you should be able to safely allow Closure to rename everything else in your program. Over the time, I've found that it is best to write wrappers that will clone data coming back from a server. In other words:
This way, any unmangled object only lives briefly right after being deserialized from Ajax. Then it gets cloned into an object which can be compiled/optimized by Closure. Use this clone everything in your program, and you get the full benefits of Closure's optimizations.
I've also found that it is useful to have such a "processing" function immediately processing everything that comes via Ajax from a server -- in addition to cloning the object, you can put post-processing code in there, as well as validations, error corrections and security checks etc. In many web apps, you already have such functions to do such checking on returned data in the first place -- you NEVER trust data returned from a server, now do you?
有点晚了,但我只是通过编写一对处理所有入站和出站 ajax 对象的网关函数来解决这个问题:
这里方便的一点是,closureToRemote 字典是平坦的 - 也就是说,即使你有要指定子属性的名称以便闭包编译器知道,您可以在同一级别上指定它们。这意味着响应格式实际上可以是一个相当复杂的层次结构,它只是您将通过名称直接访问的基本键,需要在某处进行硬编码。
每当我要进行 ajax 调用时,我都会通过 declosureizeData() 传递要发送的数据,这意味着我正在将数据从闭包的命名空间中取出。当我收到数据时,我做的第一件事是通过closureizeData()运行它,将名称放入闭包的命名空间中。
请注意,映射字典只需位于我们代码中的一个位置,如果您有结构良好的 ajax 代码,并且始终进出同一代码路径,那么集成它就是“一次性完成” “忘记它”类型的活动。
A little late to the game, but I got around this just by writing a pair of gateway functions that process all of my inbound and outbound ajax objects:
The handy thing here is that the closureToRemote dict is flat - that is, even though you have to specify the names of child attributes so that the closure compiler knows, you can specify them all on the same level. This means that the response format can actually be a fairly intricate hierarchy, it's just the base keys that you will be accessing directly by name that need to be hard coded somewhere.
Whenever I am about to make an ajax call, I pass the data I'm sending through declosureizeData(), implying that I am taking the data out of closure's namespacing. When I receive data, the first thing I do is run it through closureizeData() to get the names into closure's namespace.
Note please that the mapping dictionary only needs to be one place in our code, and if you have well-structured ajax code that always comes into and out of the same code path, then integrating it is a "do-it-once-and-forget-about-it" type of activity.
您可以尝试将其定义为记录类型,
这告诉它数据具有字符串类型的属性 hello。
You could try defining it as a record type,
That tells it data has property hello of type string.
显然,这里不应该责怪注释,只需向设置对象引入一些未使用的属性就会导致编译器重命名内容。
我想知道这些来自哪里以及迄今为止我唯一合乎逻辑的解释(已确认这里),是编译器保留一个它不会重命名的事物的全局名称表。只要有一个具有名称的 extern,就会导致该名称的任何属性都被保留。
结果如下:
注意
transform
没有被重命名,但elementThing
却被重命名,即使我尝试注释此类型,我也无法让它重命名相应地进行变换。
但是如果我添加以下 extern source
function a() {}; a.prototype.elementThing = function() {};
它不会重命名elementThing
尽管查看代码,我可以清楚地看出构造函数返回的类型与externa
,但不知何故,编译器就是这样做的。我想这只是闭包编译器的限制,我认为这真是太遗憾了。Apparently annotations are not to blame here, simply by introducing some unused properties to the settings object will result in the compiler renaming stuff.
I'd like to know where these came from and the only logical explanation I have so far (confirmed here), is that the compiler keeps a global name table of things it won't rename. Simply having a extern with a name will result in any property of that name to be keept around.
Results in the following output:
Notice how
transform
isn't renamed butelementThing
is, even if I try to annotate this type I can't get it to renametransform
accordingly.But if I add the following extern source
function a() {}; a.prototype.elementThing = function() {};
it won't renameelementThing
despite looking at the code, I can clearly tell that the type returned by the constructor is unrelated to the externa
, yet somehow, this is how the compiler does it. I guess this is just a limitation of the closure compiler, which I think is a darn shame.