O que é a compilação JIT e AOT no Angular?

AOT e JIT

Neste post falaremos sobre a compilação JIT (Just In Time) e AOT (Ahead of Time) e o ganho potencial de usar AOT em vez de JIT com o Angular.

JIT

A compilação JIT é uma combinação de duas abordagens tradicionais de tradução para código de máquina: anterior à execução e durante a execução (interpretação), portanto combina algumas vantagens e desvantagens de ambas. A grosso modo, a compilação JIT combina a velocidade do código compilado à flexibilidade da interpretação, com o gargalo do interpretador e da compilação. A compilação dinâmica permite otimizações que não são possíveis em compilações estáticas, levando em alguns casos a códigos mais rápidos.

A primeira versão do Angular, chamada de AngularJS apresentava apenas a opção de compilação JIT, onde o código JavaScript é compilado e interpretado no browser do cliente.

Mas o JavaScript não compila como outras linguagens estritamente tipadas, certo?

Dizer que não há “tempo de compilação” é um dos mitos associados ao JavaScript. A compilação ocorre imediatamente após o código ser carregado integralmente no navegador, mas antes que o conteúdo seja renderizado, fazendo com que usuários esperem um pouco mais pela operação.

O JIT é necessário para os navegadores porque eles próprios não processam códigos binários como se fossem servidos. Assim, uma vez que carregados o(s) arquivo(s) JavaScript, eles são compilados para o binário correspondente, que por sua vez são interpretados e executados pelo navegador.

AOT

O processo AOT compila uma linguagem de alto nível (C, C++, etc.) ou um bytecode (código intermediário. Ex: Java) em código de maquina nativo (dependente do sistema) para que o arquivo binário resultante, seja executado nativamente, ajudando a eliminar sobrecargas de compilação durante a execução. Outros exemplos são Android Runtime, OpenJDK e o próprio Angular.

A compilação ahead of time (a frente do tempo), por outro lado, não é muito diferente do JIT, exceto pelo momento de compilação do código. Mas em se tratando de Angular, o ponto chave é onde ocorre a compilação.

JIT e AOT no Angular versão 2 ou superior

No Angular, o processo AOT ocorre na compilação do TypeScript em JavaScript. E, posteriormente a interpretação no cliente, rodando como JIT.

Essencialmente, estamos compilando o código duas vezes com aplicativos Angular (versão 2 em diante), uma vez quando convertemos TS para JS e, em seguida, quando o navegador converte JS em binário.

Embora não possamos controlar o último, podemos controlar quando a compilação de TS para JS é executada.

Mas é importante fazer o uso do AOT para gerar códigos JavaScript otimizados que no final das contas irá demandar menos do navegador cliente e portanto menos JIT. Não menos importante, mas fora do escopo deste post, o AOT gera conteúdo mais seguro e otimizado, trazendo mais ganho a solução.

Servir e Construir – com AOT e sem o AOT

A CLI Angular vem com suporte incorporado ao AOT. No ambiente de desenvolvimento, ele usa a compilação JIT para melhorar as experiências dos desenvolvedores e recarregá-las mais rapidamente. No ambiente de produção, a compilação AOT é usada para melhor experiência do usuário e carregamentos de página mais rápidos. Para definir o destino da compilação adiciona-se o argumento –dev ou –prod aos comandos ng.

Veja o exemplo abaixo de um projeto e a compilação:

npm install -g @angular/cli
ng set packageManager yarn
ng new teste-aot
cd teste-aot
yarn add spectre.css
ng generate module bttf
ng generate component bttf
ng generate service bttf/bttf

// construindo
ng build

Se analisarmos o diretório “dist” veremos os artefatos gerados.

Agora, gerando com a diretiva de produção:

ng build --prod

Analisando agora os mesmos arquivos vemos uma grande mudança nos tamanhos dos arquivos. Com a construção do desenvolvimento, o vendor.bundle.js tinha 1,88 MB de tamanho e o main.bundle.js era de 7,51 KB. Na versão de produção, vendor.bundle.js é reduzido para 1.07MB e main.bundle.js cresce para 23.6kB.

