Em um projeto grande, a tarefa de identificar alterações para o usuário final por diferenças no código de front-end do aplicativo pode surgir. O desenvolvedor do Yandex.Market Nikita Sidorov @nickshevr contou como resolvemos esse problema usando a biblioteca Diffector, sobre como criar e analisar o gráfico do módulo nos aplicativos Node.js. e sobre como encontrar defeitos no código antes de ser lançado.

- Hoje vou tentar ser o mais sincero possível com você.
Trabalho no Yandex.Market há pouco mais de um ano e meio. Estou fazendo a mesma quantidade de web e comecei a perceber mudanças em mim mesmo, você também pode notá-las. Meu comprimento médio do cabelo aumentou e a barba começou a aparecer. Hoje, olhei para os meus colegas: no Sergei Berezhnoy
veged , no Vova Grinenko
tadatuta , e percebi - esse é um bom critério para o que quase amadureci como desenvolvedor de front-end de verdade.
E, chegando a esse juramento, decidi conversar com você sobre a vida, sobre a qual todos nós participamos. Principalmente sobre a vida antes do tempo de execução. Agora vou explicar sobre o que tudo isso será.

E a vida? Sobre a vida do código, é claro. Código é o que fazemos. Deixe-me lembrá-lo, eu decidi ser sincero com você aqui, então o primeiro slide foi o mais simples possível. Peguei a verdade, a primeira etapa - a adoção, veja você, ninguém discutirá com esse axioma.

E então eu percebi que teria que modificá-lo, mas para que ficasse claro. Que seja algum tipo de aceitação de requisitos. Qualquer código começa com o fato de você examinar a tarefa e tentar aceitar os requisitos que o definem.

Depois disso, é claro, começamos a fase de escrita - escrevemos nosso código. Em seguida, cobrimos com testes, verificamos sua eficácia. Depois disso, já verificamos se nosso aplicativo funciona com nosso código como um todo. Depois disso, entregamos ao testador - deixe-o verificar. O que você acha depois disso? Lembro-lhe, a vida antes da execução. Você acha que o tempo de execução segue isso? De fato, acontece assim. E isso não é um erro na apresentação. Muitas vezes, em qualquer fase das verificações - e pode haver muito mais do que eu indiquei -, você pode ter alguma chamada para escrever novamente. Concordo, isso pode ser um grande problema. Isso pode atrasar a entrega de alguns recursos da produção e, em princípio, atrasá-lo como desenvolvedor, porque o ticket ficará com você. E aqui tudo passa, passa. Existem mais algumas vezes M para N verificações e somente então o código chega ao usuário no navegador. Mas esse é o nosso objetivo. Nosso objetivo é escrever um código que realmente esteja disponível para o usuário e que realmente funcione em seu benefício.
Hoje vamos falar sobre a primeira parte. Sobre o que acontece antes, mas não realmente sobre os testes.

A propósito, parece algo assim. Tomei a nossa vez no rastreador, coletei o meu, contei a mediana. Acontece que meus ingressos estão muito menos em desenvolvimento do que em cheques. E como você sabe, quanto mais tempo o teste for, maior a chance de aparecer goto no início ou goto no final - e eu não quero.
E também, se você prestar atenção, há duas palavras no slide aqui - desenvolvimento (é isso que nós, desenvolvedores estamos fazendo) e verificação (é isso que estamos fazendo, mas também testadores). Portanto, o problema é relevante, de fato, para os testadores.

