Подтвердить что ты не робот

Запретить компилятору Google Closure переименовывать параметры

Я пытаюсь заставить компилятор 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"});

Функция ajax найдена здесь имеет эту аннотацию и, похоже, работает. Итак, почему это не так? Если данные являются возвращаемым значением из внешнего источника или объекта настроек, я хотел бы сказать компилятору, чтобы он не касался его, трюк 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 file и получаю следующий вывод:

$.ajax({success:function(a){alert(a.a)}});

Он также выдает предупреждение о том, что свойство success не определено, как я ожидал, но он не может переименовать success просто a, который все равно сломает мой код. Я смотрю представленную аннотацию для ajax, и я нахожу это выражение типа {Object.<string,*>=}, я комментирую свой код и перекомпилирую. Все еще не работает...

4b9b3361

Ответ 1

Поскольку ваш фокус, похоже, находится на источнике, а не на выходе, кажется, что вы сосредоточены на DRY (не повторяйте себя). Здесь альтернативное решение DRY.

Вы можете запустить компилятор Closure с помощью --create_name_map_files. При этом выдается файл с именем _props_map.out. Вы можете использовать свои JSON-исходящие вызовы на стороне сервера (ASP.Net MVC или что-то еще, что может быть) использовать эти карты при выпуске их JSON, поэтому они на самом деле излучают минимизированный JSON, который использует переименовывающий компилятор Closure. Таким образом вы можете изменить имя переменной или свойства на своем контроллере и ваших сценариях, добавить еще и т.д., А минимизация переносится из сценариев на обратный путь к выходу Controller. Весь ваш источник, включая контроллер, продолжает оставаться неминифицированным и легко читаемым.

Ответ 2

Я думаю, что вы действительно пытаетесь это сделать, это прекратить переименование имен свойств на объекте, возвращающемся с контроллера AJAX на сервере, что, очевидно, сломало бы вызов.

Поэтому, когда вы вызываете

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

Вы хотите, чтобы он оставил сообщение в покое, правильно?

Если это сделано так, как вы упомянули ранее, но оно прекрасно составлено для .Message на выходе. Вышеизложенное:

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

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

Минимизирует теперь:

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

Используя r['Message'] вместо r.Message, вы не сможете переименовать свойство с помощью minifier. Это называется методом экспорта, который, как вы заметите, в документации Closure Compiler предпочтительнее внешних. То есть, если вы используете метод externs, чтобы сделать это, вы собираетесь сделать несколько людей в Google сердитыми. Они даже указали идентификатор в названии "нет": http://code.google.com/closure/compiler/docs/api-tutorial3.html#no

Тем не менее, вы также можете сделать это, используя метод externs, и здесь он во всей своей странности:

externs.js

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

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

test.js

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

C:\java\clos > java -jar compiler.jar --externs externs.js --js jquery-1.6.js --js test.js --compilation_level ADVANCED_OPTIMIZATIONS --js_output_file output.js

И выходит:

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

Ответ 3

К сожалению, выполнение data["hello"] по всему месту является рекомендуемым (и официальным) способом Closure предотвращения переименования переменных.

Я полностью согласен с вами в том, что мне не нравится этот бит. Тем не менее, все другие решения дадут вам субоптимальные результаты с компиляцией или могут нарушаться в неясных ситуациях - и если вы готовы жить с субоптимальными результатами, то зачем использовать компилятор Closure в первую очередь?

Однако данные, возвращаемые с сервера, действительно все, что вам нужно для обработки, поскольку вы должны иметь возможность безопасно разрешить Closure переименовать все остальное в вашей программе. С течением времени я обнаружил, что лучше всего писать обертки, которые будут клонировать данные, возвращаемые с сервера. Другими словами:

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

Таким образом, любой unmangled объект живет только кратко сразу после десериализации из Ajax. Затем он клонируется в объект, который может быть скомпилирован/оптимизирован Closure. Используйте этот клон все в своей программе, и вы получаете все преимущества оптимизации Closure.

Я также обнаружил, что полезно иметь такую ​​ "обработку" функцию, которая немедленно обрабатывает все, что приходит через Ajax с сервера, - в дополнение к клонированию объекта, вы можете поместить там пост-обрабатывающий код, так как а также проверки, исправления ошибок и проверки безопасности и т.д. Во многих веб-приложениях у вас уже есть такие функции для выполнения такой проверки на возвращенных данных в первую очередь - вы НИКОГДА доверяете данным, возвращаемым с сервера, теперь вы?

Ответ 4

Немного поздно в игре, но я обошел это, написав пару функций шлюза, которые обрабатывают все мои входящие и исходящие объекты 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);
}

Полезная вещь здесь заключается в том, что dictToRemote dict является плоским - то есть, хотя вы должны указать имена дочерних атрибутов, чтобы компилятор замыкания знал, вы можете указать их все на одном уровне. Это означает, что формат ответа может быть довольно сложной иерархией, это просто базовые ключи, к которым вы будете обращаться напрямую по имени, которые где-то должны быть жестко закодированы.

Всякий раз, когда я собираюсь сделать ajax-вызов, я передаю данные, которые я отправляю через declosureizeData(), подразумевая, что я беру данные из закрытия имен. Когда я получаю данные, первое, что я делаю, это запустить его через closizeData(), чтобы получить имена в пространство имен закрытия.

Обратите внимание, что словарь сопоставления должен быть только одним местом в нашем коде, и если у вас есть хорошо структурированный код ajax, который всегда входит и выходит из одного и того же пути кода, то интеграция его - это "сделай-он- один раз-и-забыть-о-это" вид деятельности.

Ответ 5

Вы можете попробовать определить его как тип записи,

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

Это говорит о том, что данные имеют свойство hello строки типа.

Ответ 6

Видимо, здесь не виноваты аннотации, просто введя некоторые неиспользуемые свойства в объект настроек, это приведет к переименованию компилятора.

Я хотел бы знать, откуда они пришли, и единственное логическое объяснение, которое у меня есть до сих пор (подтверждено здесь), заключается в том, что компилятор хранит глобальную таблицу имен, которую он не будет переименовывать. Просто наличие 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);

Результаты в следующем выпуске:

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);

Обратите внимание, что transform не переименовывается, но elementThing есть, даже если я пытаюсь аннотировать этот тип, я не могу заставить его переименовать transform соответственно.

Но если я добавлю следующий extern-источник function a() {}; a.prototype.elementThing = function() {};, он не переименует elementThing, несмотря на просмотр кода, я могу ясно сказать, что тип, возвращаемый конструктором, не связан с extern a, но каким-то образом, как это делает компилятор. Я предполагаю, что это просто ограничение компилятора закрытия, который, по моему мнению, является штопором.