Compilación avanzada

Descripción general

El uso del compilador de Closure con un compilation_level de ADVANCED_OPTIMIZATIONS ofrece mejores índices de compresión que la compilación con SIMPLE_OPTIMIZATIONS o WHITESPACE_ONLY. La compilación con ADVANCED_OPTIMIZATIONS logra una compresión adicional, ya que es más agresiva en la forma 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 muestra 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 introduce el concepto de extern: un símbolo que se define en un código externo al código procesado por el compilador.

Antes de leer este instructivo, debes estar familiarizado con el proceso de compilación de JavaScript con una de las herramientas de Closure Compiler (la IU del servicio del compilador, la API del servicio del compilador o la aplicación del compilador).

Nota sobre la terminología: La marca de línea de comandos --compilation_level admite las abreviaturas más comunes ADVANCED y SIMPLE, así como los ADVANCED_OPTIMIZATIONS y SIMPLE_OPTIMIZATIONS más precisos. En este documento, se usa el formulario más largo, 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. Aspectos a tener en cuenta cuando uses ADVANCED_OPTIMIZATIONS
    1. Eliminación del código que quieres conservar
    2. Los nombres de las propiedades no coinciden
    3. Compila dos partes del 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 SIMPLE_OPTIMIZATIONS, Closure Compiler reduce el tamaño de JavaScript cambiando el nombre de las variables locales. Sin embargo, hay símbolos que no son variables locales que se pueden acortar, y hay formas de reducir el código además de cambiarles el nombre. La compilación con ADVANCED_OPTIMIZATIONS aprovecha toda la variedad de 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 completamente el código de la siguiente manera:

alert("Flowers");

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

El nivel ADVANCED_OPTIMIZATIONS va más allá de la abreviatura simple de nombres de 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 de la secuencia de comandos 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 entregado:

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

  • intercalado de función:

    La compilación con ADVANCED_OPTIMIZATIONS reemplaza la llamada a displayNoteTitle() por la alert() única 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 "intercalado". Si la función fuera más o más complicada, la incorporación podría cambiar el comportamiento del código, pero Closure Compiler determina que, en este caso, la intercalación es segura y ahorra espacio. La compilación con ADVANCED_OPTIMIZATIONS también intercala constantes y algunas variables cuando determina que puede hacerlo de forma segura.

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

Cómo habilitar ADVANCED_OPTIMIZATIONS

La IU del servicio de Closure Compiler, la API de servicio y la aplicación tienen métodos diferentes para configurar compilation_level en ADVANCED_OPTIMIZATIONS.

Cómo habilitar ADVANCED_OPTIMIZATIONS en la IU del servicio de Closure Compiler

Si quieres habilitar ADVANCED_OPTIMIZATIONS para la IU del servicio de Closure Compiler, haz clic en el botón de selección "Avanzado".

Cómo habilitar ADVANCED_OPTIMIZATIONS en la API del servicio de Closure Compiler

A fin de habilitar ADVANCED_OPTIMIZATIONS para la API del servicio de Closure Compiler, incluye un parámetro de solicitud llamado compilation_level con un valor de ADVANCED_OPTIMIZATIONS, como en el siguiente programa de Python:

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

Cómo habilitar ADVANCED_OPTIMIZATIONS en la aplicación de Closure Compiler

A fin de habilitar ADVANCED_OPTIMIZATIONS para la aplicación de Closure Compiler, 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é hay que tener en cuenta cuando se usa ADVANCED_OPTIMIZATIONS

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

Eliminación del código que deseas conservar

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

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

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

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

Sin embargo, si notas que Closure Compiler está quitando funciones que quieres conservar, puedes hacerlo de las siguientes dos maneras:

  • Mueve tus llamadas de función al código procesado por Closure Compiler.
  • Incluye elementos externos para las funciones que deseas exponer.

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

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

Es posible que encuentres una eliminación de código no deseada si solo compilas parte de tu código con Closure Compiler. Por ejemplo, puedes tener un archivo de biblioteca que contenga solo 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, Closure Compiler quita todas las funciones de tu 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 Closure Compiler ve que se la llama.

En otras palabras, puedes evitar la eliminación no deseada de código incluyendo el punto de entrada de tu programa en el código que pasas a Closure Compiler. El punto de entrada de un programa es el lugar del código donde el programa comienza a ejecutarse. Por ejemplo, en el programa de nota de flores de la sección anterior, las últimas tres líneas se ejecutan tan pronto como se carga la versión JavaScript en el navegador. Este es el punto de entrada para este programa. Para determinar qué código debes conservar, Closure Compiler comienza en este punto de entrada y sigue el flujo de control del programa desde allí.

