Compilación avanzada

Descripción general

Usar el compilador de Closure con un nivel de optimización compilation_level de ADVANCED_OPTIMIZATIONS ofrece mejores tasas de compresión que la compilación con SIMPLE_OPTIMIZATIONS o WHITESPACE_ONLY. La compilación con ADVANCED_OPTIMIZATIONS logra una compresión adicional porque es más agresiva en las formas en que transforma el código y cambia el nombre de los símbolos. Sin embargo, este enfoque más agresivo significa que debes tener más cuidado cuando usas ADVANCED_OPTIMIZATIONS para asegurarte de que el código de salida funcione de la misma manera que el código de entrada.

En este instructivo, se explica qué hace el nivel de compilación ADVANCED_OPTIMIZATIONS y qué puedes hacer para asegurarte de que tu código funcione después de la compilación con ADVANCED_OPTIMIZATIONS. También se presenta el concepto de extern: un símbolo que se define en código externo al código procesado por el compilador.

Antes de leer este instructivo, debes conocer el proceso de compilación de JavaScript con una de las herramientas de Closure Compiler, como la aplicación de compilador basada en Java.

Nota sobre la terminología: La marca de línea de comandos --compilation_level admite las abreviaturas de uso más común ADVANCED y SIMPLE, así como las más precisas ADVANCED_OPTIMIZATIONS y SIMPLE_OPTIMIZATIONS. En este documento, se usa la forma más larga, pero los nombres se pueden usar indistintamente en la línea de comandos.

  1. Compresión aún mejor
  2. Cómo habilitar ADVANCED_OPTIMIZATIONS
  3. Qué debes tener en cuenta cuando usas ADVANCED_OPTIMIZATIONS
    1. Eliminación del código que deseas conservar
    2. Nombres de propiedad incoherentes
    3. Compilación de dos porciones de código por separado
    4. Referencias rotas entre el código compilado y el no compilado

Compresión aún mejor

Con el nivel de compilación predeterminado de SIMPLE_OPTIMIZATIONS, el compilador de Closure reduce el tamaño de JavaScript cambiando el nombre de las variables locales. Sin embargo, hay otros símbolos además de las variables locales que se pueden abreviar, y hay otras formas de reducir el código además de cambiar el nombre de los símbolos. La compilación con ADVANCED_OPTIMIZATIONS aprovecha al máximo las posibilidades de reducción de código.

Compara los resultados de SIMPLE_OPTIMIZATIONS y ADVANCED_OPTIMIZATIONS para el siguiente código:

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

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

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

La compilación con SIMPLE_OPTIMIZATIONS acorta el código de la siguiente manera:

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

La compilación con ADVANCED_OPTIMIZATIONS acorta por completo el código a lo siguiente:

alert("Flowers");

Ambas secuencias de comandos producen una alerta que dice "Flowers", pero la segunda secuencia de comandos es mucho más pequeña.

El nivel ADVANCED_OPTIMIZATIONS va más allá del simple acortamiento de los nombres de las variables de varias maneras, incluidas las siguientes:

  • Cambio de nombre más agresivo:

    La compilación con SIMPLE_OPTIMIZATIONS solo cambia el nombre de los parámetros note de las funciones displayNoteTitle() y unusedFunction(), ya que estas son las únicas variables del script que son locales para una función. ADVANCED_OPTIMIZATIONS también cambia el nombre de la variable global flowerNote.

  • Eliminación de código no utilizado:

    La compilación con ADVANCED_OPTIMIZATIONS quita por completo la función unusedFunction(), ya que nunca se llama en el código.

  • Inserción de funciones:

    La compilación con ADVANCED_OPTIMIZATIONS reemplaza la llamada a displayNoteTitle() con el único alert() que compone el cuerpo de la función. Este reemplazo de una llamada a función por el cuerpo de la función se conoce como "inserción en línea". Si la función fuera más larga o más complicada, la inserción podría cambiar el comportamiento del código, pero el compilador de Closure determina que, en este caso, la inserción es segura y ahorra espacio. La compilación con ADVANCED_OPTIMIZATIONS también inserta constantes y algunas variables cuando determina que puede hacerlo de forma segura.

Esta lista es solo una muestra de las transformaciones que reducen el tamaño y que puede realizar la compilación de ADVANCED_OPTIMIZATIONS.

Cómo habilitar ADVANCED_OPTIMIZATIONS

Para habilitar ADVANCED_OPTIMIZATIONS para la aplicación del compilador de Closure, incluye la marca de línea de comandos --compilation_level ADVANCED_OPTIMIZATIONS, como en el siguiente comando:

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

Qué debes tener en cuenta cuando usas ADVANCED_OPTIMIZATIONS

