A próxima transição da Web: Novas arquiteturas trazem novos frameworks

A web é composta por tecnologias que começaram há mais de 25 anos. HTTP, HTML, CSS e JS foram padronizados pela primeira vez em meados dos anos 90 (quando eu tinha 8 anos). Desde então, a web evoluiu para uma plataforma de aplicativos onipresente. À medida que a web evoluiu, também evoluiu a arquitetura para o desenvolvimento desses aplicativos. Atualmente, existem muitas arquiteturas principais para criar aplicativos para a Web. A arquitetura mais popular empregada por desenvolvedores da Web hoje é o Single Page App (SPA), mas estamos fazendo a transição para uma arquitetura nova e aprimorada para criar aplicativos da Web.

Os elementos e estão por aí desde o início. Links para um navegador obter coisas de um servidor e formulários para um navegador enviar coisas para um servidor (e obter coisas em troca). Com essa comunicação bidirecional estabelecida como parte da especificação desde o início, foi possível criar aplicações poderosas na web desde sempre.

Aqui estão as principais arquiteturas (em ordem cronológica de uso popular):

  1. Aplicativos de várias páginas (MPAs)

Cada arquitetura de desenvolvimento web tem benefícios e pontos problemáticos. Eventualmente, os pontos problemáticos se tornaram um problema suficiente para motivar a mudança para a próxima arquitetura que veio com suas próprias compensações.

Não importa como construímos nossos aplicativos, quase sempre precisamos de código em execução em um servidor (exceções notáveis ​​incluem jogos como o Wordle, que costumava armazenar o estado do jogo no local storage). Uma das coisas que distingue essas arquiteturas é onde o código reside. Vamos explorar cada um deles e observar como a localização do código mudou ao longo do tempo. Conforme abordamos cada arquitetura, consideraremos especificamente os seguintes casos de uso de código:

  • Persistência: Salvar e ler dados de um banco de dados

Há, naturalmente, mais partes de um aplicativo da web do que esses pedaços, mas esses são os pedaços que mais se movimentam e onde passamos a maior parte do nosso tempo como desenvolvedores da web. Dependendo da escala do projeto e da estrutura da equipe, podemos trabalhar em todas essas categorias de código ou em apenas uma parte.

Aplicativos de Várias Páginas (MPAs)

No início, essa era a única arquitetura que funcionava na web com base nos recursos dos navegadores da época.

Com aplicativos de várias páginas, todo o código que escrevemos está no servidor. O código de feedback da interface do usuário no cliente é manipulado pelo navegador do usuário.

Comportamentos de Arquitetura de MPAs

Solicitação de documentos: Quando o usuário insere uma URL na barra de endereços, o navegador envia uma solicitação ao nosso servidor. Nossa lógica de roteamento chamará uma função para buscar dados que se comunica com o código de persistência para recuperar os dados. Esses dados são usados ​​pela lógica de renderização para determinar o HTML que será enviado como resposta ao cliente. Enquanto isso, o navegador está dando feedback ao usuário com algum tipo de estado pendente (normalmente na posição do favicon).

Solicitação de mutações: Quando o usuário envia um formulário, o navegador serializa o formulário em uma solicitação enviada ao nosso servidor. Nossa lógica de roteamento chamará uma função que se comunicam com o código de persistência para alterar os dados e fazer atualizações no banco de dados. Em seguida, ele responderá com um redirecionamento para que o navegador acione uma solicitação GET para obter uma nova interface do usuário (que acionará a mesma coisa que aconteceu quando o usuário inseriu a URL para começar). Novamente, o navegador fornecerá feedback ao usuário com interface do usuário pendente.

Observação: é importante que as mutações bem-sucedidas enviem uma resposta de redirecionamento ao invés de apenas o novo HTML. Caso contrário, você terá a solicitação POST em sua pilha de histórico no navegador e pressionar o botão “Voltar” acionará a solicitação POST novamente (você já se perguntou por que os aplicativos às vezes dizem “NÃO APERTE O BOTÃO VOLTAR!!” Sim, é por isso. Eles deveriam ter respondido com um redirecionamento).

Prós e Contras de MPAs

O modelo mental dos MPAs é simples. Nós não apreciamos isso naquela época. Embora houvesse algum estado e fluxos complicados manipulados principalmente por cookies nas solicitações, na maioria das vezes tudo acontecia dentro do tempo de um ciclo de solicitação/resposta.

