简体   繁体   English

防止Google Closure Compiler重命名设置对象

[英]Prevent Google Closure Compiler from renaming settings objects

I'm trying to get the Google Closure Compiler to not rename objects when passed as settings or data to a function. 我试图让Google Closure Compiler在作为设置或数据传递给函数时不重命名对象。 By looking at the annotations present in jQuery, I thought this would work: 通过查看jQuery中的注释,我认为这样可行:

/** @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. 这里找到的ajax函数有这个注释,它似乎工作。 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. 如果数据是来自外部源或设置对象的返回值,我希望能够告诉编译器不要触摸它,使用this["escape"]技巧就是在我看来打扰这样的东西。

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 . success已重命名为cSuccess (使用大写字母S)已重命名为a

I now compile the same code with the jQuery 1.6 externs file and get the following output: 我现在使用jQuery 1.6 externs文件编译相同的代码并获得以下输出:

$.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. 它还会产生一个警告,表明属性Success未定义,正如我所料,但它不能将Success重命名为a ,这仍然会破坏我的代码。 I look at the annotation present for the ajax and I find this type expression {Object.<string,*>=} , I annotate my code accordingly, and recompile. 我查看了ajax的注释,我找到了这个类型表达式{Object.<string,*>=} ,我相应地注释了我的代码,然后重新编译。 Still not working... 还是行不通...

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). 既然你的注意力集中在源头而不是输出上,那么你所关注的似乎就是DRY(不要重复自己)。 Here's an alternative DRY solution. 这是另一种DRY解决方案。

You can run the Closure Compiler with --create_name_map_files . 您可以使用--create_name_map_files运行Closure Compiler。 Doing so emits a file named _props_map.out . 这样做会发出一个名为_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. 你可以让你的JSON发射服务器端调用(ASP.Net MVC或它可能是什么)在发出它们的JSON时使用这些映射,因此它们实际上发出缩小的JSON,利用重命名Closure Compiler执行。 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. 这样,您可以在Controller和脚本上更改变量或属性的名称,添加更多等,并且缩小从脚本一直传回Controller控制器输出。 All of your source, including the Controller, continues to be non-minified and easy to read. 您的所有源代码(包括Controller)仍然是非缩小且易于阅读的。

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. 我认为你真正要做的就是阻止它重命名从服务器上的AJAX控制器返回的对象上的属性名称,这显然会打破这个调用。

So when you call 所以当你打电话

$.ajax({
    data: { joe: 'hello' },
    success: function(r) {
        alert(r.Message);
    }
});

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. 如果是这样,你通过前面提到的方式完成,但它在输出中很好地编译为.Message。 The above becomes: 以上变为:

var data = {};
data['joe'] = 'hello';

$.ajax({
    data: data,
    /**
    @param Object.<string> r
    */
    success: function (r) {
        alert(r['Message']);
    }
});

Minifies now to: 现在缩小为:

$.ajax({data:{joe:"hello"},success:function(a){alert(a.Message)}});

By using r['Message'] instead of r.Message , you prevent the property rename by the minifier. 通过使用r['Message']而不是r.Message ,可以防止r.Message对属性进行重命名。 That's called the export method, which as you'll notice in the Closure Compiler documentation is preferred over externs. 这就是所谓的导出方法,正如您在Closure Compiler文档中所注意到的那样,它比externs更受欢迎。 That is, if you use the externs method to do this instead, you're going to make several people at Google angry. 也就是说,如果你使用externs方法来做这件事,那么你就会让Google的几个人生气。 They even put an ID on the heading named, "no": http://code.google.com/closure/compiler/docs/api-tutorial3.html#no 他们甚至在名为“no”的标题上添加了ID: 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方法做到这一点,这里有一点奇怪:

externs.js externs.js

/** @constructor */
function Server() { };

/** @type {string} */
Server.prototype.Message;

test.js test.js

$.ajax({
    data: { joe: 'hello' },
    /**
    @param {Server} r
    */
    success: function (r) {
        alert(r.Message);
    }
});

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 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: 出来了:

$.ajax({data:{a:"hello"},success:function(a){alert(a.Message)}});

Unfortunately, doing data["hello"] all over the place is the recommended (and official) Closure way of preventing variable renaming. 不幸的是,在整个地方做data["hello"]是阻止变量重命名的推荐(和官方)Closure方法。

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? 但是,所有其他解决方案都会在编译时为您提供次优结果,或者可能会在不明显的情况下中断 - 如果您愿意接受次优结果,那么为什么要首先使用Closure Compiler?

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. 但是,从服务器返回的数据实际上只需要处理,因为您应该能够安全地允许Closure重命名程序中的所有其他内容。 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: 换一种说法:

var data1 = { hello:data["hello"] };
// Then use data1.hello anywhere else in your program

