Desenvolvimento de JavaScript responsável, parte 2

Em abril deste ano, publicamos uma tradução do primeiro material de uma série dedicada a uma abordagem responsável ao desenvolvimento do JavaScript. Lá, o autor refletiu sobre as modernas tecnologias da Web e seu uso racional. Agora, oferecemos uma tradução do segundo artigo desta série. Ele é dedicado a alguns detalhes técnicos sobre o design de projetos da web.



Tenha uma ideia


Você e sua equipe promoveram entusiasticamente a idéia de uma revisão completa do site antigo da empresa. Seus pedidos chegaram à liderança e chegaram até à vista daqueles que estão no topo. Você recebeu uma luz verde. Sua equipe entusiasticamente começou a trabalhar, atraindo designers, redatores e outros especialistas. Logo você lançou um novo código.

O trabalho começou inocentemente. O comando npm install está aqui, o comando npm install está lá. Assim que você olhou em volta, as dependências de produção já estavam sendo estabelecidas como se o desenvolvimento do projeto fosse uma bebida selvagem e você foi quem não se importou com o que seria amanhã de manhã.

Então você começou.

Mas, ao contrário das consequências da festa mais louca de beber, a coisa terrível não começou na manhã seguinte. Infelizmente - não na manhã seguinte. O acerto de contas veio em meses. Ela assumiu a forma desagradável de leve náusea e dor de cabeça para proprietários de empresas e gerentes de nível médio que se perguntaram por que, após o lançamento do novo site, as conversões e as receitas caíram. Então o desastre ganhou força. Isso aconteceu quando o diretor técnico voltou do fim de semana, que passou em algum lugar fora da cidade. Ele se perguntou por que o site da empresa estava carregando tão lentamente (se é que havia) em seu telefone.

Costumava ser bom para todos. Agora outros tempos sombrios chegaram. Conheça sua primeira ressaca depois de consumir uma grande dose de JavaScript.

Isso não é culpa sua.


Enquanto você estava tentando lidar com uma ressaca infernal, palavras como “eu te disse” pareceriam uma repreensão bem merecida para você. E se você pudesse lutar naquele momento, eles poderiam servir como uma ocasião para uma luta.

Quando se trata das consequências do uso imprudente de JavaScript, você pode culpar tudo e todos. Mas procurar os culpados é uma perda de tempo. O próprio dispositivo da web moderno exige que as empresas resolvam problemas mais rapidamente do que seus concorrentes. Essa pressão significa que nós, nos esforçando para aumentar nossa produtividade o máximo possível, provavelmente agarraremos qualquer coisa. Isso significa que nós, com um alto grau de probabilidade (embora isso não possa ser chamado de inevitável), criaremos aplicativos nos quais haverá muitos excessos e, muito provavelmente, usaremos padrões que prejudicam o desempenho e a disponibilidade dos aplicativos.

Desenvolvimento Web não é uma tarefa fácil. Este é um trabalho longo. Raramente é executado bem na primeira tentativa. A melhor coisa sobre esse trabalho, no entanto, é que não precisamos fazer tudo perfeitamente desde o início. Podemos fazer melhorias nos projetos depois que eles são lançados e, de fato, este material é dedicado a isso, o segundo de uma série de artigos sobre uma abordagem responsável ao desenvolvimento de JS. A perfeição é um objetivo muito distante. Enquanto isso, vamos lidar com a ressaca do JavaScript, melhorando, por assim dizer, os scripts
no site em um futuro próximo.

Lidamos com problemas comuns


Isso pode parecer uma abordagem mecânica para resolver problemas, mas primeiro faça uma lista de problemas típicos e maneiras de lidar com eles. Em grandes equipes de desenvolvimento, essas coisas são frequentemente esquecidas. Isso é especialmente verdade para as equipes que trabalham com vários repositórios ou não usam um modelo otimizado para seus projetos.

▍ Aplique o algoritmo de agitação de árvore


Primeiro, verifique se suas ferramentas estão configuradas para implementar o algoritmo de agitação de árvore . Se você nunca se deparou com esse conceito antes, dê uma olhada neste meu material escrito no ano passado. Se explicarmos a operação desse algoritmo em poucas palavras, podemos dizer que, devido ao seu uso nas montagens de produção do aplicativo, não incluem os pacotes que, embora importados para o projeto, não são usados ​​nele.

