DEV Community

Cover image for Decidi reproduzir o novo NLW Setup da Rocketseat... usando Vue.js
Sidney Alex
Sidney Alex

Posted on

Decidi reproduzir o novo NLW Setup da Rocketseat... usando Vue.js

A Rocketseat é uma das comunidades referência no ensino de JavaScript no Brasil. No backend eles variam bastante, mas no frontend eles são 100% focados na dupla React+React Native e os metaframeworks que surgem dessas ferramentas. O NLW é um evento que acontece algumas vezes por ano, onde durante uma semana é feito o desenvolvimento de algumas aplicações, sempre um backend, um frontend e uma aplicação mobile.
O Vue.js sempre foi o meu framework de estimação, mesmo utilizando React e Angular no trabalho. Então tomei a decisão acompanhar essa edição do evento, mas adicionando uma camada de complexidade: reproduzir as aplicações utilizando Vue e outras ferramentas disponíveis no ecossistema Vue/Nuxt/unJS.
Além do desafio, é uma oportunidade bastante justa de comparar os frameworks ao nível de código, arquitetura e funcionalidades.

Backend: Substituindo o Fastify pelo Nitro

O backend construído para o desafio recebe e envia dados no formato JSON, então nada que o Nitro não possa fazer. O Nitro é o servidor embutido no Nuxt 3 e que assim como outras ferramentas do unJS, pode ser utilizado isoladamente sem nenhuma outra dependência, como explico nesse post.

A instalação de outras bibliotecas como Prisma, Day.js e Zod segue o mesmo padrão encontrado no evento e em suas respectivas documentações. O que muda ao utilizar o Nitro é o sistema de rotas, baseado em arquivos e a construção das rotas, que utiliza eventos no lugar do clássico (request,response).
Sistema de rotas do nitro

As rotas separadas em arquivos ajudam bastante na organização do código e nos obrigam a separar o código específico de cada uma. Abaixo exemplo de uma rota com o Nitro.

//   /routes/day/index.get.ts

import dayjs from "dayjs";
import { z } from "zod";
import { prisma } from "../../utils/prisma";

export default defineEventHandler(async (event) => {
  const getDayParams = z.object({
    date: z.coerce.date(),
  });

  const query = getQuery(event);

  const { date } = getDayParams.parse(query);

  const parsedDate = dayjs(date).startOf("day");

  const weekDay = parsedDate.get("day");

  const possibleHabits = await prisma.habit.findMany({
    where: {
      created_at: {
        lte: date,
      },
      weekDays: {
        some: {
          week_day: weekDay,
        },
      },
    },
  });

  const day = await prisma.day.findUnique({
    where: {
      date: parsedDate.toDate(),
    },
    include: {
      dayHabits: true,
    },
  });

  const completedHabits =
    day?.dayHabits.map((dayHabit) => dayHabit.habit_id) ?? [];

  return { possibleHabits, completedHabits };
});

Enter fullscreen mode Exit fullscreen mode

De forma geral, o backend não é tão diferente. Boa parte da aplicação, principalmente o uso do Prisma, Zod e Day.js é exatamente igual.
Link do repositório completo abaixo:

Tailwind CSS: A configuração que (quase) não mudou.

Por ser um framework do CSS, o Tailwind se torna compatível e visualmente idêntico em todos os frameworks.

Aplicação com Vue

O que mudei no tailwind.config.js se resume a adição dos arquivos com extensão .vue e a instalação do plugin oficial @tailwindcss/forms.

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: {
        background: "#09090A",
      },
      gridTemplateRows: {
        7: "repeat(7, minmax(0,1fr))",
      },
    },
  },
  plugins: [require("@tailwindcss/forms")],
};
Enter fullscreen mode Exit fullscreen mode

Frontend: A briga começa aqui!

Não detalharei as diferenças básicas entre os frameworks, existem milhares de artigos na web fazendo isso. Deixarei o link do repositório antes de falarmos sobre Nuxt(esse é o tópico surpresa por aqui).

A aplicação web no evento da Rocketseat foi gerada utilizando Vite, sendo incrível já que o Vite é mantido pelo core team do Vue. Significa que os passos para criar o projeto são os mesmos, detalhe para a nova CLI do Vue, baseada no Vite e que está disponível para seleção nas opções do comando yarn create vite

CLI do Vite

A estrutura de pastas é a mesma do projeto com React, ambos os frameworks usam arquivos de componentes(SFC) em sua arquitetura.