Onde esta arquitetura falha:

  1. Atualizações de página inteira: Tornando algumas coisas difíceis (gerenciamento de foco), outras impraticáveis ​​(imagine uma atualização de página inteira toda vez que curtimos um tweet) e algumas coisas simplesmente impossíveis (transições de página animadas).

É notável que a plataforma web esteja melhorando constantemente com a proposta de API de transições de página, que torna os MPAs uma opção mais viável para mais casos de uso. Mas para a maioria dos aplicativos da web, isso ainda não é suficiente. De qualquer forma, na época esse problema estava longe das mentes dos comitês de padrões e agora, nossos usuários querem mais!

Aplicativos de várias páginas progressivamente aprimorados (PEMPAs)

Progressive Enhancement é a ideia de que nossos aplicativos da Web devem ser funcionais e acessíveis a todos os navegadores da Web e, em seguida, aproveitar quaisquer recursos extras que o navegador tenha para aprimorar a experiência. O termo foi cunhado em 2003 por Nick Finck e Steve Champeon, falando dos recursos do navegador.

XMLHttpRequest foi inicialmente desenvolvido pela equipe do Outlook Web Access da Microsoft em 1998, mas não foi padronizado até 2016 (você acredita nisso!?). É claro que isso nunca impediu os fornecedores de navegadores e desenvolvedores da web antes. AJAX foi popularizado como um termo em 2005 e muitas pessoas começaram a fazer solicitações HTTP no navegador. As empresas foram construídas com base na ideia de que não precisamos voltar ao servidor para obter mais do que pequenos pedaços de dados para atualizar a interface do usuário na hora. Com isso, poderíamos criar aplicativos de várias páginas progressivamente aprimorados:

“Uau!”, você pode estar pensando, “espere um minuto… de onde veio todo esse código?”. Portanto, agora não apenas assumimos a responsabilidade pelo feedback da interface do usuário no navegador, mas também temos roteamento, busca de dados, mutação de dados e lógica de renderização no lado do cliente , além do que já tínhamos no servidor. “E daí?”

Bem, aqui está o negócio. A idéia por trás do aprimoramento progressivo é que nossa linha de base deve ser um aplicativo funcional. Especialmente no início dos anos 2000, não podíamos garantir que nosso usuário usaria um navegador capaz de executar nosso novo e sofisticado material AJAX ou que estaria em uma rede rápida o suficiente para baixar nosso JavaScript antes de interagir com nosso aplicativo. Portanto, precisávamos manter a arquitetura MPA existente e usar apenas JavaScript para aprimorar a experiência.

Dito isso, dependendo do nível de aprimoramento de que estamos falando, podemos realmente ter que escrever código em quase todas as nossas categorias, a exceção sendo a camada de persistência de dados (a menos que desejemos suporte ao modo offline, o que é realmente legal, mas não é um padrão da indústria na prática, por isso não está incluído no gráfico).

Além disso, tivemos que adicionar mais código ao back-end para dar suporte às solicitações AJAX que nosso cliente faria. Portanto, mais em ambos os lados da rede.

Esta é a era do jQuery, MooTools, etc.

Comportamentos de Arquitetura de PEMPAs

Solicitação de documentos: Quando o usuário solicita o documento pela primeira vez, ocorre aqui a mesma coisa que no exemplo do MPA. No entanto, um PEMPA também carregará o JavaScript no lado do cliente incluindo tags <script> que serão usadas para os recursos de aprimoramento.

Navegação no lado do cliente: Quando o usuário clica em um elemento âncora com um href que está em nosso aplicativo, nosso código de busca de dados do lado do cliente evita o comportamento padrão de atualização de página inteira e usa JavaScript para atualizar a URL. Em seguida, a lógica de roteamento do cliente determina quais atualizações precisam acontecer na interface do usuário e executa manualmente essas atualizações, incluindo a exibição de quaisquer estados pendentes (feedback na interface do usuário) enquanto a biblioteca de busca de dados faz uma solicitação de rede para um endereço do servidor. A lógica de roteamento do servidor chama o código de busca de dados para recuperar dados do na camada de persistência de dados e envia isso como uma resposta (como XML ou JSON, podemos escolher 😂) que o cliente usa para executar as atualizações finais da interface do usuário com sua lógica de renderização.