E porque isso? Na construção da produção, os arquivos JavaScript são minificados pelo webpack. As estatísticas do webpack imprimem todos os tamanhos de arquivo para arquivos ES5 não-compactados e não compactados – portanto, a classificação não conta nas estatísticas acima!

Versões mais nova do Angular tiveram melhorias neste procedimento e portanto mais ganho de performance.

Para compilação AOT, precisamos de algumas ferramentas para realizá-lo automaticamente em nosso processo de criação. Atualmente existem duas soluções sólidas. Ambos os métodos funcionam, mas servem a propósitos diferentes e têm diferentes vantagens e desvantagens. Vejamos:

Solução 1: ferramenta de linha de comando ngc

A ferramenta de linha de comando ngc vem com o pacote @angular/compiler-cli. É um wrapper em torno do compilador de Typescript (tsc). Pode-se especificar os arquivos a serem compilados em tsconfig.json com os arquivos e excluir o campo. Opções específicas do compilador podem ser colocadas dentro da propriedade angularCompilerOptions.

Ao executar o ngc, ele procura o módulo de entrada (fornece o contexto para a compilação) e os componentes, diretivas e pipes correspondentes. Para cada um deles, ele compila e gera um arquivo .cript sufixado .ngfactory.ts, onde os modelos compilados residem. Os arquivos compilados são gerados ao lado do arquivo original por padrão. O destino pode ser modificado por genDir dentro de angularCompilerOptions.

Ele também gera os arquivos compilados de Typescript (mensionados no início) para Javascript, além dos arquivos de fábrica compilados. Esses arquivos representam os arquivos Typescript não compilados com a descrição das classes originais ao lado deles em um arquivo com sufixo .metadata.json. Esses arquivos não são necessários para executar o aplicativo no modo AOT. Eles só são úteis quando se constrói uma biblioteca Angular que suporta compilação AOT.

Para usar os arquivos recém-gerados, teremos que alterar o bootstrap do aplicativo.

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { MainModule } from './modules/main.module';

platformBrowserDynamic().bootstrapModule(MainModule);

Precisamos mudar o arquivo do módulo para o .ngfactory.ts com o sufixo
e também importar o bootstrap de @angular/platform-browser. O @angular/navegador de plataforma não inclui o compilador, obtendo um enorme ganho no tamanho do arquivo.

import { platformBrowser } from '@angular/platform-browser';
import { MainModuleNgFactory } from './modules/main.module.ngfactory';

platformBrowser().bootstrapModuleFactory(MainModuleNgFactory);

Depois de gerar os arquivos de Typescript compilados do AOT, é necessária outra etapa na qual agrupamos o aplicativo.

Vantagens

  • Pode sempre ser usado com a versão mais recente do Angular logo após ser lançado;
  • Após a compilação, qualquer tipo de ferramenta de agrupamento pode ser usado;
  • Gera arquivos de metadados para o desenvolvimento de bibliotecas.

Desvantagens

  • Suporta apenas HTML em modelos e CSS em estilos;
  • Ainda não há modo de exibição;
  • Precisa manter a versão AOT do arquivo de inicialização.

Solução 2: plugin @ngtools/webpack

O próximo pacote @ngtools/webpack é um plugin para o Webpack 2 publicado como parte do repositório CLI Angular. Ele fornece um carregador e um plug-in para a configuração.

'use strict';
let path = require('path');
let AotPlugin = require('@ngtools/webpack').AotPlugin;

module.exports = {
  module: {
    rules: [
      { test: /\.ts/, use: '@ngtools/webpack' }
    ]
  },
  
  plugins: [
    new AotPlugin({
      tsConfigPath: path.join(process.cwd(), 'tsconfig.json'),
      entryModule: path.join(process.cwd(), 'src/app/modules/main.module#MainModule')
    })
  ]
};

O plug-in precisa do local da configuração do Typescript e do módulo de entrada do aplicativo. A propriedade entryModule consiste no caminho do arquivo e na classe do módulo exportado dividida por um hashmark. Com estes, pode executar o compilador AOT e gerar os arquivos de fábrica.

