Compilation avanzata

Panoramica

L'utilizzo di Closure Compiler con un compilation_level di ADVANCED_OPTIMIZATIONS offre tassi di compressione migliori rispetto alla compilazione con SIMPLE_OPTIMIZATIONS o WHITESPACE_ONLY. La compilazione con ADVANCED_OPTIMIZATIONS ottiene una compressione aggiuntiva grazie a un approccio più aggressivo nella trasformazione del codice e nella ridenominazione dei simboli. Tuttavia, questo approccio più aggressivo significa che devi prestare maggiore attenzione quando utilizzi ADVANCED_OPTIMIZATIONS per assicurarti che il codice di output funzioni allo stesso modo del codice di input.

Questo tutorial illustra la funzione del livello di compilazione ADVANCED_OPTIMIZATIONS e cosa puoi fare per assicurarti che il tuo codice funzioni dopo la compilazione con ADVANCED_OPTIMIZATIONS. Introduce anche il concetto di extern: un simbolo definito in un codice esterno a quello elaborato dal compilatore.

Prima di leggere questo tutorial, devi avere familiarità con la procedura di compilazione di JavaScript con uno degli strumenti Closure Compiler, ad esempio l'applicazione di compilazione basata su Java.

Una nota sulla terminologia: il flag della riga di comando --compilation_level supporta le abbreviazioni più comunemente utilizzate ADVANCED e SIMPLE, nonché quelle più precise ADVANCED_OPTIMIZATIONS e SIMPLE_OPTIMIZATIONS. Questo documento utilizza la forma più lunga, ma i nomi possono essere utilizzati in modo intercambiabile nella riga di comando.

  1. Compressione ancora migliore
  2. Come attivare ADVANCED_OPTIMIZATIONS
  3. Aspetti da considerare quando utilizzi ADVANCED_OPTIMIZATIONS
    1. Rimozione del codice che vuoi conservare
    2. Nomi proprietà non coerenti
    3. Compilazione di due parti di codice separatamente
    4. Riferimenti interrotti tra codice compilato e non compilato

Compressione ancora migliore

Con il livello di compilazione predefinito SIMPLE_OPTIMIZATIONS, Closure Compiler riduce le dimensioni di JavaScript rinominando le variabili locali. Esistono simboli diversi dalle variabili locali che possono essere abbreviati, tuttavia, e ci sono modi per ridurre il codice diversi dal cambio di nome dei simboli. La compilazione con ADVANCED_OPTIMIZATIONS sfrutta l'intera gamma di possibilità di riduzione del codice.

Confronta gli output per SIMPLE_OPTIMIZATIONS e ADVANCED_OPTIMIZATIONS per il seguente codice:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

La compilazione con SIMPLE_OPTIMIZATIONS abbrevia il codice in questo modo:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

La compilazione con ADVANCED_OPTIMIZATIONS abbrevia completamente il codice in questo modo:

alert("Flowers");

Entrambi gli script producono un avviso con il testo "Flowers", ma il secondo script è molto più piccolo.

Il livello ADVANCED_OPTIMIZATIONS va oltre il semplice accorciamento dei nomi delle variabili in diversi modi, tra cui:

  • Rinomina più aggressiva:

    La compilazione con SIMPLE_OPTIMIZATIONS rinomina solo i parametri note delle funzioni displayNoteTitle() e unusedFunction(), perché sono le uniche variabili nello script locali a una funzione. ADVANCED_OPTIMIZATIONS rinomina anche la variabile globale flowerNote.

  • Rimozione del codice inutilizzato:

    La compilazione con ADVANCED_OPTIMIZATIONS rimuove completamente la funzione unusedFunction(), perché non viene mai chiamata nel codice.

  • Inlining delle funzioni:

    La compilazione con ADVANCED_OPTIMIZATIONS sostituisce la chiamata a displayNoteTitle() con il singolo alert() che compone il corpo della funzione. Questa sostituzione di una chiamata di funzione con il corpo della funzione è nota come "inlining". Se la funzione fosse più lunga o più complicata, l'incorporamento potrebbe modificare il comportamento del codice, ma Closure Compiler determina che in questo caso l'incorporamento è sicuro e consente di risparmiare spazio. La compilazione con ADVANCED_OPTIMIZATIONS incorpora anche le costanti e alcune variabili quando determina di poterlo fare in modo sicuro.

Questo elenco è solo un campione delle trasformazioni che riducono le dimensioni che la compilazione di ADVANCED_OPTIMIZATIONS può eseguire.

Come attivare ADVANCED_OPTIMIZATIONS

Per abilitare ADVANCED_OPTIMIZATIONS per l'applicazione Closure Compiler, includi il flag della riga di comando --compilation_level ADVANCED_OPTIMIZATIONS, come nel seguente comando:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

Aspetti a cui prestare attenzione quando utilizzi ADVANCED_OPTIMIZATIONS

Di seguito sono elencati alcuni effetti indesiderati comuni di ADVANCED_OPTIMIZATIONS e i passaggi che puoi seguire per evitarli.

