Construindo um aplicativo rápido semelhante ao Next.js com o Bun

Construindo um aplicativo rápido semelhante ao Next.js com o Bun
AdsTerra, Junte-se ao AdsTerra

Neste tutorial, usaremos o empacotador Bun para criar um aplicativo de blog rápido, semelhante ao Next.js, com renderização pelo lado do servidor (SSR) e hidratação pelo lado do cliente. O empacotador Bun, que ainda está em Beta até o momento desta escrita, foi projetado para velocidade e experiência do desenvolvedor. É uma alternativa interessante para empacotadores populares como webpack, esbuild e Vite.

Também exploraremos a nova funcionalidade de Macros em JavaScript do Bun, que faz parte da integração mais próxima que o Bun busca entre seu empacotador e tempo de execução para aumentar a velocidade. As Macros do Bun permitem que funções em JavaScript sejam executadas no momento do empacotamento.

Pré-requisitos

Para acompanhar este tutorial, você precisará dos seguintes itens:

  • Node.js v14 ou posterior instalado em sua máquina
  • npm; geralmente isso é incluído junto com o Node.js
  • CURL; você pode instalá-lo com o seguinte comando: sudo apt install curl
  • Um entendimento básico de TypeScript, React e princípios de desenvolvimento web será benéfico, mas não é obrigatório.

O que é o Bun?

O Bun é um sofisticado runtime JavaScript equipado com APIs da Web embutidas, incluindo Fetch e WebSockets, entre muitas outras. Ele incorpora o JavaScriptCore, um motor conhecido por sua velocidade e eficiência de memória, mesmo que geralmente seja mais desafiador de incorporar em comparação com motores populares como o V8.

O Bun é projetado para acelerar o processo de desenvolvimento JavaScript a velocidades sem precedentes. Como uma ferramenta abrangente, o Bun não apenas aprimora as taxas de compilação e análise, mas também vem com seu próprio conjunto de ferramentas para gerenciamento de dependências e empacotamento. Isso torna o Bun uma solução completa e centralizada para desenvolvedores que buscam otimizar seu fluxo de trabalho e melhorar a eficiência.

O que é o empacotador Bun?

O empacotador Bun é um empacotador nativo rápido que faz parte do ecossistema do Bun. Ele foi projetado para reduzir a complexidade do JavaScript, fornecendo uma API de plugin unificada que funciona tanto com o empacotador quanto com o tempo de execução do Bun. Isso significa que qualquer plugin que estenda as capacidades de empacotamento do Bun também pode ser usado para estender as capacidades de tempo de execução do Bun.

O empacotador Bun foi projetado para ser rápido, com benchmarks mostrando que ele é significativamente mais rápido do que outros empacotadores populares. Ele também oferece uma ótima experiência para desenvolvedores, com uma API projetada para ser clara e previsível.

O empacotador Bun oferece suporte a uma variedade de tipos de arquivo e sistemas de módulos, e possui suporte embutido para tree shaking, source maps e minificação. Ele também possui suporte experimental para Componentes de Servidor React.

Construindo um projeto semelhante ao Next.js

Para demonstrar como usar o empacotador Bun e as Macros do Bun, construiremos um aplicativo de blog semelhante ao Next.js com renderização pelo lado do servidor e hidratação pelo lado do cliente, empacotaremos-o com o empacotador e o serviremos com o servidor embutido do Bun.

Criando um novo projeto Bun

Para começar, vamos usar a biblioteca React SSR para criar um novo projeto SSR:

bun create react-ssr

Em seguida, vamos navegar até a pasta do projeto e executar a aplicação:

cd react-ssr
bun install
bun run dev

Após executar o comando acima, a aplicação Bun será executada em http://localhost:3000