This way, any unmangled object only lives briefly right after being deserialized from Ajax. 这样,任何未编译的对象只能在从Ajax反序列化后才能短暂存在。 Then it gets cloned into an object which can be compiled/optimized by Closure. 然后它被克隆到一个可以由Closure编译/优化的对象中。 Use this clone everything in your program, and you get the full benefits of Closure's optimizations. 在程序中使用此克隆的所有内容,您将获得Closure优化的全部好处。

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传输的所有东西是有用的 - 除了克隆对象,你可以在其中放置后处理代码,以及验证在许多Web应用程序中,您已经拥有了这样的功能,可以首先检查返回的数据 - 您永远不会信任从服务器返回的数据,现在对吗?

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: 游戏稍晚,但我只是通过编写一对处理所有入站和出站ajax对象的网关函数来解决这个问题:

//This is a dict containing all of the attributes that we might see in remote
//responses that we use by name in code.  Due to the way closure works, this
//is how it has to be.
var closureToRemote = {
  status: 'status', payload: 'payload', bit1: 'bit1', ...
};
var closureToLocal = {};
for (var i in closureToRemote) {
  closureToLocal[closureToRemote[i]] = i;
}
function _closureTranslate(mapping, data) {
  //Creates a new version of data, which is recursively mapped to work with
  //closure.
  //mapping is one of closureToRemote or closureToLocal
  var ndata;
  if (data === null || data === undefined) {
    //Special handling for null since it is technically an object, and we
    //throw in undefined since they're related
    ndata = data;
  }
  else if ($.isArray(data)) {
    ndata = []
    for (var i = 0, m = data.length; i < m; i++) {
      ndata.push(_closureTranslate(mapping, data[i]));
    }
  }
  else if (typeof data === 'object') {
    ndata = {};
    for (var i in data) {
      ndata[mapping[i] || i] = _closureTranslate(mapping, data[i]);
    }
  }
  else {
    ndata = data;
  }
  return ndata;
}

function closureizeData(data) {
  return _closureTranslate(closureToLocal, data);
}
function declosureizeData(data) {
  return _closureTranslate(closureToRemote, data);
}

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. 这里的方便之处在于closureToRemote dict是平的 - 即使你必须指定子属性的名称以便闭包编译器知道,你可以在同一级别指定它们。 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. 每当我打算进行ajax调用时,我都会通过declosureizeData()传递我发送的数据,这意味着我正在从闭包的命名空间中获取数据。 When I receive data, the first thing I do is run it through closureizeData() to get the names into closure's namespace. 当我收到数据时,我要做的第一件事就是通过closureizeData()来运行它,以便将名称输入到闭包的命名空间中。

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. 请注意,映射字典只需要在我们的代码中占一个位置,如果你有一个结构良好的ajax代码,它总是进出相同的代码路径,那么集成它就是“do-it-once-and - 忘记它“活动的类型。

You could try defining it as a record type, 您可以尝试将其定义为记录类型,

/**
  @param {{hello: string}} data
*/

That tells it data has property hello of type string. 这告诉它数据具有string类型的属性hello。

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. 只需拥有一个带名字的extern就会导致该名称的任何属性都被保留。

/** @type {Object.<string,*>} */
var t = window["t"] = {
  transform: function(m, e) {
    e.transform = m;
  },
  skew: function(m, e) {
    e.skew = m;
  }
}

/** 
 * @constructor
 */
function b() {
  this.transform = [];
  this.elementThing = document.createElement("DIV");
}

t.transform(new b().transform, new b().elementThing);

Results in the following output: 结果如下:

function c() {
    this.transform = [];
    this.a = document.createElement("DIV")
}(window.t = {
    transform: function (a, b) {
        b.transform = a
    },
    b: function (a, b) {
        b.b = a
    }
}).transform((new c).transform, (new c).a);

Notice how transform isn't renamed but elementThing is, even if I try to annotate this type I can't get it to rename transform accordingly. 注意如何重命名transform但是elementThing是,即使我尝试注释这个类型我也不能让它相应地重命名transform

But if I add the following extern source function a() {}; a.prototype.elementThing = function() {}; 但是如果我添加以下extern源function a() {}; a.prototype.elementThing = function() {}; function a() {}; a.prototype.elementThing = function() {}; it won't rename elementThing despite looking at the code, I can clearly tell that the type returned by the constructor is unrelated to the extern a , yet somehow, this is how the compiler does it. 尽管查看代码,它也不会重命名elementThing ,我可以清楚地告诉构造函数返回的类型与extern a无关,但不知怎的,编译器就是这样做的。 I guess this is just a limitation of the closure compiler, which I think is a darn shame. 我想这只是闭包编译器的一个限制,我认为这是一种耻辱。

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

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