Noções básicas sobre as restrições impostas pelo Compilador de interdições

O Compile Compiler espera que a entrada JavaScript esteja em conformidade com algumas restrições. Quanto maior o nível de otimização solicitado, mais restrições o compilador vai aplicar no JavaScript de entrada.

Este documento descreve as principais restrições para cada nível de otimização. Consulte também esta página da wiki para ver outras suposições feitas pelo compilador.

Restrições para todos os níveis de otimização

O compilador coloca as duas restrições a seguir em todo o JavaScript processado para todos os níveis de otimização:

  • O compilador reconhece apenas o ECMAScript.

    O ECMAScript 5 é a versão do JavaScript suportada em praticamente todos os lugares. No entanto, o compilador também é compatível com muitos recursos do ECMAScript 6. O compilador é compatível apenas com recursos oficiais de linguagem.

    Recursos específicos do navegador que estão em conformidade com a especificação de linguagem ECMAScript adequada funcionarão bem com o compilador. Por exemplo, objetos ActiveX são criados com sintaxe JavaScript legal, portanto, o código que cria objetos ActiveX funciona com o compilador.

    Os responsáveis pela manutenção do compilador trabalham ativamente para aceitar novas versões de linguagem e os recursos delas. Os projetos podem especificar qual versão de linguagem do ECMAScript eles pretendem usar a sinalização --language_in.

  • O compilador não preserva os comentários.

    Todos os níveis de otimização do compilador removem comentários. Portanto, o código que depende de comentários especialmente formatados não funciona com o Compiler.

    Por exemplo, como o compilador não preserva os comentários, não é possível usar os "comentários condicionais" do JScript. No entanto, é possível contornar essa restrição encapsulando comentários condicionais em expressões eval(). O Compilador pode processar o seguinte código sem gerar um erro:

     x = eval("/*@cc_on 2+@*/ 0");
    

    Observação: você pode incluir licenças de código aberto e outros textos importantes na parte superior da saída do compilador usando a anotação @preserve.

Restrições para SIMPLE_OPTIMIZATIONS

O nível de otimização simples renomeia parâmetros de função, variáveis locais e funções definidas localmente para reduzir o tamanho do código. No entanto, algumas construções JavaScript podem interromper esse processo de renomeação.

Evite as construções e práticas a seguir ao usar SIMPLE_OPTIMIZATIONS:

  • with:

    Quando você usa with, o compilador não consegue distinguir entre uma variável local e uma propriedade de objeto com o mesmo nome, então ele renomeia todas as instâncias do nome.

    Além disso, a instrução with dificulta a leitura do código por humanos. A instrução with muda as regras normais de resolução de nome e pode dificultar até mesmo para o programador que escreveu o código identificar a que um nome se refere.

  • eval():

    O compilador não analisa o argumento de string de eval(), então não renomeia símbolos nesse argumento.

  • Representações de strings de nomes de parâmetros ou funções:

    O compilador renomeia funções e parâmetros de função, mas não altera nenhuma string no código que se refere a funções ou parâmetros pelo nome. Portanto, evite representar nomes de parâmetros ou funções como strings no código. Por exemplo, a função da biblioteca de protótipo argumentNames() usa Function.toString() para recuperar os nomes dos parâmetros de uma função. Mas, embora argumentNames() possa induzir você a usar os nomes dos argumentos no código, a compilação no modo simples quebra esse tipo de referência.

Restrições para ADVANCED_OPTIMIZATIONS

O nível de compilação ADVANCED_OPTIMIZATIONS realiza as mesmas transformações que a SIMPLE_OPTIMIZATIONS e também adiciona renomeação global de propriedades, variáveis e funções, eliminação de código morto e nivelamento de propriedade. Esses novos cartões impõem outras restrições ao JavaScript de entrada. Em geral, o uso dos recursos dinâmicos do JavaScript impedirá análises estáticas corretas no código.

Implicações da renomeação de variáveis, funções e propriedades globais:

A renomeação global de ADVANCED_OPTIMIZATIONS torna as seguintes práticas perigosas:

  • Referências externas não declaradas:

    Para renomear variáveis, funções e propriedades globais corretamente, o compilador precisa saber sobre todas as referências a esses globals. É necessário informar ao compilador sobre símbolos definidos fora do código que está sendo compilado. A seção Compilação avançada e externas descreve como declarar símbolos externos.

  • Como usar nomes internos não exportados em código externo:

    O código compilado precisa exportar todos os símbolos a que o código não faz referência. A seção Compilação avançada e externas descreve como exportar símbolos.

  • Como usar nomes de strings para se referir a propriedades de objetos:

    O compilador renomeia propriedades no modo avançado, mas nunca renomeia strings.

      var x = { renamed_property: 1 };
      var y = x.renamed_property; // This is OK.
    
      // 'renamed_property' below doesn't exist on x after renaming, so the
      //  following evaluates to false.
      if ( 'renamed_property' in x ) {}; // BAD
    
      // The following also fails:
      x['renamed_property']; // BAD
    

    Se você precisar referenciar uma propriedade com uma string entre aspas, sempre use uma string entre aspas:

      var x = { 'unrenamed_property': 1 };
      x['unrenamed_property'];  // This is OK.
      if ( 'unrenamed_property' in x ) {};   // This is OK
    
  • Referência a variáveis como propriedades do objeto global:

    O compilador renomeia propriedades e variáveis de maneira independente. Por exemplo, o compilador trata as duas referências a seguir para foo de maneira diferente, mesmo que elas sejam equivalentes:

      var foo = {};
      window.foo; // BAD
    

    Este código pode ser compilado para:

      var a = {};
      window.b;
    

    Se você precisar fazer referência a uma variável como uma propriedade do objeto global, sempre faça referência a ela dessa maneira:

    window.foo = {}
    window.foo;
    