O objetivo é bastante prosaico. Em geral, gosto de dizer que a vida precisa ser simplificada: já trabalhamos muito com você. O objetivo se parece com isso, mas você deve admitir que é bastante efêmero; portanto, vamos destacar alguns critérios básicos dos quais o objetivo pode depender.
Obviamente, quanto menos código, mais fácil é para nós. Quanto mais rápido tivermos as verificações de IC, mais cedo perceberemos se estamos certos ou não. Ou seja, localmente, geralmente pode começar para sempre. Velocidade de verificação - isso se aplica diretamente ao testador. Se o nosso aplicativo for grande e precisar ser verificado na íntegra, é uma quantidade enorme de tempo. A velocidade de liberação depende de tudo isso. Incluir é impossível liberar até que passemos em todas as verificações e até que entendamos que o código é exatamente o que queremos.
Para resolver alguns dos problemas de que estamos falando, vamos analisar o gráfico de dependência dos módulos em nossa linguagem de programação. E, na verdade, vamos descrevê-lo.

O gráfico é orientado: possui arestas com direções. Nos nós do gráfico, teremos apenas os módulos da linguagem sobre a qual estamos falando. Costelas são um tipo específico de vínculo. Existem vários tipos de comunicação.

Vejamos um exemplo comum. Há o arquivo A. Aqui, algo do arquivo B é importado para ele, e esse é um relacionamento entre nós.

O mesmo acontecerá se você substituir a importação por exigir. De fato, nem tudo é tão simples aqui.

Eu sugiro, já que estamos falando sobre o tipo de dependência, considere pelo menos dois tipos - para acelerar seu pipeline, para acelerar o percurso do gráfico. É necessário observar não apenas o módulo dependente, mas também o módulo dependente. Proponho chamar o módulo A - o pai, B - o filho, e aconselho que você mantenha os links sempre como uma lista dupla. Isso simplificará sua vida, informo com antecedência.
Depois de descrevermos o gráfico de alguma forma, vamos concordar em como o construiremos.

Existem duas maneiras. Sua ferramenta favorita em sua linguagem de programação favorita usando o mesmo AST (árvores de sintaxe abstrata) ou regulares. Qual é o lucro aqui? O fato de que aqui você não está vinculado a ninguém, mas ao mesmo tempo precisa implementar tudo sozinho. Você terá que descrever todos os tipos de conexões de todas as coisas e tecnologias que você usa, seja um coletor CSS separado, algo assim. Mas você tem total liberdade de vôo, por assim dizer.
Além disso, a segunda opção, também estarei promovendo um pouco, esta é uma opção apenas para a maioria das pessoas que já possui um sistema de compilação configurado. O fato é que o sistema de montagem coleta um gráfico dependendo do design, por padrão.

Vejamos um dos sistemas de montagem mais populares do Yandex, este é o webpack. Aqui dei um exemplo de como você pode coletar todo o resultado do webpack em um arquivo separado, que pode ser alimentado ao nosso ou a algum outro analisador. Ele o coleta com a ajuda da AST, a biblioteca de bolotas é usada. Você pode ter notado quando algo caiu. Eu notei.
E quais são as vantagens. O fato é que, quando você descreveu seu sistema de compilação, você absolutamente pediu honestamente a entrada. Esses são os arquivos dos quais suas dependências são desenroladas, os pontos de desvio iniciais. Isso é bom, porque você não precisa gravá-los novamente. Além disso, webpack e babel, e tudo isso, e bolota, inclusive, ainda não é sua manutenção. E, portanto, todos os tipos de novos recursos da linguagem, todos os tipos de bugs e tudo mais são corrigidos mais rapidamente do que se você fizesse isso, principalmente se você não tiver uma equipe muito grande. Sim, mesmo que seja grande, não é tão grande quanto o código aberto.
Na verdade, isso é um mais e um menos. É como se uma aresta dupla (espada de dois gumes) fosse obtida. O fato é que este gráfico é construído durante a montagem. É bom, ou seja, podemos montar o projeto e reutilizar imediatamente o resultado da montagem. Mas e se não queremos montar um projeto, mas apenas queremos obter esse gráfico?
E um sinal menos importante, de fato. Se você tiver alguma coisa personalizada conectada, falaremos muito mais tarde sobre conexões, então o sistema de compilação não permitirá que você faça isso. Ou você precisará integrar isso, como o plug-in do seu webpack.