Rimozione del codice che vuoi conservare

Se compili solo la funzione riportata di seguito con ADVANCED_OPTIMIZATIONS, Closure Compiler produce un output vuoto:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

Poiché la funzione non viene mai chiamata nel codice JavaScript che passi al compilatore, Closure Compiler presuppone che questo codice non sia necessario.

In molti casi, questo comportamento è esattamente quello che vuoi. Ad esempio, se compili il codice insieme a una libreria di grandi dimensioni, Closure Compiler può determinare quali funzioni della libreria utilizzi effettivamente ed eliminare quelle che non utilizzi.

Se, tuttavia, noti che Closure Compiler rimuove funzioni che vuoi conservare, esistono due modi per impedirlo:

  • Sposta le chiamate di funzione nel codice elaborato da Closure Compiler.
  • Includi gli extern per le funzioni che vuoi esporre.

Le sezioni successive descrivono in dettaglio ciascuna opzione.

Soluzione: sposta le chiamate di funzione nel codice elaborato da Closure Compiler

Potresti riscontrare la rimozione indesiderata del codice se compili solo una parte del codice con Closure Compiler. Ad esempio, potresti avere un file di libreria che contiene solo definizioni di funzioni e un file HTML che include la libreria e che contiene il codice che chiama queste funzioni. In questo caso, se compili il file della libreria con ADVANCED_OPTIMIZATIONS, Closure Compiler rimuove tutte le funzioni della libreria.

La soluzione più semplice a questo problema è compilare le funzioni insieme alla parte del programma che le chiama. Ad esempio, Closure Compiler non rimuoverà displayNoteTitle() quando compila il seguente programma:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

La funzione displayNoteTitle() non viene rimossa in questo caso perché Closure Compiler rileva che viene chiamata.

In altre parole, puoi impedire la rimozione di codice indesiderato includendo il punto di ingresso del tuo programma nel codice che passi a Closure Compiler. Il punto di ingresso di un programma è il punto del codice in cui inizia l'esecuzione del programma. Ad esempio, nel programma di note sui fiori della sezione precedente, le ultime tre righe vengono eseguite non appena il codice JavaScript viene caricato nel browser. Questo è il punto di ingresso per questo programma. Per determinare il codice da conservare, Closure Compiler inizia da questo punto di ingresso e traccia il flusso di controllo del programma in avanti da qui.

Soluzione: includi gli extern per le funzioni che vuoi esporre

Ulteriori informazioni su questa soluzione sono disponibili di seguito e nella pagina relativa a esterni ed esportazioni.

Nomi proprietà incoerenti

La compilazione di Closure Compiler non modifica mai i valori letterali stringa nel codice, indipendentemente dal livello di compilazione utilizzato. Ciò significa che la compilazione con ADVANCED_OPTIMIZATIONS tratta le proprietà in modo diverso a seconda che il codice acceda a queste proprietà con una stringa. Se combini riferimenti di stringa a una proprietà con riferimenti con sintassi punto, Closure Compiler rinomina alcuni dei riferimenti a quella proprietà, ma non altri. Di conseguenza, il codice probabilmente non verrà eseguito correttamente.

Ad esempio, prendi in considerazione il seguente codice:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

Le ultime due istruzioni di questo codice sorgente fanno esattamente la stessa cosa. Tuttavia, quando comprimi il codice con ADVANCED_OPTIMIZATIONS, ottieni questo:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

L'ultima istruzione del codice compresso genera un errore. Il riferimento diretto alla proprietà myTitle è stato rinominato in a, ma il riferimento tra virgolette a myTitle all'interno della funzione displayNoteTitle non è stato rinominato. Di conseguenza, l'ultima affermazione si riferisce a una proprietà myTitle che non esiste più.

Soluzione: utilizza nomi coerenti per le proprietà

Questa soluzione è piuttosto semplice. Per un determinato tipo o oggetto, utilizza esclusivamente la sintassi con il punto o le stringhe tra virgolette. Non mescolare le sintassi, soprattutto in riferimento alla stessa proprietà.

Inoltre, se possibile, preferisci utilizzare la sintassi con il punto, in quanto supporta controlli e ottimizzazioni migliori. Utilizza l'accesso alle proprietà delle stringhe tra virgolette solo quando non vuoi che Closure Compiler esegua la ridenominazione, ad esempio quando il nome proviene da una fonte esterna, come JSON decodificato.

Compilazione separata di due porzioni di codice

Se dividi l'applicazione in diversi blocchi di codice, potresti voler compilare i blocchi separatamente. Tuttavia, se due blocchi di codice interagiscono, ciò potrebbe causare difficoltà. Anche se riesci, l'output delle due esecuzioni di Closure Compiler non sarà compatibile.

Ad esempio, supponiamo che un'applicazione sia divisa in due parti: una che recupera i dati e una che li visualizza.

Ecco il codice per recuperare i dati:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