A continuación, se indican algunos efectos no deseados comunes de ADVANCED_OPTIMIZATIONS y los pasos que puedes seguir para evitarlos.

Eliminación del código que deseas conservar

Si compilas solo la siguiente función con ADVANCED_OPTIMIZATIONS, Closure Compiler produce un resultado vacío:

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

Dado que la función nunca se llama en el código JavaScript que pasas al compilador, Closure Compiler supone que este código no es necesario.

En muchos casos, este comportamiento es exactamente lo que deseas. Por ejemplo, si compilas tu código junto con una biblioteca grande, Closure Compiler puede determinar qué funciones de esa biblioteca usas realmente y descartar las que no usas.

Sin embargo, si observas que Closure Compiler quita funciones que deseas conservar, existen dos formas de evitarlo:

  • Mueve las llamadas a funciones al código que procesa Closure Compiler.
  • Incluye externs para las funciones que quieras exponer.

En las siguientes secciones, se analiza cada opción con más detalle.

Solución: Mueve las llamadas a funciones al código procesado por el compilador de Closure

Es posible que se quite código no deseado si solo compilas parte de tu código con Closure Compiler. Por ejemplo, puedes tener un archivo de biblioteca que solo contenga definiciones de funciones y un archivo HTML que incluya la biblioteca y que contenga el código que llama a esas funciones. En este caso, si compilas el archivo de biblioteca con ADVANCED_OPTIMIZATIONS, el compilador de Closure quita todas las funciones de la biblioteca.

La solución más simple a este problema es compilar tus funciones junto con la parte de tu programa que llama a esas funciones. Por ejemplo, Closure Compiler no quitará displayNoteTitle() cuando compile el siguiente programa:

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

En este caso, no se quita la función displayNoteTitle() porque el compilador de Closure ve que se la llama.

En otras palabras, puedes evitar la eliminación de código no deseado si incluyes el punto de entrada de tu programa en el código que pasas al compilador de Closure. El punto de entrada de un programa es el lugar del código donde comienza la ejecución del programa. Por ejemplo, en el programa de notas de flores de la sección anterior, las últimas tres líneas se ejecutan en cuanto se carga JavaScript en el navegador. Este es el punto de entrada para este programa. Para determinar qué código debes conservar, el compilador de Closure comienza en este punto de entrada y realiza un seguimiento del flujo de control del programa hacia adelante desde allí.

Solución: Incluye Externs para las funciones que deseas exponer

Encontrarás más información sobre esta solución a continuación y en la página sobre externs y exports.

Nombres de propiedad incoherentes

La compilación de Closure Compiler nunca cambia los literales de cadena en tu código, sin importar el nivel de compilación que uses. Esto significa que la compilación con ADVANCED_OPTIMIZATIONS trata las propiedades de manera diferente según si tu código accede a ellas con una cadena. Si mezclas referencias de cadenas a una propiedad con referencias de sintaxis de puntos, Closure Compiler cambia el nombre de algunas de las referencias a esa propiedad, pero no de otras. Como resultado, es probable que tu código no se ejecute correctamente.

Por ejemplo, considera el siguiente código:

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

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

Las dos últimas instrucciones de este código fuente hacen exactamente lo mismo. Sin embargo, cuando comprimes el código con ADVANCED_OPTIMIZATIONS, obtienes lo siguiente:

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

La última instrucción del código comprimido produce un error. Se cambió el nombre de la referencia directa a la propiedad myTitle por a, pero no se cambió el nombre de la referencia entre comillas a myTitle dentro de la función displayNoteTitle. Como resultado, la última instrucción hace referencia a una propiedad myTitle que ya no existe.

Solución: Sé coherente con los nombres de tus propiedades

Esta solución es bastante simple. Para cualquier tipo u objeto determinado, usa exclusivamente la sintaxis de puntos o las cadenas entre comillas. No mezcles las sintaxis, especialmente cuando hagas referencia a la misma propiedad.

Además, cuando sea posible, prefiere usar la sintaxis de punto, ya que admite mejores verificaciones y optimizaciones. Usa el acceso a propiedades de cadenas entre comillas solo cuando no quieras que Closure Compiler cambie el nombre, por ejemplo, cuando el nombre proviene de una fuente externa, como JSON decodificado.

Compilación de dos partes del código por separado

Si divides tu aplicación en diferentes fragmentos de código, es posible que desees compilar los fragmentos por separado. Sin embargo, si dos fragmentos de código interactúan entre sí, esto puede causar dificultades. Incluso si lo logras, el resultado de las dos ejecuciones del compilador de Closure no será compatible.

Por ejemplo, supongamos que una aplicación se divide en dos partes: una que recupera datos y otra que los muestra.

Este es el código para recuperar los datos:

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

Este es el código para mostrar los datos:

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