Solicitação de mutações: Quando o usuário envia um formulário, nossa lógica de mutação de dados do lado do cliente impede a atualização padrão de página inteira e o comportamento de POST e usa JavaScript para serializar o formulário e enviar os dados para um endereço do servidor. A lógica de roteamento do servidor então chama a função de mutação de dados, que interage com o código de persistência de dados para realizar a mutação e responde com os dados atualizados ao cliente. A lógica de renderização do cliente usará esses dados atualizados para atualizar a interface do usuário, conforme necessário; em alguns casos, a lógica de roteamento do lado do cliente enviará o usuário para outro local que aciona um fluxo semelhante ao fluxo de navegação do lado do cliente.

Prós e Contras de PEMPAs

Definitivamente, resolvemos os problemas com os MPAs trazendo o código para o lado do cliente e assumindo a responsabilidade do feedback da interface do usuário. Temos muito mais controle e podemos dar aos usuários uma sensação de aplicativo mais personalizado.

Infelizmente, para dar aos usuários a melhor experiência que eles procuram, temos que ser responsáveis ​​pelo roteamento, busca de dados, mutações e lógica de renderização. Existem alguns problemas com isso:

  1. Comportamento padrão: Não fazemos um trabalho tão bom quanto o navegador com roteamento e envio de formulários. Manter os dados da página atualizados nunca foi uma preocupação antes, mas agora é mais da metade do nosso código no lado do cliente. Além disso, race conditions, reenvios de formulários e tratamento de erros são ótimos lugares para os bugs se esconderem.

Em uma nota pessoal, foi nessa época que entrei no mundo do desenvolvimento web. Lembro dessa época com um misto de saudade saudade e arrepio de medo 🍝.

Aplicativos de página única (SPAs)

Não demorou muito para percebermos que poderíamos remover os problemas de duplicação se apenas excluíssemos o código da interface do usuário do back-end. Então foi isso que fizemos:

Você notará que este gráfico é quase idêntico ao PEMPA. A única diferença é que a lógica de renderização desapareceu. Parte do código de roteamento também desapareceu porque não precisamos mais ter rotas para interface do usuário. Tudo o que nos resta são as rotas da API. Esta é a era do Backbone, Knockout, Angular, Ember, React, Vue, Svelte, etc. Esta é a estratégia usada pela maior parte da indústria hoje.

Comportamentos de Arquitetura de SPAs

Como o back-end não possui mais lógica de renderização, todas as solicitações de documentos (a primeira solicitação que um usuário faz ao inserir nossa URL) são atendidas por um servidor de arquivos estático (normalmente um CDN). Nos primórdios dos SPAs, esse documento HTML era quase sempre um arquivo HTML efetivamente vazio com um <div id="root"></div> no <body> que seria usado para “montar” o aplicativo. Hoje em dia, no entanto, os frameworks nos permitem pré-renderizar qualquer quantidade da página que tivermos no tempo de build ou deploy usando uma técnica conhecida como “Static Site Generation” (SSG).

Os outros comportamentos nesta estratégia são os mesmos dos PEMPAs. Só que agora usamos principalmente fetch ao invés de XMLHttpRequest.

Prós e Contras de SPAs

O que é interessante é que a única diferença entre SPAs e PEMPAs relacionados a suas arquiteturas é que a solicitação de documento é pior! Então, por que fizemos isso!?

De longe, o maior ganho aqui é a experiência do desenvolvedor. Essa foi a força original para a transição de PEMPAs para SPAs em primeiro lugar. Não ter duplicação de código foi um benefício enorme. Justificamos essa mudança por vários meios (afinal, o DX é uma entrada para o UX). Infelizmente, melhorar o DX é tudo o que os SPAs realmente fizeram por nós.

Lembro-me de estar pessoalmente convencido de que a arquitetura SPA ajudava no desempenho percebido porque um CDN poderia responder com um documento HTML mais rápido do que um servidor poderia gerar um, mas em cenários do mundo real isso nunca pareceu fazer diferença (e isso é ainda menos verdadeiro graças a infraestrutura moderna). A triste realidade é que os SPAs ainda têm os mesmos outros problemas que os PEMPAs, embora com ferramentas mais modernas tornam as coisas muito mais fáceis de lidar.

Para piorar a situação, os SPAs também introduziram vários novos problemas:

  1. Tamanho dos pacotes/arquivos finais: Meio que explodiu. Leia mais sobre o impacto do JavaScript no desempenho de uma página da Web neste artigo completo no Web Almanac.

