2011-10-19 4 views
13

Sto cercando di ottenere che il compilatore di chiusura di Google non rinomini gli oggetti quando vengono passati come impostazioni o dati a una funzione. Osservando l'annotazioni presenti in jQuery, ho pensato che questo avrebbe funzionato:Impedisci al compilatore di chiusura di Google di rinominare gli oggetti delle impostazioni

/** @param {Object.<string,*>} data */ 
window.hello = function(data) { 
    alert(data.hello); 
}; 
hello({ hello: "World" }); 

Tuttavia, finisce in questo modo:

window.a = function(b) { 
    alert(b.a) 
}; 
hello({a:"World"}); 

La funzione ajax trovato here ha questa annotazione e sembra funzionare . Quindi, perché non questo? Se i dati sono il valore di ritorno da una fonte esterna o da un oggetto impostazioni che vorrei poter dire al compilatore di non toccarlo, usare il trucco this["escape"] è intrusivo per qualcosa di simile a mio parere.

Ecco un esempio migliore

function ajax(success) { 
     // do AJAX call 
    $.ajax({ success: success }); 
} 
ajax(function(data) { 
    alert(data.Success); 
}); 

uscita:

$.b({c:function(a){alert(a.a)}}); 

success è stato rinominato in c e Success (con la S maiuscola) è stato rinominato a.

ora compilo lo stesso codice con il jQuery 1.6 externs file e ottengo il seguente output:

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

produce anche un avvertimento che la proprietà Success non è definita, come mi sarei aspettato, ma non può cambiare titolo Success a semplicemente a, che comunque romperà il mio codice. Osservo il presente di annotazione per lo ajax e trovo questo tipo di espressione {Object.<string,*>=}, annoto il mio codice di conseguenza e ricompilifico. Ancora non funziona ...

+1

Per una migliore comprensione per chi legge questo in futuro: il JS collegato è un file extern. Viene semplicemente usato insieme al code-to-compile per impedire la ridenominazione di variabili, proprietà e funzioni/metodi "esternalizzati". Le annotazioni in esso indicano semplicemente l'uso corretto per i controlli del tipo in fase di compilazione. In nessun modo istruiscono il compilatore a non rinominare i metodi ei parametri di jQuery. – Kiruse

risposta

8

Dal momento che la vostra attenzione sembra essere sulla sorgente piuttosto che l'uscita, sembra che quello che stai concentrato su è asciutto (Don Ripeti te stesso). Ecco una soluzione DRY alternativa.

È possibile eseguire la chiusura Compiler con --create_name_map_files. In questo modo viene emesso un file denominato _props_map.out. È possibile avere le chiamate sul lato server di emissione JSON (ASP.Net MVC o qualsiasi altra cosa possa essere) utilizzare queste mappe quando si emettono i loro JSON, in modo che emettano effettivamente JSON miniato che sfrutta i nomi del compilatore di chiusura. In questo modo puoi cambiare il nome di una variabile o proprietà sul tuo Controller e i tuoi script, aggiungere altro, ecc., E la ministeria trasporta dagli script fino all'output del Controller. Tutta la tua fonte, incluso il Controller, continua ad essere non minificata e facile da leggere.

+0

Questa è una soluzione con cui sono disposto ad andare. Un po 'di lavoro da impostare, ma questa è davvero una buona idea. –

+0

Che cosa è esattamente in questo file '_props_map.out'? Ne ho creato uno ma non mi sembra una buona idea ... –

+1

NVM, stavo eseguendo il compilatore senza nemmeno attivare le ottimizzazioni avanzate. –

0

Si potrebbe provare a definirlo come un tipo di record,

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

che gli dice di dati ha proprietà ciao di tipo stringa.

+0

Anche se ha funzionato, non ho un record statico, è un oggetto con molte proprietà, impostazioni e dati. Non conosco lo schema, quindi non posso definirlo. La mia teoria di lavoro è che il compilatore di chiusura dovrebbe prenderlo, fuori da quella annotazione di tipo, ma non sembra funzionare. –

0

Apparentemente le annotazioni non sono da biasimare qui, semplicemente introducendo alcune proprietà inutilizzate all'oggetto delle impostazioni, il compilatore rinominerà le cose.

Mi piacerebbe sapere dove questi venivano e l'unica spiegazione logica che ho finora (confermata here), è che il compilatore mantiene una tabella nome globale delle cose non sarà rinominare. Avere semplicemente un extern con un nome si tradurrà in una qualsiasi proprietà di quel nome.