Ecco il codice per visualizzare i dati:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

Se provi a compilare questi due blocchi di codice separatamente, riscontrerai diversi problemi. Innanzitutto, Closure Compiler rimuove la funzione getData() per i motivi descritti in Rimozione del codice che vuoi conservare. In secondo luogo, Closure Compiler genera un errore irreversibile durante l'elaborazione del codice che mostra i dati.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

Poiché il compilatore non ha accesso alla funzione getData() quando compila il codice che mostra i dati, considera getData come non definito.

Soluzione: compila tutto il codice per una pagina

Per garantire una compilazione corretta, compila tutto il codice di una pagina insieme in un'unica esecuzione di compilazione. Closure Compiler può accettare più file JavaScript e stringhe JavaScript come input, in modo da poter trasferire il codice della libreria e altro codice insieme in un'unica richiesta di compilazione.

Nota: questo approccio non funziona se devi combinare codice compilato e non compilato. Consulta la sezione Riferimenti interrotti tra codice compilato e non compilato per suggerimenti su come gestire questa situazione.

Riferimenti interrotti tra codice compilato e non compilato

La ridenominazione dei simboli in ADVANCED_OPTIMIZATIONS interromperà la comunicazione tra il codice elaborato da Closure Compiler e qualsiasi altro codice. La compilazione rinomina le funzioni definite nel codice sorgente. Qualsiasi codice esterno che chiama le tue funzioni non funzionerà dopo la compilazione, perché fa ancora riferimento al vecchio nome della funzione. Allo stesso modo, i riferimenti nel codice compilato a simboli definiti esternamente potrebbero essere modificati da Closure Compiler.

Tieni presente che il "codice non compilato" include qualsiasi codice passato alla funzione eval() come stringa. Closure Compiler non modifica mai i valori letterali stringa nel codice, quindi non cambia le stringhe passate alle istruzioni eval().

Tieni presente che si tratta di problemi correlati ma distinti: mantenere la comunicazione compilata-esterna e mantenere la comunicazione esterna-compilata. Questi problemi separati hanno una soluzione comune, ma ci sono sfumature per ogni aspetto. Per ottenere il massimo da Closure Compiler è importante capire di quale caso si tratta.

Prima di continuare, ti consigliamo di acquisire familiarità con extern e esportazioni.

Soluzione per chiamare codice esterno da codice compilato: compilazione con extern

Se utilizzi il codice fornito nella pagina da un altro script, devi assicurarti che Closure Compiler non rinomini i riferimenti ai simboli definiti in quella libreria esterna. A tal fine, includi un file contenente gli extern per la libreria esterna nella compilazione. In questo modo, Closure Compiler saprà quali nomi non controlli e che quindi non possono essere modificati. Il codice deve utilizzare gli stessi nomi utilizzati dal file esterno.

Esempi comuni sono le API come l'API OpenSocial e l'API Google Maps. Ad esempio, se il tuo codice chiama la funzione OpenSocial opensocial.newDataRequest(), senza gli extern appropriati, Closure Compiler trasformerà questa chiamata in a.b().

Soluzione per chiamare codice compilato da codice esterno: implementazione di extern

Se hai codice JavaScript che riutilizzi come libreria, potresti voler utilizzare Closure Compiler per ridurre solo la libreria, consentendo comunque al codice non compilato di chiamare le funzioni nella libreria.

La soluzione in questa situazione è implementare un insieme di extern che definiscono l'API pubblica della tua libreria. Il tuo codice fornirà definizioni per i simboli dichiarati in questi extern. Ciò significa qualsiasi classe o funzione menzionata dai tuoi esterni. Potrebbe anche significare che le tue classi implementano interfacce dichiarate negli extern.

Questi esterni sono utili anche per gli altri, non solo per te. I consumatori della tua libreria dovranno includerli se compilano il codice, poiché la tua libreria rappresenta uno script esterno dal loro punto di vista. Pensa agli esterni come al contratto tra te e i tuoi consumatori, entrambi avete bisogno di una copia.

A questo scopo, assicurati che durante la compilazione del codice vengano inclusi anche gli extern. Potrebbe sembrare insolito, dato che spesso pensiamo agli extern come "provenienti da un'altra posizione", ma è necessario comunicare a Closure Compiler quali simboli stai esponendo, in modo che non vengano rinominati.

Un avvertimento importante è che potresti ricevere diagnostica di "definizione duplicata" relativa al codice che definisce i simboli extern. Closure Compiler presuppone che qualsiasi simbolo negli extern sia fornito da una libreria esterna e al momento non riesce a capire che stai fornendo intenzionalmente una definizione. Questi messaggi diagnostici possono essere eliminati e l'eliminazione può essere considerata una conferma che stai effettivamente soddisfacendo la tua API.

Inoltre, Closure Compiler può verificare il tipo delle definizioni in modo che corrispondano ai tipi delle dichiarazioni extern. In questo modo, avrai un'ulteriore conferma che le tue definizioni sono corrette.