As bibliotecas foram criadas para ajudar a resolver esses problemas e reduzir seu impacto. Isso tem sido incrivelmente útil, mas alguns diriam que a rotatividade é cansativa. Isso se tornou a maneira padrão de fato de criar aplicativos da web desde meados da década de 2010. Estamos bem na década de 2020 e há algumas novas ideias no horizonte.

Aplicativos de página única progressivamente aprimorados (PESPAs)

MPAs têm um modelo mental simples. SPAs têm recursos mais poderosos. Pessoas que passaram pelo estágio MPA e estão trabalhando em SPAs realmente lamentam a simplicidade que perdemos na última década. Isso é particularmente interessante se você considerar o fato de que a motivação por trás da arquitetura SPA foi principalmente melhorar a experiência do desenvolvedor em relação aos PEMPAs. Se pudéssemos, de alguma forma, mesclar SPAs e MPAs em uma única arquitetura para obter o melhor de ambos, podemos ter algo que seja simples e mais capaz. Isso é o que os aplicativos de página única aprimorados progressivamente são.

Considere que, com o Progressive Enhancement, a linha de base é um aplicativo funcional, mesmo sem JavaScript do lado do cliente. Portanto, se nossa estrutura permite e incentiva o Aprimoramento Progressivo como um princípio fundamental, a base de nosso aplicativo vem com a base sólida do modelo mental simples de MPAs. Especificamente, o modelo mental de pensar nas coisas no contexto de um ciclo de solicitação/resposta. Isso nos permite eliminar em grande parte os problemas dos SPAs.

Isso é importante: o principal benefício do Progressive Enhancement não é que “seu aplicativo funciona sem JavaScript” (embora esse seja um bom benefício colateral), mas sim que o modelo mental é drasticamente mais simples.

Continue lendo…

Para fazer isso, os PESPAs precisam “emular o navegador” quando impedem o comportamento padrão. Portanto, o código do servidor funciona da mesma maneira, independentemente de o navegador estar fazendo a solicitação ou seja uma solicitação de busca baseada em JavaScript. Embora esse código ainda exista, podemos manter o modelo mental simples no restante de nosso código. Uma parte importante disso é que os PESPAs emulam o comportamento do navegador de revalidar dados na página quando são feitas mutações para manter os dados na página atualizados. Com os MPAs, temos que recarregar a página inteira. Com PESPAs, essa revalidação acontece em cada solicitações de busca.

Lembre-se de que também tivemos um problema significativo com os PEMPAs: duplicação de código. Os PESPAs resolvem esse problema tornando o código de renderização da interface do back-end e o código de renderização de interface do front-end extamento o mesmo. Ao usar uma biblioteca de interface do usuário capaz de fazer ambos: renderizar seus dados no servidor e criar a interativa/manipulação de atualizações no cliente, não temos problemas de duplicação de código.

Você notará que há pequenas caixas para busca, mutação e renderização de dados. Esses pedaços são para aprimoramento. Por exemplo, estados pendentes, interface do usuário otimista, etc. realmente não têm lugar no servidor, portanto, teremos algum código que será executado apenas no cliente. E usando as bibliotecas de interface do usuário modernas, a colocação que obtemos o torna sustentável.

Comportamentos de Arquitetura de PESPAs

Solicitação de documentos: Com PESPAs são efetivamente idênticas às PEMPAs. O HTML inicial necessário para o aplicativo é enviado diretamente do servidor e o JavaScript também é carregado para aprimorar a experiência de interação do usuário.

Navegação do lado do cliente: quando o usuário clicar em um link, impediremos o comportamento padrão. Nosso roteador determinará os dados e a interface do usuário necessários para a nova rota e acionará a busca de dados para quaisquer dados que a próxima rota precise e renderizará a interface do usuário que é renderizada para essa rota.