Vue folders

A composição dos SFCs difere, no JSX os componentes são funções ou classes do JavaScript. No Vue o SFC possui separação entre o template, script e estilo. O único obrigatório é o <template>.

/src/App.vue
<template>
  <div class="w-screen h-screen flex justify-center items-center">
    <div class="w-full max-w-5xl px-6 flex flex-col gap-16">
      <Header />
      <SummaryTable />
    </div>
  </div>
</template>

<script setup lang="ts">
import Header from "@/components/Header.vue";
import SummaryTable from "@/components/SummaryTable.vue";
</script>

<style scoped>
</style>
Enter fullscreen mode Exit fullscreen mode

Entrando no componente SummaryTables, vemos algumas diferenças entre as duas ferramentas. A primeira delas é a renderização de listas. No JSX precisamos chamar o método map no objeto ou array que queremos iterar e retornar um novo componente a partir do callback que passamos. No Vue isso é resolvido com o atributo v-for no elemento que queremos iterar.

<template>
    <!-- ... -->
      <div
        v-for="(day, index) in weekDays"
        :key="index"
        class="text-zinc-400 text-xl h-10 w-10 font-bold flex items-center justify-center"
      >
        {{ day }}
      </div>
    <!-- ... -->
</template>

<script setup lang="ts">
// ...
const weekDays = ["D", "S", "T", "Q", "Q", "S", "S"];
// ...
</script>
Enter fullscreen mode Exit fullscreen mode

No quesito renderização de listas, ponto para o Vue. Mas nem sempre ser mais simples significa ser melhor. No mesmo componente, temos outro exemplo onde o React leva a melhor.

<template>
    <!-- ... -->
      <HabitDay
        v-for="date in summaryDates"
        :key="date.toString()"
        :date="date"
        :amount="dayInSummary(date)?.amount"
        :defaultCompleted="dayInSummary(date)?.completed"
      />
    <!-- ... -->
</template>

<script setup lang="ts">
// ...
const summaryDates = generateDatesFromYearBeginning();

// ...
function dayInSummary(date: any) {
  const value = summary.value.find((day) =>
    dayjs(date).isSame(day.date, "day")
  );
  return value;
}
</script>
Enter fullscreen mode Exit fullscreen mode

No JSX cada iterável tem o componente gerado de uma função anônima passado no método map. A função dayInSummary no projeto com React é declarada e executada na função anônima. A solução mais rápida que encontrei foi declarar a função na tag <scrit> e chamar nos valores do HabitDay, com certeza existe uma solução melhor, mas não gastei neurônios aqui. Pela flexibilidade, ponto do React aqui.

Um ponto levantado durante as aulas é o uso do hook useEffect para disparar funções quando o componente é montado. Não é proibido, mas o hook não foi feito para resolver esse problema em específico. Enquanto existem RFCs na comunidade React debatendo isso, no Vue podemos utilizar o composable onMounted para essa finalidade.

 <template>
<!-- ... -->
</template>

<script setup lang="ts">
// ...
const summary = ref<Summary>([]);

onMounted(async () => {
  const data = await api.get("summary").then((response) => response.data);
  summary.value = data;
});
// ...
</script>
Enter fullscreen mode Exit fullscreen mode

Substituindo o Radix pelo HeadlessUI

O Radix infelizmente não possui versão para Vue, mas sem desespero. Podemos substituir componentes como Dialog e Popover tranquilamente com o HeadlessUI.
O Headless possui versões para React e Vue, é mantido pelo time do Tailwind.

Headless Dialog

Não é 100%, mas resolve o problema. Componentes como Checkbox e Progress podem ser feitos de forma simples utilizando apenas o Vue e Tailwind.

Dialog

Formulários e Two-Way Data Binding

Aqui o Two-Way Data Binding do Vue ganha destaque simplificando algumas operações. Representado pelo atributo v-model, o Two-Way Data Binding faz quase que automaticamente algumas operações onde precisamos aplicar alguma regra de negócio. O destaque nessa aplicação é no formulário de recorrência de dias, onde o Vue monta o array com os dias da semana selecionados quase automaticamente.

Iteramos os dias da semana, e colocamos o index como valor do checkbox. O v-model vai e manipular o array armazenado em weekDays com o que passamos no atributo value.