Considere um exemplo específico. Eu executei um comando na minha projeção, onde existem apenas três arquivos, e obtive essa saída. E isso só mostro uma chave, chamada módulos. Estamos apenas conversando com você sobre o gráfico de dependência dos módulos; portanto, analisamos os módulos, tudo é lógico.

Muita informação, mas não precisamos de tudo. Deixe alguns pontos e vamos conversar sobre eles. Suponha que consideremos o primeiro módulo. Ele tem um nome, há razões. Os motivos são apenas a conexão, de fato, com os módulos “dependentes”, acontece quem importa esse módulo para si. Estes são os dados básicos para construir um gráfico sobre ele.

Além disso, preste atenção às exportações usadas e às exportações fornecidas. Falaremos sobre eles um pouco mais tarde. Mas estas também são coisas muito importantes.


E se você descrever sua decisão, precisará falar sobre os tipos de conexões que acontecem entre os módulos. Ou seja, temos, é claro, nosso sistema de módulos dentro de nossa linguagem: seja cjs-modules ou esm-modules. Além disso, você deve concordar que podemos ter uma conexão entre arquivos no sistema de arquivos no nível do próprio sistema de arquivos. Estes são alguns tipos de estruturas: algum tipo de estrutura, dependendo de como os pais são.

E um exemplo tão banal - se você escreveu o lado do servidor do Node, com bastante frequência você poderia ver um pacote npm tão popular como o Config. Permite definir de forma bastante conveniente suas configurações.

Para usá-lo, é necessário obter a pasta de configuração, onde você tem NODE_PATH, e especificar vários arquivos JavaScript - apenas para apresentar a configuração para diferentes ambientes. Como exemplo, eu criei um pai, especifiquei padrão, desenvolvimento e produção.

E, de fato, toda a configuração funciona assim. Ou seja, quando você escreve require ('config'), ele apenas lê o módulo dentro de si e pega o nome do módulo na variável de ambiente. Como você entende, não ficou claro lá que esses arquivos são de alguma forma usados, porque não há importação / exigência direta, o webpack nem o reconheceria.

Hoje também falamos sobre injeção de dependência. Eu não era algo inspirado, mas como suporte, olhei para uma das bibliotecas aqui. É chamado de JS de inversão. Como você pode ver, ele fornece uma sintaxe bastante personalizada: lazyInject, nameProvider, e aqui está. E, você deve admitir, não está claro que tipo de provedor é, que tipo de módulo ele realmente injeta aqui. E precisamos disso, e temos que entender. Ou seja, novamente, o sistema de compilação não será resolvido e teremos que fazer isso sozinhos.
Suponha que construamos um gráfico e sugiro que você o armazene em algum lugar. O que nos permitirá fazer isso? Isso nos permitirá fazer algum tipo de análise heurística, jogar um pouco de Data Science e fazê-lo, concentrando-se em um intervalo de tempo.

Qual é a ideia? Aqui, de fato, estão diretamente nossos dados. Recentemente, implementamos nosso sistema de design no Yandex.Market e, em particular, implementamos uma biblioteca de componentes como parte desse sistema de design. E aqui você pode ver: consideramos o número de importações, o componente de reação da nossa biblioteca, o componente comum. E você pode distribuir em diretórios. Nesse caso, temos um repositório não mono e, portanto, temos platform.desktop, platform.touch e src.
O que podemos pensar quando vemos esses números? Podemos supor que o comando touch não parece aumentar o uso de componentes comuns. Isso significa que os componentes são ruins para dispositivos móveis - mal fabricados ou o comando touch é preguiçoso. Mas isso é realmente verdade?

Se olharmos em um período mais longo, em um período mais longo de tempo, isso nos permitirá fazer apenas o armazenamento de gráficos após cada versão, então entenderemos que, de fato, está tudo bem para o toque, o indicador está crescendo nele. Para o src, é ainda melhor, para o desktop, acontece que não.