Solicitações de mutação: Você notou que esses dois gráficos são iguais? Sim! Isso não é um acidente! As mutações com PESPAs são feitas por meio de envios de formulários. Chega de colocar isso em onClick + fetch (no entanto, mutações imperativas são boas para aprimoramento progressivo, como redirecionar para a tela de login quando a sessão do usuário expirar). Quando o usuário enviar um formulário, impediremos o comportamento padrão. Nosso código de mutação serializa o formulário e o envia como requisição para a rota associada ao action do formulário (que tem como padrão a URL atual). A lógica de roteamento no back-end chama a função action que se comunica com o código de persistência para executar a atualização e envia de volta uma resposta bem-sucedida (por exemplo: uma curtição de tweet) ou redireciona (por exemplo: criando um novo repositório GitHub). Se for um redirecionamento, o roteador carrega código/dados/recursos para essa rota (em paralelo) e aciona a lógica de renderização. Se não for um redirecionamento, o roteador revalida os dados para a UI atual e aciona a lógica de renderização para atualizar a UI. Curiosamente, independentemente de ser uma mutação direta ou um redirecionamento, o roteador está envolvido, fornecendo o mesmo modelo mental para ambos os tipos de mutações.

Prós e Contras de PESPAs

PESPAs eliminam muitos problemas de arquiteturas anteriores. Vamos vê-los um por um:

Questões em MPAs:

  1. Atualizações de página inteira: PESPAs impedem o comportamento padrão e, em vez disso, usam JS do lado do cliente para emular o navegador. Da perspectiva do código que escrevemos, isso não parece diferente de um MPA, mas da perspectiva do usuário, é uma experiência muito melhor.

Questões em PEMPAs:

  1. Impedir o comportamento padrão: Um aspecto central dos PESPAs é que eles devem se comportar da mesma maneira que o navegador em relação ao roteamento e formulários. É assim que eles conseguem nos dar o modelo mental de uma MPA. Cancelar solicitações de reenvio de formulários, lidar com respostas fora de ordem adequadamente para evitar problemas de race conditions e revelar erros para evitar spinners que nunca desaparecem fazem parte do que torna uma PESPA uma PESPA. É aqui que um framework realmente ajuda.

Problemas de SPA:

  1. Tamanho dos pacotes/arquivos finais: Ir para um PESPA requer um servidor, o que significa que podemos mover uma tonelada de nosso código para o back-end. Tudo o que a interface do usuário precisa é de uma pequena biblioteca de interface do usuário que possa ser executada no servidor e no cliente, algum código para lidar com as interações e feedback da interface do usuário e o código para os componentes. E graças à divisão do código baseado em URLs/rotas, podemos finalmente dizer adeus às páginas da Web com centenas de KBs de JS. Além disso, devido ao aprimoramento progressivo, a maior parte do aplicativo deve funcionar antes que o carregamento do JS seja concluído. Além disso, há esforços em frameworks JS para reduzir ainda mais a quantidade de JS necessária no cliente.

É importante ressaltar que PESPAs não funcionarão exatamente da mesma forma com e sem JavaScript do lado do cliente. De qualquer forma, esse nunca é o objetivo do aprimoramento progressivo. Só que a maior parte do aplicativo deve funcionar sem JavaScript. E não é apenas porque nos preocupamos com a experiência do usuário sem JavaScript. É porque, ao visar o aprimoramento progressivo, simplificamos drasticamente nosso código de interface do usuário. Você ficaria surpreso com o quão longe podemos chegar sem JS, mas para alguns aplicativos simplesmente não é necessário ou prático ter tudo funcionando sem JavaScript do lado do cliente. Mas ainda podemos colher os principais benefícios dos PESPAs, mesmo que alguns de nossos elementos de interface do usuário exijam algum JavaScript para operar.

O que distingue uma PESPA:

  • Funcional é a linha de base, JS é usado para aprimorar, não habilitar

Quanto aos contras. Ainda estamos descobrindo quais são. Mas aqui estão alguns pensamentos e reações iniciais:

Muitos que estão acostumados com SPAs e SSG lamentarão que agora tenhamos código do lado do servidor executando nosso aplicativo. No entanto, para qualquer aplicativo do mundo real, não podemos evitar o código do lado do servidor. Certamente, existem alguns casos de uso em que podemos criar o site inteiro uma vez e colocá-lo em um CDN, mas a maioria dos aplicativos nos quais trabalhamos diariamente não se encaixa nessa categoria.