Implications of dead code elimination

The ADVANCED_OPTIMIZATIONS compilation level removes code that is never executed. This elimination of dead code makes the following practices dangerous:

  • Calling functions from outside of compiled code:

    When you compile functions without compiling the code that calls those functions, the Compiler assumes that the functions are never called and removes them. To avoid unwanted code removal, either:

    • compile all the JavaScript for your application together, or
    • export compiled functions.

    Advanced Compilation and Externs describes both of these approaches in greater detail.

  • Retrieving functions through iteration over constructor or prototype properties:

    To determine whether a function is dead code, the Compiler has to find all the calls to that function. By iterating over the properties of a constructor or its prototype you can find and call methods, but the Compiler can't identify the specific functions called in this manner.

    For example, the following code causes unintended code removal:

    function Coordinate() {
    }
    Coordinate.prototype.initX = function() {
      this.x = 0;
    }
    Coordinate.prototype.initY = function() {
      this.y = 0;
    }
    var coord = new Coordinate();
    for (method in Coordinate.prototype) {
      Coordinate.prototype[method].call(coord); // BAD
    }
        

    O compilador não entende que initX() e initY() são chamados no loop for e, portanto, remove esses dois métodos.

    Se você transmitir uma função como um parâmetro, o compilador poderá encontrar chamadas para esse parâmetro. Por exemplo, o compilador não remove a função getHello() ao compilar o código a seguir no modo avançado.

    function alertF(f) {
      alert(f());
    }
    function getHello() {
      return 'hello';
    }
    // The Compiler figures out that this call to alertF also calls getHello().
    alertF(getHello); // This is OK.
        

Implicações da separação de propriedades de objetos

No modo avançado, o compilador recolhe propriedades de objeto para se preparar para o encurtamento de nomes. Por exemplo, o compilador transforma isso:

   var foo = {};
   foo.bar = function (a) { alert(a) };
   foo.bar("hello");

neste:

   var foo$bar = function (a) { alert(a) };
   foo$bar("hello");

Essa separação de propriedade permite que o cartão de renomeação posterior seja renomeado com mais eficiência. O compilador pode substituir foo$bar por um único caractere, por exemplo.

No entanto, a separação de propriedades também torna estas práticas perigosas:

  • Como usar this fora dos construtores e métodos de protótipo:

    A nivelamento de propriedade pode mudar o significado da palavra-chave this em uma função. Exemplo:

       var foo = {};
       foo.bar = function (a) { this.bad = a; }; // BAD
       foo.bar("hello");
    

    se torna:

       var foo$bar = function (a) { this.bad = a; };
       foo$bar("hello");
    

    Antes da transformação, o this em foo.bar se refere a foo. Após a transformação, this refere-se ao this global. Em casos como este, o compilador produz este aviso:

    "WARNING - dangerous use of this in static method foo.bar"

    Para evitar que a separação de propriedades divida suas referências para this, use apenas this em construtores e métodos de protótipos. O significado de this não é ambíguo quando você chama um construtor com a palavra-chave new ou em uma função que é uma propriedade de um prototype.

  • Usar métodos estáticos sem saber em qual classe eles são chamados:

    Por exemplo, se você tiver:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    
    o compilador recolherá os dois métodos create (após a transcompilação de ES6 para ES5) para que a chamada de cls.create() falhe. Evite isso com a anotação @nocollapse:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    

  • Como usar o super em um método estático sem conhecer a superclasse:

    O código a seguir é seguro, porque o compilador sabe que super.sayHi() se refere a Parent.sayHi():

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends Parent {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    No entanto, a separação de propriedades vai interromper o seguinte código, mesmo que myMixin(Parent).sayHi seja igual a Parent.sayHi não compilado:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends myMixin(Parent) {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    Evite essa interrupção com a anotação /** @nocollapse */.

  • Usando get.setters/setters ou objetos Object.defineProperties:

    O compilador não entende bem essas construções. O getter e os setters do ES6 são transformados em Object.defineProperties(...) pela transpilação. Atualmente, o compilador não pode analisar esta versão de forma estática e presume que o acesso e o conjunto de propriedades não têm efeitos colaterais. Isso pode ter repercussões perigosas. Exemplo:

    class C {
      static get someProperty() {
        console.log("hello getters!");
      }
    }
    var unused = C.someProperty;
    

    É compilado para:

    C = function() {};
    Object.defineProperties(C, {a: // Note someProperty is also renamed to 'a'.
      {configurable:!0, enumerable:!0, get:function() {
        console.log("hello world");
        return 1;
    }}});
    

    Determinamos que C.someProperty não tinha efeitos colaterais. Por isso, ele foi removido.