/src/components/NewHabitForm.vue
<template>
  <!-- ... -->
  <div class="flex flex-col gap-2 mt-3">
    <div
      class="flex items-center gap-3"
      v-for="(weekDay, index) in availableWeekDays"
      :key="index"
    >
      <div>
        <input
          class="..."
          type="checkbox"
          :value="index"
          v-model="weekDays"
        />
      </div>
      <span class="text-white leading-tight"> {{ weekDay }} </span>
    </div>
  </div>
  <!-- ... -->
</template>

<script setup lang="ts">
// ...
const availableWeekDays = [
  "Domingo",
  "Segunda-Feira",
  "Terça-Feira",
  "Quarta-Feira",
  "Quinta-Feira",
  "Sexta-Feira",
  "Sábado",
];
const weekDays = ref<number[]>([]);
// ...
</script>
Enter fullscreen mode Exit fullscreen mode

v-model exemplo

Se pontuar tudo, o texto ficará maior do que já está. Quem quiser ver a implementação completa, só abrir o repositório abaixo:

nlw-setup-vue

This template should help get you started developing with Vue 3 in Vite.

VSCode + Volar (and disable Vetur) + TypeScript Vue Plugin (Volar).

Type Support for .vue Imports in TS

TypeScript cannot handle type information for .vue imports by default, so we replace the tsc CLI with vue-tsc for type checking. In editors, we need TypeScript Vue Plugin (Volar) to make the TypeScript language service aware of .vue types.

If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a Take Over Mode that is more performant. You can enable it by the following steps:

  1. Disable the built-in TypeScript Extension
    1. Run Extensions: Show Built-in Extensions from VSCode's command palette
    2. Find TypeScript and JavaScript Language Features, right click and select Disable (Workspace)
  2. Reload the VSCode window by running Developer: Reload Window from the command palette.

Customize configuration

See Vite

Nitro + Vue = Nuxt

O Nitro é o servidor que vem incluído por padrão no Nuxt 3.
Então, em um projeto Nuxt, criei a pasta server e colei a pasta routes e utils do projeto backend. Você não leu errado, apenas copiei e colei a pasta. Troquei o nome de routes para api. Realizei a configuração do Prisma e pronto! API funcionando dentro do Nuxt.
Nuxt folders

Frontend

O Nuxt 3 possui um sistema automático de importações, então as funções do Vue, tudo que estiver em pastas específicas como components não precisam ser importados, o Nuxt realiza as importações automaticamente.

Tailwind e Headless

Fiz a instalação do módulo oficial do Tailwind para Nuxt e do módulo do HeadlessUI para aproveitar as importações automáticas. Para finalizar copiei o arquivo tailwind.config.js do nosso frontend.

Ao executar o comando dev com o gerenciador de pacotes, o Nuxt inicia o Nitro para backend, Vite para frontend e o módulo do Tailwind habilita uma rota especial que é uma espécie de documentação e exibe modificações que fazemos no arquivo de configurações.
Nuxt Terminal
Exemplo da cor de background que está customizada nas configurações do Tailwind.
Tailwind viewer

Copiando as pastas dentro do src do frontend, e instalando as bibliotecas restantes, podemos realizar algumas substituições.

Substituindo o Axios pelos composables nativos do Nuxt 3

O Nuxt 3 possui composables(hooks para quem vem do React) nativos que realizam requisições otimizadas. As rotas que declaramos na pasta server/api são tipadas automaticamente.
rotas tipadas
A resposta da api também é tipada pelo Nuxt, que suporta Top-level await, então matamos o estado vazio inicial e o composable onMouted de todos os componentes, substituindo apenas por um await na tag <script>.
resposta tipada

A implementação completa no repositório abaixo:

Nuxt 3 Minimal Starter

Look at the Nuxt 3 documentation to learn more.

Setup

Make sure to install the dependencies:

# yarn
yarn install

# npm
npm install

# pnpm
pnpm install
Enter fullscreen mode Exit fullscreen mode

Development Server

Start the development server on http://localhost:3000

npm run dev
Enter fullscreen mode Exit fullscreen mode

Production

Build the application for production:

npm run build
Enter fullscreen mode Exit fullscreen mode

Locally preview production build:

npm run preview
Enter fullscreen mode Exit fullscreen mode

Check out the deployment documentation for more information.




Até a próxima! 🤓

Oldest comments (1)

Collapse
 
cn-2k profile image
cn-2k

Post incrível, também fiz essa edição mas em Vue.js, nem pensei no Nuxt. Achei muito bacana essa ideia do Nitro, vou começar a estudar melhor e esse post vai me ajudar bastante, obrigado!