Relacionado a isso, as pessoas estão preocupadas com o custo do servidor. A ideia é que o SSG nos permite construir nosso aplicativo uma vez e depois podemos colocar em uma CDN para um número quase infinito de usuários a um custo muito baixo. Há duas falhas nessa crítica. 1) Provavelmente estamos acessando APIs em nosso aplicativo, então esses usuários ainda estarão acionando muitos de nossos códigos de servidor mais caros em suas visitas de qualquer maneira. 2) CDNs oferecem suporte a mecanismos de cache HTTP, portanto, se formos realmente capazes de usar SSG, podemos definitivamente fazer uso disso para fornecer respostas rápidas e limitar a quantidade de trabalho com o qual nosso servidor de renderização está lidando.

Outro problema comum que as pessoas têm ao deixar SPAs é que agora temos que lidar com os desafios de renderização no servidor. Este é definitivamente um modelo diferente para pessoas acostumadas a executar seu código apenas no lado do cliente, mas se estivermos usando frameworks que cuidam disso e levam isso em consideração, dificilmente será um desafio. Se não, pode ser definitivamente um desafio grande, mas existem soluções alternativas razoáveis ​​para forçar determinado código a ser executado apenas no lado do cliente enquanto migramos.

Como eu disse, ainda estamos descobrindo os contras dos aplicativos de página única aprimorados progressivamente, mas acho que os benefícios valem muito a pena comparado aos contras.

Também devo mencionar que, embora tenhamos os recursos de uma arquitetura PESPA por algum tempo com as ferramentas existentes, o foco no Progressive Enhancement, ao mesmo tempo em que compartilhamos o código da lógica de renderização, é novo. Este post está interessado principalmente em demonstrar as arquiteturas padrões em aplicativo web, não apenas os recursos da plataforma.

Uma implementação PESPA: Remix

Liderando a busca de PESPAs está o Remix, um framework web com foco a laser nos fundamentos da web e na experiência moderna do usuário. Remix é o primeiro framework web a oferecer tudo o que descrevi como uma oferta PESPA. Outros frameworks podem e estão se adaptando para seguir o exemplo do Remix nisso. Estou especificamente ciente de que SvelteKit e SolidStart trabalham nos princípios PESPA em suas implementações. Imagino que mais virão (novamente, os meta-frameworks são capazes de arquitetura PESPA há algum tempo, no entanto, o Remix colocou essa arquitetura em primeiro plano e outros estão seguindo a idéia). Veja como as coisas ficam quando temos um framework web para o nosso PESPA:

Nesse caso, o Remix atua como uma ponte na rede. Sem o Remix, teríamos que implementar isso nós mesmos para ter um PESPA completo. O Remix também lida com nosso roteamento por meio de uma combinação de roteamento baseado em convenção e baseado em configuração. O Remix também ajudará com os pedaços de aprimoramento progressivo de nossas buscas e mutações de dados (como o botão de curtir do Twitter) e o feedback da interface do usuário para implementar coisas como estados pendentes e interface do usuário otimista.

Graças ao roteamento aninhado incorporado ao Remix, também obtemos uma melhor organização do código (algo que o Next.js também está buscando). Embora o roteamento aninhado não seja necessário para a arquitetura PESPA especificamente, a divisão de código baseada em rota é uma parte importante. Além disso, obtemos uma divisão de código muito mais granular com roteamento aninhado, por isso é um aspecto importante.

O Remix está demonstrando que podemos nos divertir mais criando melhores experiências mais rapidamente com a arquitetura PESPA. E acabamos com situações como esta:

Uma de desempenho perfeita no Lighthouse por padrão? Pode me chamar!

Conclusão

Pessoalmente, estou pronto aqui para esta transição. Obter um UX e DX melhores ao mesmo tempo é uma vitória sólida. Acho que é importante e estou animado com o que o futuro nos reserva. Como recompensa por terminar esta postagem no blog, criei um repositório que demonstra todo esse código se movendo através dos tempos usando um aplicativo TodoMVC! Veja ele aqui: kentcdodds/the-webs-next-transformation. Espero que ajude a tornar algumas das ideias mais concretas.

E é isso que estou animado para ensinar a você aqui no EpicWeb.dev. Se você gostaria de acompanhar meu progresso aqui, coloque seu e-mail na caixa de formulário. Vamos tornar a web melhor 🎉

Valeu!

Para uma visão mais detalhada da história da construção de aplicativos para a web, leia “The new wave of Javascript web frameworks” por Frontend Mastery.

Para saber mais sobre aprimoramento progressivo, leia “Progressively enhance for a more resilient web”, de Jenna Smith.

Créditos

--

--

☕🇳🇿 - https://eduardorabelo.me

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store