TL;DR
Use o FeatureTests.io para realizar testes de recursos do ES6+. Os resultados desses testes são armazenados em cache por padrão no navegador do usuário e compartilhados em todos os sites que o usuário visita e que usam esse serviço.
No bootstrapper do seu site/aplicativo, verifique os resultados desses testes de recursos para decidir quais arquivos são apropriados para carregar.
Se os testes forem aprovados, o senhor poderá carregar sua fonte original *.es6.js
e saber que eles funcionarão de forma nativa e com bom desempenho nesse navegador. Se algum teste falhar, o senhor poderá voltar a carregar os arquivos pré-transpilados da etapa de compilação *.es5.js
do seu código.
Use a mesma lógica de verificação para decidir se o navegador do usuário precisa de uma grande biblioteca de shim (como ES6-Shim) ou se o navegador não precisar de nenhum (ou apenas alguns) dos polyfills da API.
Essencialmente: carregar somente o código necessárioe carregue a melhor e mais nativa versão dele que o navegador pode suportar.
O problema
Se estiver usando algum código ES6+ em seus aplicativos, é provável que esteja usando um transpilador como o Babel ou talvez Traceur. Essas ferramentas são fantásticas e bastante capazes de produzir versões transpiladas de seu código ES6+ que podem ser executadas em navegadores ES5+ (a grande maioria).
No entanto, há uma nuance que está sendo amplamente ignorada, e o objetivo desta postagem é trazê-la à tona como motivação para um novo serviço que lancei para ajudar a resolver essa preocupação: FeatureTests.io.
Permita-me fazer esta pergunta/cenário retórico para talvez ilustrar minha preocupação:
Vamos supor que o TC39 continue adicionando novos e incríveis recursos à especificação da linguagem. Mas por que os navegadores precisam implementar qualquer um desses recursos? Não poderíamos sempre contar com transpiladores, para sempre, e não poderíamos sempre e somente servir esses arquivos transpilados para o navegador?
Nesse caso, isso não significaria que esses recursos nunca precisariam entrar em um navegador? A especificação ES poderia se tornar apenas uma especificação de transpilador, certo?
…
Se o senhor refletir sobre esse cenário por apenas um ou dois instantes, é provável que várias preocupações saltem aos olhos. A principal delas é que o senhor provavelmente percebe que o código transpilado produzido é maior e talvez mais lento (se não agora, certamente mais tarde, quando os navegadores tiverem a chance de otimizar as implementações de recursos nativos). Também é necessário enviar dezenas de kb de código polyfill para corrigir o espaço da API no navegador.
Tudo isso funciona, mas não é o ideal. O melhor código que o senhor pode fornecer ao navegador de cada usuário é o menor, o mais rápido e o mais bem adaptado que puder fornecer na prática. Certo?
O problema é o seguinte: se o senhor usar apenas um transpilador de etapa de compilação e sempre servir incondicionalmente esse código transpilado equivalente ao ES5, nunca estará realmente usando nenhuma das implementações de recursos nativos. O senhor estará sempre e para sempre usando o código transpilado mais antigo, maior e (talvez) mais lento.
Por enquanto, embora o suporte a ES6 em navegadores pareça permanecer nas porcentagens mais baixas, isso pode não parecer um grande problema. Mas o senhor já pensou em quanto do ES6 seu aplicativo/site está usando (ou usará em breve)?
Meu palpite é que a maioria dos sites usará de 20 a 30% dos recursos do ES6 de forma generalizada. E a maioria deles, se não todos, já está implementada em praticamente todas as versões mais recentes dos navegadores. Além disso, o novo navegador Microsoft Edge já tem 81% de suporte ao ES6 (no momento em que este artigo foi escrito), e o FF/Chrome, com ~50-60%, vai alcançá-lo rapidamente.
Não demorará muito para que uma parte significativa dos seus usuários tenha suporte total ao ES6 para todos os recursos que seu site/aplicativo usa ou que praticamente usará em um futuro próximo.
O senhor não quer atender a cada usuário com o melhor código possível?
A solução
Em primeiro lugar, continue transpilando seu código usando sua(s) ferramenta(s) favorita(s). Continue fazendo isso em uma etapa de compilação.
Quando o senhor for implantar o .js
para o diretório exposto na Web que pode ser carregado no navegador, inclua os arquivos de origem originais (ES6+), bem como esses arquivos transpilados. Além disso, não se esqueça de incluir os polyfills conforme necessário. Por exemplo, o senhor pode nomeá-los como *.es6.js
(fonte original) e *.es5.js
(transpilado) para mantê-los em ordem. Ou o senhor pode usar subdiretórios es6/
e es5/
para organizá-los. O senhor entendeu o ponto, tenho certeza.
Agora, como o senhor decide, quando seu site/aplicativo for carregado pela primeira vez, qual conjunto de arquivos é apropriado para carregar no navegador de cada usuário?
O senhor precisa de um bootstrapper que carregue primeiro, logo no início. Por exemplo, o senhor envia uma página HTML com um único <script>
e ela inclui código inline ou uma referência a um único .js
único. Muitos sites/aplicativos de qualquer complexidade já fazem isso de uma forma ou de outra. É bastante comum carregar um pequeno bootstrapper que, em seguida, configura e carrega o restante do aplicativo.
Se o senhor ainda não tem uma técnica como essa, não é nada difícil de fazer, e há muitos benefícios que obterá, inclusive a capacidade de carregar condicionalmente as versões apropriadas dos arquivos para cada navegador, como explicarei daqui a pouco. Na verdade, isso não é tão intimidador quanto parece.
Agora, no seu bootstrapper (seja qual for a configuração do seu), como o senhor vai decidir quais arquivos carregar?
O senhor precisa fazer um teste de recursos essa instância do navegador para decidir quais são seus recursos. Se todos os recursos de que o senhor precisa forem compatíveis, carregue o *.es6.js
. Se alguns estiverem faltando, carregue os polyfills e o arquivo *.es5.js
.
É isso aí. É verdade. Não, realmente, é só isso que estou sugerindo.
Teste de recursos ES6
O teste de recursos para APIs é fácil. Tenho certeza de que o senhor provavelmente sabe como fazer coisas como:
if (Number.isNaN) { numberIsNaN = true; } else { numberIsNaN = false; }
Mas e quanto à sintaxe, como detectar se o navegador suporta =>
funções de seta ou o let
declarações de escopo de bloco?
Isso é mais difícil, porque isso não funciona da maneira que esperamos:
try { x = y => y; arrows = true; } catch (err) { arrows = false; }
A sintaxe falha na compilação do JS (em navegadores compatíveis com o pré-ES6) antes de tentar ser executada, portanto, o try..catch
não consegue capturá-la. A solução? Adiar a compilação.
try { new Function( "(y => y)" ); arrows = true; } catch (err) { arrows = false; }
O new Function(..)
compila o código fornecido em tempo de execução, portanto, qualquer erro de compilação pode ser detectado pelo seu try..catch
.
Ótimo, problema resolvido.
Mas o senhor deseja desenvolver pessoalmente testes de recursos para todos os diferentes recursos do ES6+ que planeja usar? E alguns deles podem ser um pouco dolorosos (lentos) de executar (como para TCO), então o senhor realmente quer fazer isso? Não seria melhor executar os testes em uma thread de Web Worker em segundo plano para minimizar qualquer impacto no desempenho da thread principal da interface do usuário?
E mesmo que o senhor tenha se dado a todo esse trabalho, o senhor realmente precisa executar todos esses testes toda vez que uma de suas páginas for carregada? Os navegadores não adicionam novos recursos a cada minuto. Normalmente, o navegador de um usuário pode ser atualizado, no máximo, a cada duas semanas, talvez meses. O senhor não poderia executar os testes uma vez e armazenar os resultados em cache por algum tempo?
Mas se esses resultados em cache estiverem disponíveis apenas para o seu site, se o usuário visitar outros sites orientados por ES6, cada um deles precisará executar novamente seu próprio conjunto de testes. Não seria melhor se os resultados dos testes pudessem ser armazenados em cache “globalmente” no navegador do usuário, de modo que qualquer site pudesse simplesmente usar o true
/ false
os resultados dos testes sem precisar executar todos os testes novamente?
Ou deixe-me inverter a situação: não seria bom se o usuário chegasse ao seu site e os resultados já estivessem armazenados em cache (por uma visita a outro site), de modo que ele não precisasse esperar que seu site os executasse e, portanto, seu site carregasse mais rápido para ele?
Todas essas razões (e mais) são o motivo pelo qual construí o ES Feature Tests como um serviço: FeatureTests.io.
Esse serviço fornece um arquivo de biblioteca https://featuretests.io/rs.js que faz todo o trabalho a que me referi acima para o senhor. O senhor pode solicitar esse arquivo de biblioteca antes de ou como seu bootstrapper é carregado e, em seguida, basta verificar os resultados dos testes (que são carregados do cache ou executados automaticamente) com um simples if
simples.
Por exemplo, para testar se o seu let
e =>
usando arquivos que podem ser carregados, isso é o que o senhor faria em seu bootstrapper:
window["Reflect.supports"]( "all", function(results){ if (results.letConst && results.arrow) { // load `*.es6.js` files } else { // load already pre-transpiled `*.es5.js` files } } );
Se o seu site ainda não tiver armazenado em cache os resultados para esse usuário, a biblioteca se comunica entre domínios (via <iframe>
do seu site para o featuretests.io
) para que os resultados do teste possam ser armazenados ou recuperados “globalmente” nesse navegador.
Se os testes precisarem ser executados, ele aciona um Web Worker para fazer os testes fora do thread. Ele até tenta usar um Web Worker compartilhado, de modo que, se o usuário estiver carregando simultaneamente mais de dois sites que usam o serviço, ambos usarão a mesma instância de worker.
Toda essa lógica o senhor obtém automaticamente ao usar este gratuito serviço.
É isso aí! É tudo o que é preciso para começar a usar o carregamento dividido condicional do código do seu site/aplicativo com base em testes de recursos ES6 no navegador.
Material avançado
A biblioteca por trás deste site é de código aberto: es-feature-tests. Também é disponível no npm.
Se quiser, o senhor pode incorporar os testes da biblioteca em seu próprio código bootstrapper e deixar de usar o FeatureTests.io. Com isso, o senhor perde os benefícios do cache compartilhado e tudo o mais, mas ainda assim não precisa criar seus próprios testes.
Ou, o serviço oferece um Ponto de extremidade da API que retorna os testes em forma de texto, para que o senhor possa recuperá-los em seu servidor durante a etapa de compilação e, em seguida, incluir e executar esses testes em seu próprio código.
O pacote npm é compatível com Node/iojs, portanto, o senhor pode executar exatamente o mesmo tipo de teste de recursos para carregamento dividido dentro de seus programas Node, como:
var ReflectSupports = require("es-feature-tests"); ReflectSupports( "all", function(results){ if (results.letConst && results.arrow) { // require(..) `*.es6.js` modules } else { // require(..) already pre-transpiled // `*.es5.js` modules } } );
De quais resultados de teste meu código precisa?
Como afirmei anteriormente, o senhor provavelmente não precisará verificar cada resultado de teste, pois provavelmente não usará 100% de todos os recursos do ES6+.
Mas, ao acompanhar constantemente os resultados dos testes, sua if
deve verificar pode ser tedioso e propenso a erros. O senhor se lembra se alguém já usou um let
em seu código ou não?
O pacote “es-feature-tests” inclui uma ferramenta CLI chamada testify
que pode examinar arquivos ou diretórios do seu código de autoria do ES6 e produzir automaticamente a lógica de verificação equivalente para o senhor. Por exemplo:
$> bin/testify --dir=/path/to/es6-code/ function checkFeatureTests(testResults){return testResults.letConst&&testResults.arrow}
Aviso: No momento da redação deste texto, este testify
é extremamente hacker e WiP. Eventualmente, ela fará uma análise total e completa, mas, por enquanto, é muito difícil. Fique atento a mais atualizações sobre essa ferramenta em breve!
O senhor pode usar testify
em seu processo de compilação (antes da transpilação, provavelmente) para examinar seus arquivos de origem ES6 e produzir esse checkFeatureTests(..)
que verifica todos os resultados de teste de que seu código precisa.
Agora, o senhor inclui esse código em linha com o bootstrapper, de modo que agora ele é lido:
// .. function checkFeatureTests(testResults){return testResults.letConst&&testResults.arrow} window["Reflect.supports"]( "all", function(results){ if (checkFeatureTests(results)) { // load `*.es6.js` files } else { // load already pre-transpiled `*.es5.js` files } } ); // ..
Essa ferramenta CLI de etapa de compilação fará com que seus testes estejam sempre ajustados ao código que você escreveu, automaticamente, o que permite definir e esquecer em termos de garantir que o código do seu site/aplicativo seja sempre carregado na melhor versão possível para cada navegador.
Resumo
Quero que o senhor escreva código ES6 e quero que comece a fazê-lo hoje mesmo. Escrevi um livro sobre ES6 para ajudar o senhor a aprendê-lo: O senhor não conhece JS: ES6 & Beyond, que o senhor pode ler gratuitamente on-line ou comprar na O’Reilly ou em outras livrarias.
No entanto, quero que o senhor seja responsável e otimize a forma como envia seu código ES6 ou o código transpilado para os navegadores dos usuários. Quero que todos nós nos beneficiemos do incrível trabalho que os navegadores estão fazendo para implementar esses recursos nativamente.
Carregar o melhor código para cada navegador – nem mais, nem menos. Espero que sim FeatureTests.io ajuda o senhor a atingir esse objetivo.
Feliz ES6!
Sobre Kyle Simpson
Kyle Simpson é um engenheiro de software orientado para a Web, amplamente aclamado por sua série de livros “You Don’t Know JS” e por quase 1 milhão de horas vistas de seus cursos on-line. O superpoder de Kyle é fazer perguntas melhores, e ele acredita profundamente no uso máximo das ferramentas minimamente necessárias para qualquer tarefa. Como um “tecnólogo centrado no ser humano”, ele é apaixonado por unir humanos e tecnologia, desenvolvendo organizações de engenharia para resolver os problemas certos, de maneiras mais simples. Kyle sempre lutará pelas pessoas por trás dos pixels.