Ainda havia uma pergunta da platéia sobre como explicar a importância para os gerentes. Aqui está o número total de importações de bibliotecas, também por tempo. Quais gerentes não gostam de gráficos? Você pode criar um cronograma e ver que o uso da biblioteca está aumentando, o que significa que isso é pelo menos uma coisa útil.
Uma das minhas partes favoritas. Vou abordar isso brevemente. Esta é uma busca por defeitos no gráfico. Hoje eu queria falar com você sobre dois tipos de defeitos: essa é uma dependência cíclica de módulos e algum módulo não utilizado, ou seja, um problema de eliminação de código morto.

Vamos começar com dependência circular.

Tudo parece ser bastante simples aqui. Você já tem um gráfico direcionado, basta encontrar um loop lá. Vou explicar por que estou falando sobre isso. O fato é que antes de escrever, basicamente, o lado do servidor no Node.js, e não usamos, em princípio, nenhum webpack / babel, nada. Ou seja, eles lançaram como estão. E havia exigir. Quem se lembra de como a importação difere da exigência? Tudo está correto. Se você escreveu mal o código, mas eu realmente escrevi, você pode descobrir em seu servidor que seu módulo está em algum tipo de dependência cíclica somente quando alguma solicitação vem dos usuários ou algum outro evento funcionará. Esse é um problema bastante global. Até o tempo de execução não entender. Ou seja, a importação é muito melhor, não haverá esse problema.

Depois, pegue o algoritmo que desejar. Aqui eu peguei um algoritmo bastante simples. Precisamos encontrar um vértice que tenha apenas um tipo de aresta - de entrada ou de saída. Se existe esse vértice, nós o excluímos, removemos as arestas e, de fato, continuamos esse processo, encontraremos e provaremos que havia um ciclo de cinco ciclos neste gráfico.
Concorde, se você analisou por código, ou seja, ainda é possível encontrar um ciclo de dois ou três comprimentos, mas é mais impossível, e em nosso projeto havia realmente um ciclo de sete, mas não em produção.

Sobre módulos não utilizados. Há também um algoritmo bastante trivial. Precisamos destacar os componentes conectados em nosso gráfico e apenas olhar, encontrar esses componentes, que não incluem nenhum dos nós de entrada. Nesse caso, esse é o componente da conexão, ambos os vértices, ao que parece, os dois nós. Então chamado entry.js. Na verdade, não importa como é chamado, é isso que você descreveu na configuração da montagem de entrada.

Mas há outra abordagem. Se você não coletou o gráfico e apenas possui um sistema de compilação, como é a maneira mais barata de fazer isso? Vamos apenas marcar todos os arquivos que entraram na montagem durante a montagem. Etiquete-os e crie muitos. Depois disso, devemos obter muitos de todos os arquivos que você possui no projeto e simplesmente subtraí-los. Essa é uma operação muito simples.

E agora não estou dizendo apenas algo teórico, fui inspirado, vim para o meu projeto e fiz isso. E atenção! Eu nem apaguei node_modules. Eu deixei isso como um ponto de crescimento para a próxima revisão. E, em resumo, fiquei tão inspirada por mim mesma que decidi de alguma forma fazer esse slide, reorganizá-lo. Deixe assim, porque é muito legal!
Bons números, você pode imaginar como tudo ficou bem? E então fui levado a uma estepe que me senti como um designer e pensei que essa é uma conquista que gostaria de acrescentar ao quadro. E, como você sabe, levantei-me, olhei e percebi que provavelmente não era designer, mas, de fato, desenvolvedor web. Mas eu não sou bobo. Peguei esse quadro, adicionado ao meu site para amuletos de SEO.

Você pode usar, até o link é. E para que você não pense que estou enganando você - hoje somos francos -, olhei realmente para os comentários. Eu acho que você pode acreditar neles.