/** @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); 

risultati nel seguente 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); 

notare come transform non viene rinominata, ma elementThing è, anche se cerco di annotare questo tipo non riesco a farlo rinominare transform conseguenza.

Ma se aggiungo il seguente origine extern function a() {}; a.prototype.elementThing = function() {}; non rinominerà elementThing nonostante guardando il codice, posso chiaramente dire che il tipo restituito dal costruttore è correlato al extern a, eppure in qualche modo, questo è come il il compilatore lo fa.Immagino che questa sia solo una limitazione del compilatore di chiusura, che credo sia un vero peccato.

6

Penso che quello che si sta realmente cercando di fare è impedire che la ridenominazione di nomi di proprietà sull'oggetto di ritorno da un controller AJAX sul server, che ovviamente avrebbe rotto la chiamata.

Così, quando si chiama

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

lo desiderate di lasciare il messaggio da solo, giusto?

Se è così che è fatto dal modo in cui lei ha citato in precedenza, ma è compilato bene per .message nell'output. La diviene sopra:

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

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

Minifies subito a:

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

Utilizzando r['Message'] invece di r.Message, si impedisce la ridenominazione proprietà da parte del minifier. Questo è chiamato il metodo di esportazione, che come noterai nella documentazione del Closure Compiler è preferibile rispetto agli extern. Cioè, se usi il metodo externs per farlo, farai arrabbiare molte persone su Google. Ci hanno anche messo un ID sull'intestazione di nome, "no": http://code.google.com/closure/compiler/docs/api-tutorial3.html#no

Detto questo, si può anche fare questo con il metodo persone esterne, e qui è in tutta la sua stranezza:

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 \ chiusura> java-jar compiler.jar --externs externs.js --js jquery-1.6.js --js test.js - -compilazione_ ADVANCED_OPTIMIZATIONS livello --js_output_file output.js

ed esce:

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

Così esattamente quello che non volevo fare, non è che l'output qui il compilatore gira 'this [" abc "]' in 'this.abc' perché è più corto, ma non mi importa meno del uscita in questo caso, quello che mi interessa è l'input, perché è quello che sto creando. E non voglio scrivere codice con parentesi ovunque, è troppo prolisso. Ma usare un 'externs.js' con il mio schema di dati è in realtà qualcosa che potrei finire per fare ma ti allontana da alcuni dei benefici dell'uso di oggetti dinamici, che è una specie del problema che sto avendo qui. –

+0

Potrei finire per non compilare questi script dinamici, non sono sicuro adesso se c'è un approccio migliore. –

+0

Se si segue l'approccio di Externs sopra, è possibile aggiungere una voce per ogni (presumo) controller MVC che emette JSON, che in realtà è piuttosto bello - si ottiene la sicurezza del tipo che inizia con i dati provenienti dal controller.Pubblicherò una seconda risposta incentrata su DRY (Do not Repeat Yourself). –

3

Sfortunatamente, fare data["hello"] dappertutto è il modo consigliato (e ufficiale) di chiusura per evitare la ridenominazione variabile.

Sono totalmente d'accordo con te che non mi piace un po 'questo. Tuttavia, tutte le altre soluzioni ti forniranno risultati non ottimali con la compilation o potrebbero rompersi in situazioni oscure e, se sei disposto a vivere con risultati non ottimali, perché utilizzare il Closure Compiler in primo luogo?

Tuttavia, i dati restituiti da un server sono davvero tutto ciò che è necessario gestire, poiché si dovrebbe essere in grado di consentire a Closure di rinominare in modo sicuro qualsiasi altra cosa nel programma. Nel corso del tempo, ho scoperto che è meglio scrivere wrapper che duplicheranno dati provenienti da un server. In altre parole:

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

In questo modo, qualsiasi oggetto unmangled vive solo brevemente a destra dopo essere stato deserializzato da Ajax. Quindi viene clonato in un oggetto che può essere compilato/ottimizzato da Closure. Usa questo clone tutto nel tuo programma e ottieni tutti i vantaggi delle ottimizzazioni di Closure.

Ho anche scoperto che è utile avere una funzione di "elaborazione" che elabora immediatamente tutto ciò che viene fornito tramite Ajax da un server: oltre a clonare l'oggetto, è possibile inserire il codice di post-elaborazione lì, così come convalide, correzioni di errori e controlli di sicurezza ecc. In molte web app, hai già queste funzioni per fare questo controllo sui dati restituiti in primo luogo - tu MAI dati di fiducia restituiti da un server, ora?

+0

Comincio a capire l'inutilità di cercare di evitare l'approccio "Tutto non quotato" ... –

+0

Benvenuto nel club ... Ci ho messo un po 'a capire l'inutilità ... Un po' mi ricorda il vecchio hacker movie "WarGames" - Uno strano gioco, l'unica mossa vincente è non giocare. Con Closure, l'unico modo è farlo a modo loro, o non usarlo affatto. :-( –

1

Un po 'tardi per il gioco, ma ho ottenuto intorno a questo semplicemente scrivendo una coppia di funzioni di gateway che elaborano tutti i miei oggetti in entrata e in uscita 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); 
} 

La cosa utile è che il closureToRemote dict is flat - ovvero, anche se è necessario specificare i nomi degli attributi secondari in modo che il compilatore di chiusura lo sappia, è possibile specificarli tutti sullo stesso livello. Ciò significa che il formato di risposta può effettivamente essere una gerarchia abbastanza complessa, sono solo le chiavi di base a cui si accede direttamente tramite un nome che deve essere codificato in modo rigido da qualche parte.

Ogni volta che sto per effettuare una chiamata Ajax, trasmetto i dati che sto inviando tramite declosureizeData(), il che implica che sto prendendo i dati dal namespace della chiusura. Quando ricevo i dati, la prima cosa che faccio è eseguirla attraverso closureizeData() per ottenere i nomi nello spazio dei nomi di chiusura.

Nota: il dizionario di mappatura deve essere solo uno spazio nel nostro codice e se si dispone di un codice ajax ben strutturato che entra sempre nello stesso percorso di codice, l'integrazione è un "do-it" tipo "attività e dimenticanza".