Criando Micro-frontends com Ragu, React e VueJS com Server Side Rendering

Maniero
5 min readSep 11, 2020

Micro-frontends é uma idéia fascinante que conheci no Tech Radar da ThoughtWorks. Trazer a mentalidade de micro-serviços para o frontend permite que equipes tenham autonomia para trabalhar de ponta-a-ponta e resolver problemas de negócio com pouca ou idealmente nenhuma dependência.

Se você já está familiarizado com o termo você certamente já viu essa imagem que ilustra um pouco o que eu estou falando:

Se nunca viu, recomendo que leia o conteúdo dessa página: https://tautorn.github.io/micro-frontends/ pra entender um pouco mais a fundo o que é.

Faz um tempo já que eu venho flertando com diversas abordagens para construir micro-frontend, vou falar um pouco sobre o que me motivou a criar o Ragu e vou mostrar um exemplo prático usando de um e-commerce desenvolvido usando Ragu.

Direto ao código: O “E-commerce”

O projeto está disponível em https://ragu-ecommerce.herokuapp.com/. Esse projeto é basicamente responsável em gerenciar os micro-frontends. (tenham um pouquinho de paciência. Estou usando o plano grátis da Heroku, provavelmente vai levar alguns segundos até os front-ends serem carregados).

Se você olhar o código-fonte da página inicial, você verá só isso no body:

<header class="ecommerce-header">
<h1>Ragu Pokémon Ecommerce</h1>
<div class="ecommerce-header__cart_count">
<ragu-component
src="
https://ragu-cart-vuejs.herokuapp.com/components/cart"
</ragu-component>

</div>
</header>
<ragu-component
id="main-component"
src="
https://ragu-catalog-react.herokuapp.com/components/featured-products">
</ragu-component>

O <ragu-component/> é um CustomElement que faz o fetch de um componente. Para ilustrar melhor o que é um componente, é legal abrirmos um desses endpoints.

GET https://ragu-cart-vuejs.herokuapp.com/components/cart{
"html":"<div data-server-rendered=\"true\" class=\"cart-cart-cart-count-CXgs\"><div class=\"cart-cart-icon-3Tit\"><svg>...</svg></div> <strong>0</strong></div>",
"props":{
},
"dependencies":[
{
"nodeRequire":"vue",
"globalVariable":"Vue",
"dependency":"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"
}
],
"client":"https://ragu-cart-vuejs.herokuapp.com/component-assets/client.735370104395e93efee6.js",
"resolverFunction":"cartcart"
}

Basicamente a resposta é o HTML que esse componente gera e o resto são instruções para executar os javascripts necessários para habilitar o comportamento dos components.

Como isso funciona?

O desenho abaixo exemplifica um pouco o funcionamento do Ragu. Basicamente o RaguDOM se comunica com o RaguServer através da URL do micro-frontend.

O RaguServer é responsável em realizar o build dos componentes e entregá-los já pré-renderizado do servidor.

Criando o catálogo de produtos em React

O repositório com o código completo está aqui: https://github.com/carlosmaniero/ragu-catalog-react

Basicamente, qualquer componente dentro da pasta /components do projeto sera publicado.

Exemplo:
O component definido em: /components/featured-products/index.tsx
Está publicado em: https://ragu-catalog-react.herokuapp.com/components/featured-products.

A função propsToState é responsável em processar qualquer informação necessária para renderizar o componente e ela é sempre executada do lado do servidor. Se você abrir o projeto e olhar as informações de rede não encontrará a request da PokéAPI.

A renderComponent, como o nome sugere, retorna o component que deve ser renderizado. Nesse caso a <PokemonList /> que recebe o estado da propsToState.

O mais legal é que o <PokeList /> é um componente React comum. Não tem nada de especial nele então você pode usar o Ragu Server com qualquer projeto react.

Criando o carrinho de produtos em Vue

O projeto do carrinho em Vue pode ser encontrado nesse repositório. Não tem nanda de muito diferente com relação ao projeto em react não. O legal desse repositório é que ele tem um mecanismo para ouvir que um produto foi adicionado ao carrinho.

carrinho: /cart/index.jswindow.addEventListener('add-to-cart', (e) => {
app.addProductToCart(e.detail);
});
catálogo: /react-components/pokemon.tsx<Button onClick={(e) => {
e.target.dispatchEvent(
new CustomEvent('add-to-cart', {
detail: pokemon,
bubbles: true
})
)
}}>Add To Cart</Button>

Ragu — Princípios norteadores.

Alguns princípios guiaram muitas das decisões de design e arquitetura do Ragu:

Deploys independentes

É bastante comum organizações criarem um pacote NPM compartilhado entre times. É uma abordagem que funciona. Mas fora o alto acoplamento, essa abordagem requer que todos os projetos que dependem dessa biblioteca sejam “buildadas” novamente quando há uma alteração.

No Ragu cada micro-frontends pode ser deployado de forma isolada optmizando eliminando gargalos.

Agnóstico a tecnologia

Você pode criar seus micro-frontends usando qualquer framework/biblioteca. As únicas limitações são: framework escolhido deve permitir renderizar um componente para string e ter uma forma de hidratar o componente (basicamente fazer bind nos eventos) do html gerado. React, por exemplo, implementa esses mecanismos com ReactDOMServer.renderToString() e ReactDOM.hydrate().

Permite Compartilhar dependências Globais

Se você tiver vários micro-frontends e diferentes squads usam React, você não vai querer que o usuário baixe o react todas as vezes que um micro-frontend for renderizado.

Para resolver esse problema é possível declarar dependências globais, essas dependências não serão adicionada ao bundle dos micro-frontends e serão automaticamente importadas quando o micro-frontend for utilizado.

dependencies: [
{
nodeRequire: 'react',
globalVariable: 'React',
dependency: 'https://react.production.min.js'
},
{
nodeRequire: 'react-dom',
globalVariable: 'ReactDOM',
dependency: 'https://react-dom.production.min.js'
}
]

O que falta?

Rever o sistema de build: Diferentemente do React que foi muito fácil configurar o RaguServer, eu tive bastante dificuldade de configurar o RaguServer para VueJS isso porque eu não conheço tanto a ferramenta, mas acredito que dá pra melhorar um pouco a forma que o build dos componentes é feito. Manja de Vue? Vamos trocar uma ideia?

Criar um setup padrão para os principais frameworks: Eu configurei todos os projetos Ragu Server na mão, isso deu um bocado de trabalho dá pra fazer um CLI pra ajudar nesse processo.

Criar clientes para os principais frameworks: Hoje eu só desenvolvi o RaguDOM que é um cliente usando CustomElements. Pretendo criar clientes para React, Vue e Angular. Assim será possível invocar os micro-frontends usando esses frameworks.

Melhorias no RaguDOM: Quero lançar alguns eventos para quando o carregamento de um componente falhar, por exemplo, bem como eventos de loading.

E também documentar um pouco melhor o projeto.

E por que 🔪 Ragu?

“Você está passando mal com minha fofura? Então aperta o clap ou aguente as consequências.”

Quando eu estava pensando em um nome para dar pro projeto, estava pensando em algo que representasse algo distribuído ou fatiado. Depois de ficar um dia inteiro pensando, eu estava quase desistindo. Deitei na cama e meu gato (que se chama Ragu) veio pedir carinho.

Achei que o nome super funcionava. Afinal ragu é um picadinho (de preferência de cogumelo não mate #GoVegan. hahaha).

--

--