Bem, para ser sincero, parecia algo assim. Eu vi uma nova hipo-biblioteca thanos-js, peguei, criei uma solicitação de pool. Em segredo, tenho direitos de administrador em nosso repositório. E eu peguei e confundi o mestre. Como você gosta disso? Bem, você e eu somos francos e, de fato, tudo se parecia com isso. Se alguém não souber, thanos-js é uma biblioteca que simplesmente remove 50% do seu código aleatoriamente.

Na verdade, usei a biblioteca lá de qualquer maneira, mas a biblioteca é chamada de maneira diferente. Isso é chamado de desvio e agora falaremos sobre isso com você. E aqui eu gostaria de observar que a solicitação de pool é bastante significativa, menos 44 mil linhas de código, e você pode imaginar - passou no teste pela primeira vez. Ou seja, o que estou falando pode realmente funcionar.

Diffector De fato, ele está envolvido não apenas na tarefa de remover módulos não utilizados, procurando defeitos no gráfico, mas também em uma tarefa mais importante. O que eu declarei inicialmente era ajudar o desenvolvedor e o testador, agora vamos falar sobre isso. E funciona mais ou menos assim.
Nós obtemos uma lista de arquivos modificados usando o sistema de controle de versão. Nós já construímos um gráfico - o difetor o constrói. E para cada arquivo modificado, procuramos o caminho para a entrada e marcamos a entrada modificada. E a entrada corresponderá às páginas do aplicativo que o usuário verá. Mas isso é bastante lógico.
E o que isso nos dá? Para teste - sabemos quais páginas do aplicativo foram alteradas. Podemos dizer ao testador que somente eles valem a pena testar. Também podemos dizer ao nosso ci-job, que executa testes automáticos, que apenas essas páginas valem a pena ser testadas. E para os desenvolvedores, tudo é muito mais simples, porque agora os testadores não escrevem para você e não perguntam: "Por que você precisa testar?"

Vejamos um exemplo de como o difetor funciona. Aqui temos um determinado diretório, pages.desktop / *. Apenas contém uma lista das próprias páginas. E as páginas também são descritas por vários arquivos. O controlador é o lado do servidor da página. Vista é algum tipo de parte de reação. E deps, isso é de outro sistema de compilação. Não temos apenas o webpack, mas também o ENB.

E fiz algumas alterações no projeto, em um arquivo vazio, cuja estrutura você viu. É isso que o diferencial me dá. Eu apenas comecei, o diffector é um aplicativo de linha de comando. Eu o iniciei, ele me disse que alterei uma página, chamada BindBonusPage.

Também posso executá-lo no modo detalhado, ver um relatório mais detalhado e realmente ver que, pelo menos, funciona em um caso tão simples. Como vemos, em nossa BindBonusPage, o arquivo de índice e o controlador foram alterados.
Mas vamos ver o que acontece se mudarmos outra coisa.

Eu mudei outra coisa. E o diferencial me disse que mudei nove páginas. E isso não me deixa mais feliz, como se ele realmente não me ajudasse.

Vamos ver porque? Agora, mostra os motivos pelos quais esta página foi considerada modificada. , . - uikit.

diff. . , diffector . , - , .

, , . , , entry, , , test-scope, . .
. , , , , .

. - , . — i18n, , . , , , . , , - .
? , , , , , .

- . , B , , -2 . . , esm.

.

.

, value, . , , . , .
, AST, , 250 , , . , , - , , .

, - GlobalContext - , . , modify, , ? , - GlobalContext. . . , side effects. , , webpack, , . , webpack sideEffects: true, . .

, - - . , . diffector, . , , . — , . , .
, , diff, expand, log, , , , .

, . D, diff. , , , . , , . , , . .
, , . . . , — , . . . , , , , , . . , .
— diffector ? , , , .

- , , .

, , entry. entry. diffector.

. -. , .

, entry -, .

. diffector. . , , - , -. , . : , BindBonusPage, -, . . , , - . .


— CI. . : , , .

, . 43 — testing, , .

. , , .

, . : , , . , , , , , . , , - .

. , , , . - , - , , , . .
, , , output . , . — . — . Obrigada