A implementação do algoritmo de agitação de árvore é um recurso padrão dos empacotadores modernos, como webpack , Rollup ou Parcel . Grunhido ou gole são gerenciadores de tarefas. Eles não fazem isso. O gerenciador de tarefas, diferentemente do bundler, não cria um gráfico de dependência . O gerenciador de tarefas está envolvido, usando os plug-ins necessários, executando manipulações separadas nos arquivos transferidos para ele. A funcionalidade dos gerenciadores de tarefas pode ser expandida usando plug-ins, oferecendo a capacidade de processar JavaScript usando empacotadores. Se a expansão dos recursos do gerenciador de tarefas nessa direção parecer um problema, você provavelmente precisará verificar manualmente a base de código e remover o código não utilizado.

Para que o algoritmo de agitação de árvore funcione com eficiência, as seguintes condições devem ser atendidas:

  1. O código do aplicativo e os pacotes instalados devem ser apresentados como módulos ES6 . Usar o algoritmo de agitação de árvore para módulos CommonJS é quase impossível.
  2. Seu empacotador não deve transformar os módulos ES6 em módulos de outro formato durante a construção do projeto. Se isso acontecer na cadeia de ferramentas que usa Babel, @ Babel / present-env deve ter a configuração modules: false . Isso fará com que o código ES6 não seja convertido em código que usa CommonJS.

Se, de repente, ao criar seu projeto, o algoritmo de agitação da árvore não for aplicado - ativar esse mecanismo pode melhorar a situação. Obviamente, a eficácia desse algoritmo varia de projeto para projeto. Além disso, a possibilidade de seu uso depende se os módulos importados têm efeitos colaterais . Isso pode afetar a capacidade do bundler de se livrar da inclusão de módulos importados desnecessários no assembly.

▍Divida o código em partes


É muito provável que você já esteja usando alguma forma de separação de código . No entanto, você deve verificar como isso é feito. Independentemente de como você separa o código, quero lhe oferecer as duas perguntas muito valiosas a seguir:

  1. Você remove o código duplicado dos pontos de entrada ?
  2. Você carrega preguiçosamente tudo o que pode ser carregado dessa maneira com importações dinâmicas ?

Esses problemas são importantes porque reduzir a quantidade de código redundante é um elemento crucial de desempenho. O carregamento lento do código também melhora o desempenho, reduzindo a quantidade de código JavaScript que faz parte da página e carrega quando é carregado. Se falarmos sobre a análise do projeto quanto à presença de código redundante, então para isso você pode usar algum tipo de ferramenta como o Bundle Buddy. Se o seu projeto tiver um problema com isso, esta ferramenta informará sobre ele.


A ferramenta Bundle Buddy pode verificar as informações de compilação do webpack e descobrir quanto do mesmo código é usado em seus pacotes configuráveis.

Se falarmos sobre carregamento lento de materiais, descobrir onde procurar oportunidades para aplicar essa otimização pode ser uma dificuldade. Quando pesquiso em um projeto existente a possibilidade de usar carregamento lento, procuro na base de código os locais que envolvem interações do usuário com o código. Pode ser, por exemplo, manipuladores de eventos de mouse ou teclado, além de outras coisas semelhantes. Qualquer código que exija a execução de alguma ação do usuário é um bom candidato para aplicar o comando dynamic import() a ele.

Obviamente, o carregamento de scripts sob demanda acarreta o risco de atrasos visíveis na mudança do sistema para o modo interativo. De fato, antes que o programa possa interagir interativamente com o usuário, é necessário fazer o download do script apropriado. Se a quantidade de dados transferidos não o incomoda, considere usar a dica de recurso rel = pré-busca para carregar esses scripts de baixa prioridade. Esses recursos não competirão por largura de banda com recursos críticos. Se o navegador do usuário suportar rel=prefetch , o uso dessa dica de ferramenta será benéfico. Caso contrário, nada de ruim acontecerá, pois os navegadores simplesmente ignoram as marcações que eles não entendem.

▍Utilize a opção externa do webpack para marcar recursos localizados em servidores estrangeiros


Idealmente, você deve hospedar o maior número possível de dependências do site em seus próprios servidores. Se, por algum motivo, você, sem opções, precisar baixar dependências dos servidores de outras pessoas - coloque-as no bloco externo nas configurações do webpack. Se isso não for feito, isso pode significar que os visitantes do seu site baixarão o código que você hospeda e o mesmo código dos servidores de outras pessoas.

Dê uma olhada em uma situação hipotética em que algo assim poderia prejudicar seu recurso. Suponha que seu site baixe a biblioteca Lodash de um recurso público da CDN. Você também instalou o Lodash no projeto para fins de desenvolvimento local. No entanto, se você não especificar nas configurações do webpack que o Lodash é uma dependência externa, seu código de produção carregará a biblioteca da CDN, mas ao mesmo tempo será incluída no pacote que está hospedado no servidor.

