Carregamento de script moderno

Passar o código certo para cada navegador não é tarefa fácil.

Neste artigo, consideraremos várias opções de como esse problema pode ser resolvido.



A passagem de código moderno por um navegador moderno pode melhorar muito o desempenho. Seus pacotes JavaScript poderão conter sintaxe moderna mais compacta ou otimizada e oferecer suporte a navegadores mais antigos.

Entre as ferramentas para desenvolvedores, predomina o padrão módulo / nomodule de carregamento declarativo de código moderno ou legado, que fornece fontes aos navegadores e permite que você decida quais usar:

<script type="module" src="/modern.js"></script> <script nomodule src="/legacy.js"></script> 

Infelizmente, nem tudo é tão simples. A abordagem HTML mostrada acima aciona uma recarga de script no Edge e Safari .

O que pode ser feito?


Dependendo do navegador, precisamos oferecer uma das opções para scripts compilados, mas alguns navegadores antigos não suportam toda a sintaxe necessária para isso.

Em primeiro lugar, existe o Safari Fix . O Safari 10.1 suporta módulos JS, não o atributo nomodule nos scripts, o que permite executar o código moderno e o legado. No entanto, o evento de beforeload não padrão suportado pelo Safari 10 e 11 pode ser usado para o preenchimento do nomodule .

Método 1: Download dinâmico


