Migrando de AngularJS para React com Web Components
Hoje, na Hash, temos um SPA construído em Angularjs 1.8. Internamente chamada de Hashboard. Ela é uma plataforma para auxiliar nossos clientes com a gestão de seus negócios e é composta por uma série de dashboards e painéis administrativos.
Pela natureza white-label do nosso modelo de negócio e por sermos uma startup de alto crescimento, a Hashboard deveria ser altamente configurável tanto a nível de UI (User Interface) quanto a regras de negócio e funcionalidades para atender de imediato certas necessidades. Devido a algumas decisões passadas que visaram a entrega pro cliente à curto prazo nós tivemos que tomar alguns contornos hard-coded para atender às necessidades do cliente e isso não escala.
Além das dificuldades técnicas, arquiteturais e de escalabilidade da Hashboard, outras necessidades começaram a surgir na Hash, como a de uma identidade visual integrada entre diferentes produtos, maior adaptabilidade para atender às necessidades e regras de negócios específicas dos nossos clientes, entre outras. Tudo isso passou a convergir para algo claro: a Hashboard precisava de uma refatoração.
Refatoração do AngularJS para React
Pensando em como atender todos os requisitos que falamos acima (contar com uma stack mais atualizada, e ao mesmo tempo, ganhar tração de desenvolvimento e pensar na agilidade da contratação), nós cogitamos várias tecnologias e abordagens. Olhando para o mercado e pensando no background dos profissionais do time, optamos por usar React e TypeScript nesse processo de refatoração.
Há diversas motivações para isso, mas as principais foram o modelo de compatibilidade reversa do React, sua comunidade, segurança quanto ao suporte a longo prazo e a experiência dos desenvolvedores. Quanto ao TypeScript, dada a complexidade do app a nível de configuração, a segurança de tipos e o auxílio que a ferramenta traz para o desenvolvimento, também pareceu fazer sentido.
Porém, não poderíamos parar a operação para reconstruir algo do zero e ao mesmo tempo não queríamos continuar (quando possível) a evoluir o legado. Ou seja, era preciso refatorar a Hashboard “de dentro para fora”, desenvolvendo novas features com React e na nova arquitetura, mas isso rodando dentro do app existente. Essa estratégia também é conhecida como Strangler pattern. Mas como fazer isso em um app complexo e entre tecnologias com abordagens tão diferentes?
Web Components
Há diversas maneiras de refatorar nesse contexto. Poderíamos, por exemplo, utilizar algo como o ngReact para criar um wrapper em volta de componentes React para serem utilizados no app Angular. É uma abordagem interessante, já que permite uma comunicação fácil entre os dois mundos. Porém, isso poderia criar uma situação ainda mais caótica, com os dois mundos compartilhando muito estado, além de causar um cenário de maior complexidade cognitiva para qualquer desenvolvimento na página.
Há vários outros modos de executar uma refatoração, mas a escolha foi trabalhar com Web Components. Uma das suas principais vantagens é a presença de custom elements, uma parte da especificação que define a capacidade de criar novos elementos DOM. Para tal, optamos pela ferramenta remount que, em resumo, monta sua aplicação React no local do Web Component, pois o mesmo é registrado como um componente HTML.
Após definir seu web component, você pode utilizá-lo em qualquer lugar como um elemento HTML comum:
Criamos um monorepo utilizando Lerna para os novos pacotes e deixamos o legado em um diretório separado, mas no mesmo repositório. Dessa forma, os dois mundos são estritamente separados. Ao gerar e publicar o build do novo mundo, podemos adicioná-lo como dependência do legado e simplesmente utilizar o novo custom element como qualquer outro elemento.
Optamos por utilizar o Lerna porque a Hashboard possui um escopo que abrange quase todas as áreas da empresa. Sendo assim, faz sentido trabalharmos com páginas dedicadas, que podem ter donos em outras equipes, evitando um frontend monolítico onde todos precisam se envolver. Basicamente, se tornam micro frontends.
Apesar de ainda ser possível compartilhar estados através das “props” do web component, há uma separação mais clara entre o universo Angular e o React. Mas de qualquer forma, optamos por criar componentes de páginas inteiras, ao invés de componentes reutilizáveis. Dessa forma, aumentamos ainda mais a separação com o legado, pois não há necessidade de compartilhar estado. Claro, há exceções, como o compartilhamento da sessão. Nesses casos, por serem poucos e para evitar acoplamento, utilizamos localStorage.
Pontos de atenção
Até aqui, vimos que essa é uma abordagem que traz diversos benefícios e pode ser utilizada em praticamente qualquer estratégia de refatoração de aplicações frontend, independe das frameworks envolvidas. Mas como sempre é o caso em tecnologia, há trade-offs.
Trabalhar com os novos componentes através de uma dependência no React gera fricção. Para testar um novo componente rodando no legado, é preciso usar algo como yarn link, o que também pode gerar suas próprias dores de cabeça.
Além disso, a aplicação provavelmente continuará dependendo do legado para coisas como roteamento (se isso for gerenciado pelo client). Para contornar isso, é necessário inverter a situação, com o React utilizando os Web Components criados a partir do Angular — o que no nosso caso era impraticável.
Ainda no exemplo do roteamento, coisas como menus de navegação também são mais complicadas de migrar dependendo de como foram implementadas. Em geral, as partes mais estruturais da aplicação acabam sendo as últimas a serem refatoradas nessa estratégia.
Caso opte por seguir como seguimos, ou seja, refatorando páginas inteiras quando uma nova feature deve ser implementada em tal, você precisará argumentar o porquê de algo simples demandar mais esforço. Por isso é importante que todos os stakeholders tenham ciência da refatoração antes de iniciá-la. Caso esse tempo extra não seja factível, uma estratégia diferente talvez seja mais adequada.
Venha enfrentar desafios conosco
Levando em conta os trade-offs apresentados, a abordagem utilizada serviu bem no contexto da Hash. Como resultado, tivemos mais autonomia nos times de frontend, a evolução da Hashboard aconteceu de forma mais simples e escalável e foi possível até migrar para uma nova arquitetura sem muitas complicações.
Para continuar acompanhando o desenvolvimento desse projeto e ficar por dentro das demais novidades e estratégias do nosso time de tecnologia, é só ficar de olho aqui no Tech Blog.
E para fazer parte da nossa equipe e nos ajudar a construir projetos como esse, é só conferir as vagas abertas na Hash clicando aqui!
Autoria
Lucas Fujicava, Paulo Vitor, Filipe Marins