Si intentas compilar estos dos fragmentos de código por separado, encontrarás varios problemas. En primer lugar, el compilador de Closure quita la función getData() por los motivos que se describen en Cómo quitar el código que deseas conservar. En segundo lugar, el compilador de Closure produce un error fatal cuando procesa el código que muestra los datos.

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

Dado que el compilador no tiene acceso a la función getData() cuando compila el código que muestra los datos, trata a getData como indefinido.

Solución: Compila todo el código de una página

Para garantizar una compilación adecuada, compila todo el código de una página en una sola ejecución de compilación. El compilador de Closure puede aceptar varios archivos y cadenas de JavaScript como entrada, por lo que puedes pasar código de biblioteca y otro código juntos en una sola solicitud de compilación.

Nota: Este enfoque no funcionará si necesitas combinar código compilado y sin compilar. Consulta Referencias rotas entre código compilado y sin compilar para obtener sugerencias sobre cómo controlar esta situación.

Referencias rotas entre el código compilado y el no compilado

El cambio de nombre de símbolos en ADVANCED_OPTIMIZATIONS interrumpirá la comunicación entre el código procesado por el compilador de Closure y cualquier otro código. La compilación cambia el nombre de las funciones definidas en tu código fuente. Cualquier código externo que llame a tus funciones dejará de funcionar después de que compiles, ya que seguirá haciendo referencia al nombre de la función anterior. Del mismo modo, Closure Compiler puede alterar las referencias en el código compilado a símbolos definidos externamente.

Ten en cuenta que el "código sin compilar" incluye cualquier código que se pase a la función eval() como una cadena. El compilador de Closure nunca altera los literales de cadena en el código, por lo que no cambia las cadenas que se pasan a las instrucciones eval().

Ten en cuenta que estos son problemas relacionados, pero distintos: mantener la comunicación compilada a externa y mantener la comunicación externa a compilada. Estos problemas separados tienen una solución común, pero hay matices en cada lado. Para aprovechar al máximo el compilador de Closure, es importante comprender qué caso tienes.

Antes de continuar, te recomendamos que te familiarices con los externs y las exportaciones.

Solución para llamar a código externo desde código compilado: Compilación con Externs

Si usas código proporcionado en tu página por otra secuencia de comandos, debes asegurarte de que Closure Compiler no cambie el nombre de tus referencias a los símbolos definidos en esa biblioteca externa. Para ello, incluye en tu compilación un archivo que contenga los externs de la biblioteca externa. Esto le indicará al compilador de Closure qué nombres no controlas y, por lo tanto, no se pueden cambiar. Tu código debe usar los mismos nombres que usa el archivo externo.

Algunos ejemplos comunes son las APIs como la API de OpenSocial y la API de Google Maps. Por ejemplo, si tu código llama a la función opensocial.newDataRequest() de OpenSocial, sin los elementos externos adecuados, Closure Compiler transformará esta llamada en a.b().

Solución para llamar a código compilado desde código externo: Implementación de Externs

Si tienes código JavaScript que reutilizas como biblioteca, es posible que desees usar Closure Compiler para reducir solo la biblioteca y, al mismo tiempo, permitir que el código sin compilar llame a funciones en la biblioteca.

En esta situación, la solución es implementar un conjunto de externs que definan la API pública de tu biblioteca. Tu código proporcionará definiciones para los símbolos declarados en estos archivos externos. Esto significa cualquier clase o función que mencionen tus archivos externos. También puede significar que tus clases implementen interfaces declaradas en los archivos externos.

Estos elementos externos también son útiles para otras personas, no solo para ti. Los consumidores de tu biblioteca deberán incluirlos si compilan su código, ya que tu biblioteca representa una secuencia de comandos externa desde su perspectiva. Piensa en los externs como el contrato entre tú y tus consumidores; ambos necesitan una copia.

Para ello, asegúrate de que, cuando compiles tu código, también incluyas los archivos externos en la compilación. Esto puede parecer inusual, ya que a menudo pensamos que los externos "vienen de otro lugar", pero es necesario indicarle al compilador de Closure qué símbolos expones para que no se cambie su nombre.

Una advertencia importante aquí es que es posible que obtengas diagnósticos de "definición duplicada" sobre el código que define los símbolos externos. El compilador de Closure supone que una biblioteca externa proporciona cualquier símbolo en los archivos externos y, actualmente, no puede comprender que proporcionas una definición de forma intencional. Estos diagnósticos se pueden suprimir de forma segura, y puedes considerar la supresión como una confirmación de que realmente estás cumpliendo con tu API.

Además, Closure Compiler puede verificar el tipo de tus definiciones para que coincidan con los tipos de las declaraciones externas. Esto proporciona una confirmación adicional de que tus definiciones son correctas.