Decidi reproduzir o novo NLW Setup da Rocketseat... usando Vue.js
Tags: vue, nuxt, nitro, nlw
Published: 2023-01-22 14:32:05

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](https://nitro.unjs.io/) é 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.](https://dev.to/s1d3a/uma-simples-api-rest-com-nitro-prisma-mongodb-5c5)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5zpf97pmfd4qcp2dfcbw.png)

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.

```ts
//   /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 };
});

```
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:
{% embed https://github.com/silvercent011/nlw-setup-nitro %}
## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8froqoc2np9kb4rpzdc7.png)

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`.
```js
// 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")],
};
```
##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](https://vitejs.dev/), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ds7nvyxpsyiv9h6ivay4.png)

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

![Vue folders](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezddkzd9bhtaj948xge0.png)

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>`.
```vue
/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>
```
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.
```vue
<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>
```
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.
```vue
<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>
```
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.
```Vue
 <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>
```
##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](https://headlessui.com/).
O Headless possui versões para React e Vue, é mantido pelo time do Tailwind.

![Headless Dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a465btoxgafnifqjc4im.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ctm1bsavgm026q3k0x8x.png)
##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`.

```vue
/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>
```
![v-model exemplo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d07g7b7g57exsai6ggoq.png)

Se pontuar tudo, o texto ficará maior do que já está. Quem quiser ver a implementação completa, só abrir o repositório abaixo:
{% embed https://github.com/silvercent011/nlw-setup-vue %}

##Nitro + Vue = Nuxt
O Nitro é o servidor que [vem incluído por padrão no Nuxt 3](https://nuxt.com/docs/guide/concepts/server-engine). 
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbxq4h6lwou892chhwwx.png)
###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](https://tailwindcss.nuxt.dev/) e do [módulo do HeadlessUI](https://github.com/P4sca1/nuxt-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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3hzf4ump4iki08cbc4ci.png)
Exemplo da cor de background que está customizada nas configurações do Tailwind.
![Tailwind viewer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kuwtg5ffjczilhcprvty.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxghmz3wcyebc06qm67r.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uun0e0gdemytsgnoe0i4.png)

A implementação completa no repositório abaixo:
{% embed https://github.com/silvercent011/nlw-setup-nuxt %}

Até a próxima! 🤓