Você pode solucionar esses problemas implementando um pequeno carregador de scripts. Semelhante à forma como o LoadCSS funciona. Em vez de esperar pela implementação dos módulos ES e do atributo nomodule nos nomodule , você pode tentar executar um script de módulo como um "teste com um teste decisivo" e, com base no resultado, optar por baixar um código moderno ou legado.

 <!-- use a module script to detect modern browsers: --> <script type="module"> self.modern = true </script> <!-- now use that flag to load modern VS legacy code: --> <script> addEventListener('load', function() { var s = document.createElement('script') if ('noModule' in s) { // notice the casing s.type = 'module' s.src = '/modern.js' } else { s.src = '/legacy.js' } document.head.appendChild(s) }) </script> 

Mas com essa abordagem, você deve aguardar a conclusão do script do módulo "litmus" antes de implementar o script correto. Isso acontece porque <sript type="module"> sempre funciona de forma assíncrona. Mas existe uma maneira melhor!

Você pode implementar a opção independente, verificando se o nomodule é nomodule no navegador. Isso significa que consideraremos navegadores como o Safari 10.1 como obsoletos, mesmo que eles suportem módulos. Mas poderia ser o melhor . Aqui está o código relevante:

 var s = document.createElement('script') if ('noModule' in s) { // notice the casing s.type = 'module' s.src = '/modern.js' } else s.src = '/legacy.js' } document.head.appendChild(s) 

Isso pode ser rapidamente transformado em uma função que carrega código moderno ou legado e também fornece carregamento assíncrono deles:

 <script> $loadjs("/modern.js","/legacy.js") function $loadjs(src,fallback,s) { s = document.createElement('script') if ('noModule' in s) s.type = 'module', s.src = src else s.async = true, s.src = fallback document.head.appendChild(s) } </script> 

Qual é o compromisso aqui?

Pré-carregamento

Como a solução é totalmente dinâmica, o navegador não poderá detectar nossos recursos JavaScript até que inicie o código de inicialização que escrevemos para inserir scripts modernos ou legados. Normalmente, um navegador verifica se há recursos que podem ser baixados com antecedência em HTML. Esse problema foi resolvido, mas não de maneira ideal: você pode pré-carregar a versão moderna do pacote em navegadores modernos usando <link rl=modulpreload> .

Infelizmente, até o momento, apenas o Chrome suporta o modulepreload .

 <link rel="modulepreload" href="/modern.js"> <script type="module">self.modern=1</script> <!-- etc --> 

Se essa técnica for adequada para você, você poderá reduzir o tamanho do documento HTML no qual incorporar esses scripts. Se sua carga útil for pequena, como uma tela inicial ou um código de download do aplicativo cliente, é improvável que a remoção do scanner de pré-carregamento afete o desempenho. E se você desenhar muito HTML importante no servidor para enviar aos navegadores, o scanner de pré-carregamento será útil e a abordagem descrita não será a melhor opção para você.

Veja como esta solução pode estar em uso:

 <link rel="modulepreload" href="/modern.js"> <script type="module">self.modern=1</script> <script> $loadjs("/modern.js","/legacy.js") function $loadjs(e,d,c){c=document.createElement("script"),self.modern?(c.src=e,c.type="module"):c.src=d,document.head.appendChild(c)} </script> 

Também deve ser observado que a lista de navegadores que suportam módulos JS é quase a mesma que aqueles que suportam <link rl=preload> . Para alguns sites, pode ser apropriado usar <link rl=preload as=script crossorigin> vez de modulepreload . O desempenho pode deteriorar porque o pré-carregamento de script clássico não implica uma análise uniforme ao longo do tempo, como é o caso do modulepreload .

Método 2: rastrear agente do usuário


Não tenho um exemplo de código adequado, pois o rastreamento do User Agent é uma tarefa não trivial. Mas então você pode ler o excelente artigo na Revista Smashing.

De fato, tudo começa com o mesmo <scrit src=bundle.js> em HTML para todos os navegadores. Quando bundle.js é solicitado, o servidor analisa a sequência do User Agent do navegador solicitante e seleciona qual JavaScript retornar - moderno ou herdado, dependendo de como o navegador foi reconhecido.

A abordagem é universal, mas traz sérias conseqüências:

  • Como servidores inteligentes são necessários, essa abordagem não funcionará sob condições de implantação estática (geradores de sites estáticos, Netlify etc.).
  • O cache desses URLs JavaScript agora depende do User Agent, que é muito volátil.
  • A definição de UA é difícil e pode levar a uma classificação falsa.
  • A linha User Agent é fácil de falsificar, e novos UAs aparecem todos os dias.

Uma maneira de contornar essas limitações é combinar o padrão de módulo / nó com a diferenciação do User Agent para evitar o envio de várias versões do pacote para o mesmo endereço. Essa abordagem reduz a capacidade de cache da página, mas fornece um pré-carregamento eficiente: o servidor gerador de HTML sabe quando usar o modulepreload e quando preload .

 function renderPage(request, response) { let html = `<html><head>...`; const agent = request.headers.userAgent; const isModern = userAgent.isModern(agent); if (isModern) { html += ` <link rel="modulepreload" href="modern.mjs"> <script type="module" src="modern.mjs"></script> `; } else { html += ` <link rel="preload" as="script" href="legacy.js"> <script src="legacy.js"></script> `; } response.end(html); } 

Para sites que já geram HTML no servidor em resposta a cada solicitação, isso pode ser uma transição eficaz para o download de scripts modernos.

Método três: bons navegadores antigos


O efeito negativo do padrão módulo / nomodule é visível em versões mais antigas do Chrome, Firefox e Safari - seu número é muito pequeno, porque os navegadores são atualizados automaticamente. Com o Edge 16-18, a situação é diferente, mas há esperança: novas versões do Edge usarão o mecanismo de renderização baseado no Chromium, que não apresenta esses problemas.

Para alguns aplicativos, esse seria um compromisso ideal: baixe a versão moderna do código em 90% dos navegadores e forneça o código herdado para os antigos. A carga nos navegadores antigos aumentará.

A propósito, nenhum dos agentes de usuários para os quais essa reinicialização é um problema ocupa uma parcela significativa do mercado móvel. Portanto, é improvável que a fonte de todos esses bytes extras sejam dispositivos móveis ou dispositivos com um processador fraco.

Se você estiver criando um site que é acessado principalmente por navegadores móveis ou novos, para a maioria desses usuários, o tipo mais simples de padrão de módulo / nó é adequado. Certifique-se de adicionar a correção Safari 10.1 se dispositivos iOS mais antigos chegarem até você.

    iOS-. <!-- polyfill `nomodule` in Safari 10.1: --> <script type="module"> !function(e,t,n){!("noModule"in(t=e.createElement("script")))&&"onbeforeload"in t&&(n=!1,e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove())}(document) </script> <!-- 90+% of browsers: --> <script src="modern.js" type="module'></script> <!-- IE, Edge <16, Safari <10.1, old desktop: --> <script src="legacy.js" nomodule async defer></script> 

Método 4: Aplicar termos do pacote


Uma boa solução seria usar o nomodule para baixar condicionalmente pacotes com código que não é necessário em navegadores modernos, como polyfills. Com essa abordagem, no pior caso, o polyfill será carregado ou executado (no Safari 10.1), mas o efeito disso será limitado ao "re-polyfilling". Considerando que hoje a abordagem com o download e a execução de polyfills em todos os navegadores prevalece, isso pode ser uma melhoria digna.

 <!-- newer browsers will not load this bundle: --> <script nomodule src="polyfills.js"></script> <!-- all browsers load this one: --> <script src="/bundle.js"></script> 

Você pode configurar a CLI Angular para usar essa abordagem com polyfills, como Minko Gachev demonstrou . Tendo aprendido sobre essa abordagem, percebi que você pode ativar a injeção automática de polyfill no preact-cli - este PR demonstra como é fácil implementar essa técnica.

E se você usa o WebPack, existe um plug html-webpack-plugin in conveniente para o html-webpack-plugin , que facilita a adição de um nomodule aos pacotes com polyfills.

Então o que escolher?


A resposta depende da sua situação. Se você estiver criando um aplicativo cliente e seu HTML contiver um pouco mais que <sript> , poderá ser necessário o primeiro método .

Se você estiver criando um site renderizado no servidor e puder pagar em cache, o segundo método poderá ser adequado para você .

Se você usar a renderização universal , o ganho de desempenho oferecido pela digitalização de pré-carregamento pode ser muito importante. Portanto, preste atenção ao terceiro ou quarto métodos. Escolha o que combina com sua arquitetura.

Pessoalmente, eu escolho, focando na duração da análise em dispositivos móveis, e não no custo do download nas versões para desktop. Os usuários móveis percebem os custos de análise e transferência de dados como despesas reais (consumo de bateria e taxas de transferência de dados), enquanto os usuários de desktop não têm essas restrições. Também procedo da otimização para 90% dos usuários - o principal público de meus projetos usa navegadores modernos e / ou móveis.

O que ler


Deseja saber mais sobre este tópico? Você pode começar por aqui:

Source: https://habr.com/ru/post/pt473994/


All Articles