Solución: Incluye elementos externos para las funciones que deseas exponer

Obtén más información sobre esta solución a continuación y en la página sobre externas y exportaciones.

Los nombres de las propiedades no coinciden

La compilación del Closure Compiler nunca cambia los literales de string 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 string. Si combinas referencias de string a una propiedad con referencias de sintaxis de punto, el compilador de Closure cambia el nombre de algunas de las referencias a esa propiedad, pero no de otras. Como resultado, es probable que el código no se ejecute correctamente.

Por ejemplo, toma el siguiente código:

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

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

Las dos últimas declaraciones 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 declaración en el código comprimido produce un error. Se cambió el nombre de la referencia directa a la propiedad myTitle a a, pero no se cambió el nombre de la referencia entre comillas a myTitle dentro de la función displayNoteTitle. Como resultado, la última declaración hace referencia a una propiedad myTitle que ya no está allí.

Solución: Sea coherente en los nombres de sus propiedades

Esta solución es bastante simple. Para cualquier tipo o objeto determinado, usa la sintaxis de puntos o las strings entre comillas de forma exclusiva. No mezcles las sintaxis, especialmente en referencia a la misma propiedad.

Además, cuando sea posible, se recomienda usar la sintaxis de puntos, ya que admite mejores comprobaciones y optimizaciones. Usa el acceso a la propiedad de string 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.

Cómo compilar dos partes del código por separado

Si divides tu aplicación en diferentes fragmentos de código, te recomendamos que compiles los fragmentos por separado. Sin embargo, si dos fragmentos de código interactúan, hacerlo puede causar dificultad. Incluso si tiene éxito, el resultado de las dos ejecuciones de Closure Compiler 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, Closure Compiler quita la función getData() por los motivos descritos en Cómo quitar el código que quieres conservar. En segundo lugar, Closure Compiler produce un error fatal cuando se procesa el código que muestra los datos.

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

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

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

Para garantizar una compilación adecuada, compila todo el código de una página juntos en una sola ejecución de compilación. Closure Compiler puede aceptar varios archivos JavaScript y strings de JavaScript como entrada para que puedas pasar el código de biblioteca y otro código juntos en una sola solicitud de compilación.

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

Referencias rotas entre el código compilado y no compilado

El cambio de nombre de los símbolos en ADVANCED_OPTIMIZATIONS interrumpirá la comunicación entre el código procesado por el Closure Compiler y cualquier otro código. La compilación renombra las funciones definidas en tu código fuente. Cualquier código externo que llame a tus funciones fallará después de la compilación, ya que sigue haciendo referencia al nombre anterior de la función. Del mismo modo, Closure Compiler puede modificar las referencias en el código compilado a símbolos definidos de forma externa.

Ten en cuenta que el "código sin compilar" incluye cualquier código que se pase a la función eval() como una string. Closure Compiler nunca modifica los literales de string del código, por lo que no cambia las strings que se pasan a las declaraciones eval().

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

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

Solución para llamar a un código externo desde un código compilado: compilación con externs

Si usas código proporcionado por otra secuencia de comandos en tu página, 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 elementos externalizados de la biblioteca externa. Eso le indicará a Closure Compiler los nombres que no controlas y, por lo tanto, no se puede cambiar. El código debe usar los mismos nombres que usa el archivo externo.

Algunos ejemplos comunes de esto son las API, 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 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 elementos externos

Si tienes código JavaScript que reutilizas como biblioteca, te recomendamos que uses Closure Compiler para reducir solo la biblioteca y permitir que el código no compilado llame a funciones de la biblioteca.

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

Estos externos son útiles para otros usuarios, 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 externalizados como el contrato entre tú y los consumidores, pero ambos necesitan una copia.

Para ello, asegúrate de que, cuando compiles tu código, también incluyas los elementos externos en la compilación. Esto puede resultar inusual, ya que a menudo pensamos que los externs son "provenientes de otro lugar", pero es necesario indicarle al compilador de Closure qué símbolos estás exponiendo, para que no se les cambie el nombre.

Una advertencia importante aquí es que puedes obtener diagnósticos de “definición duplicada” sobre el código que define los símbolos de extern. Closure Compiler supone que cualquier biblioteca externa suministra cualquier símbolo en los elementos externos y, por el momento, no puede comprender que proporcionas una definición de forma intencional. Estos diagnósticos son seguros para suprimir, y puedes considerar la supresión como una confirmación de que realmente estás entregando tu API.

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