Se você conhece bem os empacotadores, tudo isso pode lhe parecer verdades comuns. Mas vi como essas coisas não estão prestando atenção. Portanto, não se apresse em verificar seu projeto quanto aos problemas acima.

Se você não considerar necessário hospedar suas dependências criadas por desenvolvedores de terceiros por conta própria, considere o uso de dns-prefetch , preconnect ou talvez até pré-carregar dicas com elas. Isso pode reduzir a pontuação do TTI (tempo para interatividade, tempo do site para a primeira interatividade). E se os recursos JavaScript forem necessários para exibir o conteúdo do site, o Índice de Velocidade do site também será necessário.

Bibliotecas alternativas menores e sobrecarga reduzida nos sistemas do usuário


O que é chamado de " Userland JavaScript " (bibliotecas JS desenvolvidas pelo usuário) parece uma enorme loja de doces obscena. Toda essa magnificência e variedade de código aberto nos inspira, desenvolvedores, com admiração. Estruturas e bibliotecas nos permitem expandir nossos aplicativos, equipando-os rapidamente com recursos que ajudam a resolver uma variedade de problemas. Se tivéssemos que implementar a mesma funcionalidade por nós mesmos, levaria muito tempo e energia.

Embora eu defenda pessoalmente a minimização agressiva do uso de estruturas e bibliotecas de clientes em meus projetos, não posso deixar de reconhecer seu enorme valor e utilidade. Mas, apesar disso, quando se trata de instalar novas dependências no projeto, devemos tratar cada uma delas com uma boa dose de suspeita. Se já criamos e lançamos algo, cuja operação depende de muitas dependências instaladas, isso significa que suportamos a carga adicional no sistema que tudo isso cria. É provável que apenas desenvolvedores de pacotes possam lidar com esse problema otimizando seu desenvolvimento. É isso mesmo?

Talvez seja assim, mas talvez não. Depende das dependências usadas. Por exemplo, o React é uma biblioteca extremamente popular. Mas o Preact é uma alternativa muito pequena ao React, que oferece ao desenvolvedor quase as mesmas APIs e permanece compatível com muitos complementos do React. Luxon e date-fns são alternativas ao moment.js , muito mais compactos que esta biblioteca, que não é tão pequena .

Em bibliotecas como o Lodash, você pode encontrar muitos métodos úteis. Mas alguns deles são fáceis de substituir pelos métodos ES6 padrão. Por exemplo, o método compacto Lodash pode ser substituído pelo método padrão de matrizes de filtro . Muitos outros métodos Lodash também podem ser substituídos com segurança pelos métodos padrão. A vantagem dessa substituição é que obtemos os mesmos recursos que o uso da biblioteca, mas eliminamos uma dependência bastante grande.

O que quer que você use, a idéia geral permanece a mesma: pergunte se sua escolha tem alternativas mais compactas. Descubra se você pode resolver os mesmos problemas com as ferramentas de idioma padrão. Talvez você fique agradavelmente surpreso com o pouco esforço necessário para reduzir seriamente o tamanho do aplicativo e a quantidade de carga desnecessária que ele exerce nos sistemas do usuário.

Use tecnologias de carregamento diferencial de script


Provavelmente, Babel está na sua cadeia de ferramentas. Essa ferramenta é usada para transformar código-fonte compatível com ES6 em código que os navegadores herdados podem executar. Isso significa que estamos condenados a oferecer pacotes enormes mesmo para navegadores que não precisam deles, até que todos os navegadores antigos simplesmente desapareçam? Claro que não ! O carregamento diferencial de recursos ajuda a contornar esse problema criando dois assemblies diferentes com base no código ES6:

  • O primeiro assembly inclui todas as conversões de código e polyfill necessárias para o seu site funcionar em navegadores herdados. Provavelmente agora você está dando aos clientes essa montagem específica.
  • O segundo assembly contém um mínimo de conversões de código e polyfill, ou ocorre sem elas. Ele foi projetado para navegadores modernos. Esta é uma montagem que você pode não ter. Pelo menos ainda não.

Para usar a tecnologia de carregamento diferencial de montagens, você precisa trabalhar um pouco. Não vou entrar em detalhes aqui - darei um link melhor para o meu material, que discute uma das maneiras de implementar essa tecnologia. A essência de tudo isso é que você pode modificar sua configuração de construção para que, durante a construção do projeto, uma versão adicional do pacote JS do seu site seja criada. Esse pacote adicional será menor que o principal. Destina-se apenas a navegadores modernos. A melhor parte é que essa abordagem permite otimizar o tamanho do pacote e, ao mesmo tempo, não sacrificar absolutamente nada dos recursos do projeto. Dependendo do código do aplicativo, a economia no tamanho do pacote pode ser bastante significativa.