Uma grande diferença aqui é que ela não transfere a fábrica, os metadados e os arquivos JavaScript JIT para o sistema de arquivos. Ele só reunirá o aplicativo com base nos arquivos de fábrica, que só existem dentro do sistema de arquivos de memória do Webpack. Ele também procura o ponto de entrada e transforma o arquivo de inicialização automaticamente para se tornar adequado para arquivos compilados AOT.

O carregador dentro da propriedade de regras permite usar qualquer tipo de arquivo dentro da propriedade templateUrl e styleUrls do decorador do componente. Por exemplo, SCSS, LESS para folhas de estilo e PUG para modelos.

Ele substitui as URLs relativas em templateUrl e styleUrls sem exigir instruções. Também módulos carregados preguiçosos em rotas compiladas para instruções de importação e os sub-módulos são também AOT compilados com a aplicação principal.

import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: 'lazy', loadChildren: './lazy.module#LazyModule' }
];

Este carregador faz basicamente o trabalho do awesome-typescript-loader + angular2-template-loader + angular-roteador-loader e adiciona a compilação AOT à cadeia.

Vantagens

  • Tipos de arquivos personalizados disponíveis para modelos e estilos através de carregadores Webpack (SCSS, PUG,…);
  • Nenhum processo separado para compilação;
  • Modo de observação para arquivos compilados AOT;
  • Não há necessidade de manter a versão AOT do arquivo de inicialização;
  • Nenhuma saída para o disco para arquivos * .ngfactory.ts separados.

Desvantagens

  • Só pode ser usado com o Webpack 2;
  • Precisa esperar por novas versões após o lançamento Angular para o repositório Angular CLI;
  • Compatível com a versão atual;
  • Não é bom para publicação de pacote compatível com AOT, porque não gera arquivos compilados separados.

Solução 3: plugin @ultimate/aot-loader

Um novo plugin Webpack 2 (atualmente em beta) apoiado pela equipe do Ultimate Angular. Ele fornece um carregador e um plug-in para a configuração.

'use strict';
let path = require('path');
let AotPlugin = require('@ultimate/aot-loader').AotPlugin;

module.exports = {
  module: {
    rules: [
      { test: /\.ts/, use: '@ultimate/aot-loader' }
    ]
  },
  
  plugins: [
    new AotPlugin({
      tsConfig: path.join(process.cwd(), 'tsconfig.json'),
      entryModule: path.join(process.cwd(), 'src/app/modules/main.module#MainModule')
    })
  ]
};

O @ultimate/aot-loader é muito semelhante em configuração e em habilidades para o plugin CLI Angular. Os arquivos de fábrica e de metadados são gerados apenas para o sistema de arquivos de memória. O ponto de entrada também é transformado para carregar os arquivos de fábrica, em vez dos regulares. Os módulos carregados com preguiça são transpostos e divididos em diferentes partes, além do pacote principal.

Carregadores dentro de regras nos permitem escrever modelos e estilos de componentes na extensão desejada (SCSS, PUG, etc.).

Vantagens

  • As mesmas vantagens do pacote Angular CLI;
  • Compatível com Angular 4 em diante.

Desvantagens

  • Só pode ser usado com o Webpack 2;
  • Não é bom para publicação de pacote compatível com AOT, porque não gera arquivos compilados separados.

Conclusão

Não importa qual solução você escolher, seu aplicativo pode se beneficiar muito da compilação AOT por meio de tamanho e velocidade. Ele reduz o tamanho de um aplicativo pequeno a médio e multiplica a velocidade de inicialização. Se você estiver usando apenas HTML para modelos e CSS para estilos ou usando um sistema de compilação diferente do Webpack ou desenvolvendo um pacote no Angular 2 (ou superior), o ngc pode ser um bom ajuste. Caso contrário, sugerimos ficar com o @ngtools/webpack ou com o plug-in @ultimate/aot-loader e com isso aproveitar os benefícios sobre a solução de linha de comando.

*** A OctalMind é uma empresa especializada no desenvolvimento de sistemas de alta tecnologia.