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)
.
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 };
});
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.
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")],
};
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
A estrutura de pastas é a mesma do projeto com React, ambos os frameworks usam arquivos de componentes(SFC) em sua arquitetura.
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>
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>
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>
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>
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.
Não é 100%, mas resolve o problema. Componentes como Checkbox
e Progress
podem ser feitos de forma simples utilizando apenas o Vue e Tailwind.
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>
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.
Recommended IDE Setup
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:
- Disable the built-in TypeScript Extension
- Run
Extensions: Show Built-in Extensions
from VSCode's command palette - Find
TypeScript and JavaScript Language Features
, right click and selectDisable (Workspace)
- Run
- 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.
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.
Exemplo da cor de background que está customizada nas configurações do Tailwind.
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.
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>
.
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
Development Server
Start the development server on http://localhost:3000
npm run dev
Production
Build the application for production:
npm run build
Locally preview production build:
npm run preview
Check out the deployment documentation for more information.
Até a próxima! 🤓
Top comments (1)
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!