Análise de pacotes configuráveis ​​para navegadores herdados (à esquerda) e pacotes configuráveis ​​para novos navegadores (à direita). O estudo do pacote foi realizado usando o webpack-bundle-analyzer. Aqui está a versão em tamanho real desta imagem.

É mais fácil oferecer pacotes diferentes para navegadores diferentes usando o seguinte truque. Funciona bem em navegadores modernos:

 <!--     : --> <script type="module" src="/js/app.mjs"></script> <!--     : --> <script defer nomodule src="/js/app.js"></script> 

Infelizmente, essa abordagem tem desvantagens. Navegadores desatualizados como o IE11 e até relativamente modernos, como as versões 15-18 do Edge, carregam os dois pacotes. Se você estiver pronto para atendê-lo - use essa técnica e não se preocupe com nada.

Por outro lado, você precisa criar algo para o caso de estar preocupado com o impacto no desempenho do aplicativo, pois os navegadores mais antigos precisam fazer o download dos dois pacotes. Aqui está uma solução potencial para esse problema que usa injeção de script (em vez da tag <script> que usamos acima). Evita o carregamento duplo de pacotes pelos navegadores apropriados. Aqui está o que estamos falando:

 var scriptEl = document.createElement("script"); if ("noModule" in scriptEl) {  //     scriptEl.src = "/js/app.mjs";  scriptEl.type = "module"; } else {  //     scriptEl.src = "/js/app.js";  scriptEl.defer = true; // type="module"   ,    . } //  ! document.body.appendChild(scriptEl); 

Esse script pressupõe que, se o navegador suportar o atributo nomodule no elemento script , ele entenderá a construção type="module" . Isso garante que os navegadores herdados recebam apenas scripts projetados para eles, e os modernos receberão scripts projetados para eles. No entanto, lembre-se de que os scripts incorporados dinamicamente são carregados de forma assíncrona por padrão. Portanto, se a ordem de carregamento de dependências for importante para você, defina o atributo async como false .

Transmitir menos


Eu não vou atacar Babel aqui. Essa ferramenta é necessária no desenvolvimento moderno da Web, mas é uma entidade muito instável. Babel adiciona muitas coisas ao código que gera, que o desenvolvedor talvez não conheça. Portanto, você não se arrependerá se olhar para as entranhas de Babel e descobrir exatamente o que ele está fazendo. Em particular, o conhecimento dos mecanismos internos de Babel deixa claro que pequenas mudanças na maneira como alguém escreve código podem ter um efeito positivo no que Babel gera.


Transmitir menos

Ou seja, é disso que estamos falando. Por exemplo, as opções padrão são um recurso muito útil do ES6 que você já pode estar usando:

 function logger(message, level = "log") {  console[level](message); } 

Aqui vale a pena prestar atenção no parâmetro level , cujo valor padrão é o log strings. Isso significa que, se quisermos chamar console.log usando a função wrapper do logger , não precisamos passar de level para essa função. Conveniente, certo? Tudo isso é bom - exceto pelo código que Babel obtém ao transformar esta função:

 function logger(message) {  var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "log";  console[level](message); } 

Este é um exemplo de como, apesar de sermos guiados por boas intenções, os confortos que Babel dá podem levar a consequências negativas. O que eram apenas alguns caracteres no código-fonte se transformou em uma construção muito mais longa na versão de produção do programa. - , , arguments .

, , ? , Babel :

 //   function logger(...args) {  const [level, message] = args;  console[level](message); } //   Babel function logger() {  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {    args[_key] = arguments[_key];  }  const level = args[0],        message = args[1];  console[level](message); } 

, Babel @babel/preset-env , . , , , , , ! — «» ( true loose ). — , , , , , . «» , Babel , .

, «» , , Babel :

 // Babel      function logger(message, level) {  console[level || "log"](message); } 

, — JavaScript, , . spread , , .

— :

  1. @babel/runtime @babel/plugin-transform-runtime , , Babel .
  2. , . @babel/polyfill . , babel /preset-env useBuiltins usage .

, , , , , . , JSX , , , . , , . , , . , Babel — . , Babel. , .

: —


, . , JavaScript-, , . , , . - . . , , , , , , , .

, , , , , , , . - — . , , , . . , , , , . , , , .

Caros leitores! JS-?

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


All Articles