
Uma nota rápida sobre como o Chrome DevTools migrou do carregador de módulo interno para módulos JavaScript padrão. Diremos como e por que a migração foi atrasada, sobre os custos ocultos da migração e sobre as conclusões da equipe de DevTools após a conclusão da migração. Mas vamos começar com a história das ferramentas para desenvolvedores web.
Introdução
Como você provavelmente sabe, o Chrome DevTools é um aplicativo da web HTML, CSS e JavaScript. Ao longo dos anos, o DevTools se tornou rico em recursos, inteligente e conhecedor da plataforma da web moderna. Embora o DevTools tenha se expandido, sua arquitetura lembra muito a original quando a ferramenta fazia parte do WebKit .
Contaremos a história do DevTools, descreveremos os benefícios e as limitações das soluções e o que fizemos para mitigar essas limitações. Então, vamos mergulhar nos sistemas modulares, como carregar o código e como acabamos usando módulos JavaScript.
No começo não havia nada
O frontend agora tem muitos sistemas modulares e suas ferramentas, bem como um formato de módulo JavaScript padronizado . Nada disso foi quando o DevTools começou. A ferramenta é construída em cima do código WebKit escrito há mais de 12 anos.
A primeira menção ao sistema modular no DevTools data de 2012: foi a introdução de uma lista de módulos com uma lista correspondente de fontes . Parte da infraestrutura Python usada na época para compilar e construir DevTools. Em 2013, os módulos foram retirados em um arquivo por
frontend_modules.json
este commit , e então, em 2014, em outros separados module.json
( aqui ). Exemplo module.json
:
{
"dependencies": [
"common"
],
"scripts": [
"StylePane.js",
"ElementsPanel.js"
]
}
module.json
Usado em ferramentas de desenvolvedor
desde 2014 para apontar para módulos e arquivos de origem. Nesse ínterim, o ecossistema da web cresceu rapidamente e muitos formatos de módulo foram criados: UMD, CommonJS e, eventualmente, módulos JavaScript padronizados. No entanto, o DevTools está preso module.json
. O sistema modular exclusivo não padronizado tinha várias desvantagens:
module.json
exigia suas próprias ferramentas de construção.- Não houve integração IDE. Claro, ela precisava de ferramentas especiais para criar arquivos que ela entendia: (
jsconfig.json
para o código VS ). - Funções, classes e objetos foram colocados no escopo global para permitir o compartilhamento entre os módulos.
- A ordem em que os arquivos foram listados era importante. Não havia garantia de que o código em que você confia foi carregado, exceto a verificação humana.
Em geral, avaliando o estado atual do sistema modular DevTools e outros formatos de módulo mais amplamente usados, chegamos à conclusão de que ele
module.json
criou mais problemas do que resolveu.
Vantagens do padrão
Escolhemos módulos JavaScript. Quando essa decisão foi tomada, os módulos na linguagem ainda estavam sinalizados no Node.js e um grande número de pacotes NPM não os suportava. Apesar disso, concluímos que os módulos JavaScript eram a melhor opção.
A principal vantagem dos módulos é que eles têm um formato padronizado por linguagem . Quando listamos os contras
module.json
, percebemos que quase todos eles estavam relacionados ao uso de um formato de módulo único e não padronizado. Escolher um formato de módulo não padronizado significa que temos que investir tempo construindo integrações usando ferramentas de construção e as ferramentas de nossos colegas. Essas integrações eram frequentemente frágeis e sem suporte para recursos, exigindo tempo de manutenção adicional e, às vezes, bugs complicados. Os bugs acabaram atingindo os usuários.
Como os módulos JavaScript eram padrão, isso significava que IDEs como o VS Code, ferramentas de verificação de tipo como o compilador Closure / TypeScript e ferramentas de construção como Rollup e minificadores podiam entender o código-fonte que você escreveu. Além do mais, quando uma nova pessoa se junta à equipe de DevTools, ela não precisa perder tempo aprendendo o proprietário
module.json
.
Claro, quando o DevTools começou, nenhum dos benefícios acima existia. Demorou anos de trabalho em grupos de padrões para implementar o tempo de execução. Demorou para o feedback dos desenvolvedores - usuários dos módulos. Mas quando os módulos apareceram no idioma, tivemos uma escolha: ou continuar apoiando nosso próprio formato, ou investir na transição para o novo formato.
Quanto custa o brilho da novidade?
Embora os módulos JavaScript tivessem muitos benefícios que queríamos usar, permanecemos no mundo
module.json
. Aproveitar os módulos da linguagem significou que tínhamos que investir um esforço significativo na dívida técnica. Nesse ínterim, a migração pode quebrar funções e introduzir bugs de regressão.
Não era sobre se deveríamos usar módulos JavaScript. A questão era quão cara é a capacidade de usar módulos JavaScript . Tivemos que equilibrar o risco de incomodar os usuários com regressões, o tempo que os engenheiros levaram para migrar e um período de deterioração do estado do sistema em que estaríamos operando.
O último ponto acabou sendo muito importante. Embora pudéssemos teoricamente chegar aos módulos JavaScript, durante a migração acabaríamos com um código que levaria em consideração os dois tipos de módulos . Isso não é apenas tecnicamente desafiador, mas também significa que todos os engenheiros que trabalham em DevTools precisam saber como trabalhar em tal ambiente. Eles teriam que se perguntar constantemente: "O que está acontecendo neste código, é
module.json
JS e como posso fazer a alteração?"
O custo latente da migração em termos de treinamento de colegas foi maior do que esperávamos.Depois de analisar os custos, chegamos à conclusão de que ainda vale a pena mudar para módulos na linguagem. Portanto, nossos principais objetivos eram:
- Certifique-se de que os módulos padrão sejam tão úteis quanto possível.
- Certifique-se de que a integração com os módulos existentes na base é
module.json
segura e não causa impactos negativos no usuário (erros de regressão, frustração do usuário). - Fornece guias de migração para DevTools. Principalmente por meio de verificações e balanços integrados ao processo para evitar erros acidentais.
Planilha, conversões e dívida técnica
O objetivo era claro. Mas as limitações eram
module.json
difíceis de contornar. Foram necessárias várias iterações, protótipos e alterações arquitetônicas antes de chegarmos a uma solução utilizável. Acabamos escrevendo um documento de projeto com uma estratégia de migração. Este documento deu uma estimativa inicial do tempo: 2-4 semanas.
A parte mais intensa da migração durou 4 meses, e 7 meses se passaram do início ao fim!O plano original, no entanto, resistiu ao teste do tempo: queríamos ensinar o tempo de execução do DevTools a carregar todos os arquivos da maneira antiga para usar aqueles listados no array
scripts
module.json
, enquanto todos os arquivos listados no array modules
tinham que ser carregados por importação de linguagem dinâmica . Qualquer arquivo que estiver no array modules
pode funcionar com import
e a export
partir do ES6.
Além disso, queríamos migrar em 2 fases. Por fim, dividimos a última fase em 2 subfases: exportação e importação. Módulos e fases foram rastreados em uma grande planilha:

Um trecho da tabela de migração aqui.
Fase de exportação
A primeira etapa foi adicionar instruções de exportação para todas as entidades que precisam ser compartilhadas entre módulos / arquivos. A transformação foi automatizada executando um script para cada pasta . Digamos que
module.json
exista tal entidade:
Module.File1.exported = function() {
console.log('exported');
Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
console.log('Local');
};
Aqui
Module
está o nome do módulo. File1
- nome do arquivo. Na árvore de código, ele se parece com isso: front_end/module/file1.JS
.
O código acima se traduz nisso:
export function exported() {
console.log('exported');
Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
console.log('Local');
}
/** Legacy export object */
Module.File1 = {
exported,
localFunctionInFile,
};
Originalmente, planejamos reescrever a importação em um arquivo neste momento. Por exemplo, no exemplo acima, reescreveríamos
Module.File1.localFunctionInFile
para localFunctionInFile
. No entanto, percebemos que seria mais fácil automatizar e mais seguro separar as duas transformações. Portanto, "transferir todas as entidades para um arquivo" se tornará a segunda subfase de importação.
Visto que adicionar uma palavra-chave
export
transforma o arquivo de um "script" em um "módulo", grande parte da infraestrutura do DevTools teve que ser atualizada de acordo. A estrutura incluía um tempo de execução de importação dinâmica, bem como ferramentas como ESLint para execução em modo de módulo.
Um incômodo foi que nossos testes foram executados em um modo "não estrito". Os módulos JavaScript implicam que os arquivos sejam executados no modo estrito. Isso afetou os testes. Como se viu, um número não trivial de testes dependia de um modo não estrito, incluindo um teste no qual o operador estava presente
with
.
No final, atualizar a primeira pasta (adicionando exportação) levou cerca de uma semana e várias tentativas de recarregamento .
Fase de importação
Depois que todas as entidades foram exportadas usando as declarações de exportação, embora permanecendo no escopo global devido ao legado, tivemos que atualizar todas as referências de entidade, se elas estiverem em vários arquivos, para usar importações ES. O objetivo final é remover todas as exportações expiradas, limpando o escopo global. A transformação foi automatizada executando um script para cada pasta .
Por exemplo, as seguintes entidades
module.json
:
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();
Convertido para:
import * as Module from '../module/Module.js';
import * as AnotherModule from '../another_module/AnotherModule.js';
import {moduleScoped} from './AnotherFile.js';
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
moduleScoped();
No entanto, essa abordagem tem ressalvas:
- Nem toda entidade foi nomeada por princípio
Module.File.symbolName
. Algumas entidades foram nomeadasModele.File
ou mesmoModule.CompletelyDifferentName
. A incompatibilidade significava que tínhamos que criar um mapeamento interno do antigo objeto global para o novo objeto importado. -
moduleScoped
. ,Events
, ,Events
. , , ,import
Events
. - , . , . , , . , , ( DevTools). , , .
JavaScript
Em fevereiro de 2020, 6 meses após o início em setembro de 2019, as últimas limpezas foram realizadas na pasta ui /. Foi assim que a migração terminou não oficialmente. Quando a poeira baixou, marcamos oficialmente a conclusão da migração em 5 de março de 2020 .
Agora, o DevTools funciona apenas com módulos JavaScript. Ainda estamos colocando algumas entidades no escopo global (em arquivos legados
module.js
) para testes legados ou integrações com outras partes das ferramentas do arquiteto. Eles serão removidos com o tempo, mas não os consideramos bloqueando o desenvolvimento. Também temos um guia de estilo para módulos JavaScript .
Estatisticas
Estimativas conservadoras do número de CL (changelist - um termo usado no Gerrit, semelhante à solicitação de pull do GitHub) envolvidos nesta migração é de cerca de 250 CL, a maioria feita por 2 engenheiros . Não temos estatísticas finais sobre o tamanho das alterações feitas, mas uma estimativa conservadora das linhas alteradas (a soma da diferença absoluta entre inserções e exclusões para cada CL) é de cerca de 30.000 linhas, que é cerca de 20% de todo o código de front-end do DevTools .
O primeiro arquivo a ser exportado é compatível com o Chrome 79, que foi lançado na versão estável em dezembro de 2019. A última mudança para mudar para importação vem no Chrome 83, que foi lançado na versão estável em maio de 2020.
Estamos cientes de uma regressão devido à migração no Chrome estável. O preenchimento do snippet de código na barra de comando quebrou devido à exportação padrão estranha . Houve várias outras regressões, mas nossos casos de teste automatizados e usuários do Chrome Canary as relataram. Corrigimos bugs antes que eles pudessem entrar em versões estáveis do Chrome.
Você pode ver toda a história registrando-se aqui . Nem todos, mas a maioria dos CL estão vinculados a esse erro.
O que aprendemos?
- . , JavaScript ( ) , DevTools . , , , .
- — , . , , . , , , .
- (, ) . -. , Python Rollup.
- (~20% ), . , , . , .
- , . . , , , . , , — . , , .
, Level Up , - SkillFactory:
- Java- (18 )
- JavaScript (12 )
E
- Data Science (12 )
- - (8 )
- Machine Learning (12 )
- «Machine Learning Pro + Deep Learning» (20 )
- « Machine Learning Data Science» (20 )
- «Python -» (9 )
- DevOps (12 )
- (9 )
- UX- (9 )
- Web- (7 )