Vamos dar uma olhada nos arquivos importantes neste projeto recém-criado:

  • dev.tsx: Este arquivo é fundamental no processo de desenvolvimento. Ele constrói uma versão do navegador de todas as páginas por meio de Bun.build. Quando o servidor de desenvolvimento está ativo, ele responde a solicitações recebidas renderizando a página correspondente do diretório "pages" em HTML estático. A saída HTML inclui uma tag <script> que referencia uma versão empacotada do arquivo hydrate.tsx.
  • hydrate.tsx: O papel principal deste arquivo é revitalizar o HTML estático enviado pelo servidor, garantindo uma experiência suave e dinâmica para o usuário no frontend.
  • pages/*.tsx: Este diretório compreende várias páginas que seguem as convenções de roteamento do Next.js; o sistema roteia as solicitações recebidas com base nas páginas definidas neste diretório.

Entendendo a SSR e a hidratação

Quando falamos sobre aplicações web modernas, dois termos que frequentemente surgem são renderização pelo lado do servidor (SSR) e hidratação. Vamos dar uma olhada mais detalhada para obter uma compreensão melhor:

SSR: Em aplicações web tradicionais, a renderização geralmente ocorre no lado do cliente, mas com a SSR, o servidor desempenha um papel mais proativo. Quando um usuário faz uma solicitação, o servidor pré-renderiza a página em HTML e envia esse HTML estático como resposta. Isso resulta em tempos de carregamento inicial mais rápidos e melhor desempenho em SEO. Em nosso projeto, o arquivo dev.tsx lida com esse processo, garantindo que a página apropriada no diretório "pages" seja convertida em HTML estático para as solicitações recebidas.

Hidratação: Embora a SSR forneça a velocidade inicial, não queremos que nosso aplicativo permaneça estático; queremos que ele seja interativo. É aqui que entra a hidratação. Após a SSR enviar a página HTML estática para o navegador, o JavaScript associado (em nosso caso, o arquivo hydrate.tsx) é executado para "hidratar" essa página estática, anexando ouvintes de eventos e tornando-a totalmente interativa. Isso cria uma transição perfeita de uma página estática para um aplicativo dinâmico sem recarregar o navegador.

Juntos, a SSR e a hidratação nos proporcionam o melhor dos dois mundos - um tempo de carregamento inicial rápido com uma experiência de usuário dinâmica e rica.

Criando páginas

O Bun adota uma abordagem de roteamento baseada em sistema de arquivos, semelhante ao Next.js. Aqui, vamos atualizar o arquivo react-ssr/pages/index.tsx para buscar alguns blogs da API JSONPlaceholder. Em seguida, criaremos outra página para lidar com a criação de novas postagens de blog.

Vamos começar atualizando o arquivo react-ssr/pages/index.tsx com o seguinte código:

import { useEffect, useState } from "react";
import { Layout } from "../Layout";
import { IBlog } from "../interface";

export default function Home() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    async function getPosts() {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      const posts = await res.json();
      console.log(posts);
      setPosts(posts);
    }
    getPosts();
  }, []);

  return (
    <Layout title="Home">
      <div className="posts-container">
        <a href="/posts">Criar Nova Postagem</a>
        {posts?.map((post: IBlog) => (
          <article key={post.id} className="post-article">
            <h2 className="post-title">{post.title}</h2>
            <p className="post-content">{post.body}</p>
          </article>
        ))}
      </div>
    </Layout>
  );
}

Aqui, o useEffect busca as postagens da API sempre que o componente Home é montado. As postagens são armazenadas no estado local do componente e são exibidas na tela. Isso garante que os dados exibidos estejam sempre atualizados toda vez que o componente for renderizado, tornando-o adequado para dados que são atualizados com frequência.

Agora, vamos criar uma interface IBlog na pasta react-ssr/interface e adicionar o seguinte código:

export interface IBlog {
    id: string;
    title: string;
    body: string;
}

Criando uma página de postagem

Para permitir que os autores criem novas postagens, precisaremos construir uma página de postagem. Vamos criar um arquivo posts/index.tsx na pasta react-ssr/pages/, da seguinte forma:

import { Layout } from "../../Layout";
export default function CreatePost() {
  return <Layout title="Criar uma nova Postagem"></Layout>;
}

Nesta página inicial, estamos importando o componente Layout que provavelmente fornece a estrutura básica da página, incluindo o cabeçalho e o rodapé. A propriedade title é passada para o componente Layout para definir o título da página.

Adicionando interatividade

Para dar vida ao nosso blog, precisaremos adicionar alguns elementos interativos. Vamos começar com um formulário simples na nossa página posts/index.tsx para coletar informações para novas postagens. Como não temos um backend para esta aplicação, vamos armazenar as postagens na API JSONPlaceholder:

import { useState } from "react";
import { Layout } from "../../Layout";
import { IBlog } from "../interface";

export default function CreatePost() {
  const [title, setTitle] = useState("");
  const [body, setBody] = useState("");

  const handleSubmit = async (e: any) => {
    e.preventDefault();
    const id = Math.random().toString(36).substr(2, 9);
    const newPost: IBlog = { id, title, body };

    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      body: JSON.stringify(newPost),
      headers: {
        "Content-type": "application/json; charset=UTF-8",
      },
    });

    const data = await response.json();
    console.log(data);

    setTitle("");
    setBody("");
  };

  return (
    <Layout title="Criar uma nova Postagem">
      <div>
        <form onSubmit={handleSubmit}>
          <label>
            Título:
            <input
              type="text"
              value={title}
              onChange={(e) => setTitle(e.target.value)}
            />
          </label>
          <label>
            Conteúdo:
            <textarea
              value={body}
              onChange={(e) => setBody(e.target.value)}
            />
          </label>
          <button type="submit">Enviar</button>
        </form>
      </div>
    </Layout>
  );
}

O código acima define um componente funcional React chamado CreatePost. Ele usa o Hook useState para gerenciar o estado local do título e do conteúdo de uma nova postagem, inicialmente definindo ambos como uma string vazia. Também configura uma função handleSubmit para ser usada quando o formulário for enviado, embora ainda não tenhamos implementado o envio do formulário.

A função de renderização do componente retorna um formulário com dois campos de entrada, para o título e o conteúdo, e um botão de envio. O estado do título e do conteúdo está vinculado aos seus respectivos campos de entrada, com seus estados sendo atualizados sempre que o valor do campo de entrada é alterado.

Em seguida, vamos atualizar a função handleSubmit no arquivo posts/index.tsx para armazenar a nova postagem:

...
import { IBlog } from 'interface/IBlog';
...
const handleSubmit = async (e: { preventDefault: () => void; }) => {
  e.preventDefault();
  const id = Math.random().toString(36).substr(2, 9);
  const newPost: IBlog = { id, title, body };
  // Enviar uma solicitação POST para a API JSONPlaceholder
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    body: JSON.stringify(newPost),
    headers: {
      'Content-type': 'application/json; charset=UTF-8',
    },
  });
  const data = await res.json();
  console.log(data);
  setTitle("");
  setBody("");
};
...

A função handleSubmit que mencionamos anteriormente é um manipulador de eventos para envios de formulários. Primeiro, ela impede o evento padrão do formulário; em seguida, gera um ID único e cria uma nova postagem com o título e o corpo do estado.

A nova postagem é então enviada para a API JSONPlaceholder por meio de uma solicitação POST. A resposta do servidor é registrada no console e o formulário é redefinido, limpando os estados do título e do conteúdo.

Adicionando estilos

Agora, vamos adicionar algum estilo ao nosso aplicativo de blog para torná-lo visualmente atraente. Vamos atualizar o arquivo public/index.css para estilizar todos os componentes do aplicativo, assim:

.posts-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 2rem auto;
  padding: 1rem;
}

.post-article {
  width: 80%;
  margin-bottom: 2rem;
  border: 1px solid #ddd;
  border-radius: 10px;
  padding: 1rem;
  box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}

Essas regras de estilo afetam os elementos com as classes .posts-container e .post-article. A classe .posts-container aplica um layout flex com direção de coluna e alinhamento central dos itens. Também define margens e preenchimento para dar espaçamento adequado. A classe .post-article define a aparência das postagens individuais, incluindo largura, margens, bordas arredondadas e sombra.

Usando as Macros do Bun

As Macros do Bun são um recurso poderoso que nos permite substituir partes do nosso código por outro código durante o tempo de construção. Isso pode ser útil para várias situações, como otimizar o desempenho, remover código que não é necessário em uma compilação específica ou melhorar a legibilidade do código.

Em nosso aplicativo de blog, usaremos as Macros do Bun para substituir a URL da API por uma chave de armazenamento local quando estivermos executando testes. Não ter que fazer chamadas reais para a API durante os testes tornará nossos testes mais rápidos e confiáveis.

Primeiro, criaremos um arquivo macro.ts e, em seguida, usaremos a função createMacro do bun.macro para criar a macro. Neste tutorial, criaremos uma macro que substitui a URL da API por uma chave de armazenamento local:

export const apiUrl = () => {
  if (process.env.NODE_ENV === 'test') {
    return 'localStorageKey';
  } else {
    return 'https://jsonplaceholder.typicode.com/posts';
  }
};

Agora, podemos usar essa macro em nossa função handleSubmit:

...
import { apiUrl } from './macro.ts' with { type: 'macro' }
... 
const handleSubmit = async (e: { preventDefault: () => void; }) => {
  e.preventDefault();
  const id = Math.random().toString(36).substr(2, 9);
  const newPost: IBlog = { id, title, body };
  // Usar a macro apiUrl
  const res = await fetch(apiUrl(), {
    method: 'POST',
    body: JSON.stringify(newPost),
    headers: {
      'Content-type': 'application/json; charset=UTF-8',
    },
  });
  const data = await res.json();
  setTitle("");
  setBody("");
};
...

Com isso, nosso aplicativo de blog está completo! Usamos o empacotador Bun para criar um aplicativo de blog rápido, semelhante ao Next.js, com renderização pelo lado do servidor e hidratação pelo lado do cliente.

Conclusão

Neste tutorial, demonstramos como configurar um projeto semelhante ao Next.js usando o Bun, criar um aplicativo de blog simples com renderização pelo lado do servidor e hidratação pelo lado do cliente, empacotá-lo usando o empacotador Bun e servir usando o servidor embutido do Bun. Também incorporamos as Macros do Bun em nosso projeto; no nosso caso, para melhorar a velocidade e a confiabilidade dos nossos testes.

O empacotador Bun é uma ferramenta poderosa que pode ajudar a reduzir a complexidade de seus projetos JavaScript e melhorar a velocidade de desenvolvimento. Sua integração com o tempo de execução do Bun e seu suporte a uma ampla variedade de tipos de arquivo e sistemas de módulos o tornam uma ferramenta versátil para qualquer desenvolvedor JavaScript. Se você está construindo um aplicativo simples do lado do cliente ou um aplicativo completo do lado do servidor, o empacotador Bun tem os recursos e o desempenho para atender às suas necessidades.

Sugestões de cursos

Descubra o caminho para se tornar um especialista em programação web. Aprenda HTML, CSS, JavaScript e os principais frameworks nesta jornada emocionante. Com instrutores experientes e materiais práticos, você desenvolverá habilidades práticas para criar sites impressionantes e aplicativos interativos. Impulsione sua carreira na indústria de tecnologia e abra portas para oportunidades de emprego lucrativas. Garanta sua vaga hoje mesmo e inicie sua jornada para se tornar um desenvolvedor web de sucesso.

Método Para Aprender a Programar do Absoluto ZERO com Node.js, React e React Native.

Curso de Node.js, React e React Native

As tecnologias ensinadas no curso são responsáveis por muitas vagas no mercado de trabalho.

Além da alta demanda, os salários vão de R$47.000,00 até R$197.000,00 anuais tendo empresas que possibilitam o trabalho remoto e até vagas Internacionais.

Para que você possa estar apto a preencher uma dessas vagas eu vou te apresentar o passo a passo para você se tornar um verdadeiro expert nessas tecnologias.

O curso te dará o passo a passo de como criar estruturar de um sistema do zero com Node.js, React e React Native.

Saiba mais sobre o curso de Node.js, React e React Native.

AdsTerra, Junte-se ao AdsTerra