Rails e Vite
Se você deseja apenas usar Vite como substituto do Webpacker integrado ao Rails (como era no Rails 6), basta usar Vite Ruby.
O objetivo deste artigo é diferente: ter 2 ambientes separados (Rails API e React frontend) usando o Vite, mas com algum grau de integração:
- você pode usar o Puma em produção para servir o aplicativo React (ou seja, apenas um contêiner)
- você pode usar
node
para automatizar qualquer outra tarefa no aplicativo Rails - iniciar ambos os servidores com um único comando
Requisitos
Estou assumindo que você já tenha:
- Node.js (verifique as Notas de Compatibilidade do Vite)
- Rails
Se você ainda não instalou nenhum, você pode tentar usar o nvm
e o rvm
ou dar uma olhada no meu post anterior sobre o asdf
.
Configuração do Rails
Vamos chamar nosso aplicativo de blogger
, então vamos começar criando a Rails API com:
rails new blogger --api
cd blogger
rails g scaffold Post title:string content:text
rails db:migrate
Seed do Banco de Dados
Crie um único post, adicionando no final do arquivo db/seeds.rb
:
Post.create!(title: "Primeiro post", content: "Este é o primeiro post!")
Agora é só rodar rails db:seed
.
CORS
Como estaremos executando dois servidores diferentes em portas diferentes, o CORS vai bloquear as solicitações do frontend para a API.
Vamos alterar isso pra permitir as chamadas vindas do frontend.
Abra o Gemfile
e descomente (ou adicione ao final) a seguinte linha:
gem "rack-cors"
Agora instale a gem:
bundle install
Pra permitir que o frontend consulte a API, vamos editar o arquivo config/initializers/cors.rb
adicionando no final a seguinte configuração:
# TODO: Add production URLs to the list
# Use '*' if you want expose the API to the world
accept = [
'http://127.0.0.1:5173', # Dev Frontend
'http://localhost:5173', # Dev Frontend
'http://127.0.0.1:5174', # Dev Frontend
'http://localhost:5174', # Dev Frontend
]
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins accept
resource '*',
headers: :any,
methods: %i[get post put delete options patch],
expose: %w[Authorization]
end
end
Configuração do Foreman para o Backend
Mais adiante usaremos o Foreman para iniciar todos os servidores. Seguindo a documentação dele que recomenda que não seja incluído no Gemfile, apenas instale a gem com:
gem install foreman
Crie um arquivo chamado Procfile
na pasta raiz do projeto com este conteúdo:
rails: bundle exec rails s -p 3000
Execute bundle install
.
Pronto: o backend está configurado. Hora de instalar e configurar o frontend.
Bônus: todas os passos acima em uma tacada só:
rails new blogger --api
cd blogger
rails g scaffold Post title:string content:text
rails db:migrate
echo 'Post.create!(title: "First post", content: "This is the first post!")' >> db/seeds.rb
rails db:seed
bundle add rack-cors
bundle install
printf '\n' >> config/initializers/cors.rb
wget https://gist.githubusercontent.com/raelgc/799cadce1303bd604a38bc3808d272b4/raw/1b401713a728178ed4ab86e061bbe1b0cc4d5033/cors.rb -O ->> config/initializers/cors.rb
gem install foreman
echo 'rails: bundle exec rails s -p 3000' >> Procfile
Configuração do Vite
Inicie o Vite na pasta raiz do projeto usando o template para React + TypeScript:
npm create vite@latest . -- --template react-ts
Se este é o primeiro aplicativo Vite que você está criando, vai aparecer esse prompt (responda y
):
Need to install the following packages:
create-vite@5.1.0
Ok to proceed? (y)
Depois disso, o configurador vai perguntar o que fazer com os arquivos existentes na pasta do projeto (responda “Ignore files and continue”):
? Current directory is not empty. Please choose how to proceed: › - Use arrow-keys. Return to submit.
Remove existing files and continue
Cancel operation
❯ Ignore files and continue
Instale todos os pacotes configurados:
npm install
Movendo o Conteúdo do Frontend
Crie a pasta para o aplicativo React e mova os arquivos relacionados ao Vite:
mkdir react
mv src react/
mv public react/
mv index.html react/
mv vite.config.ts react/
mkdir public && touch public/.keep
Atualize o tsconfig.node.json
para indicar o novo caminho substituindo a linha "include": ["vite.config.ts"]
por:
"include": ["react/vite.config.ts", "react/src/**/*.d.ts"]
No arquivo package.json
, atualize os comandos para apontar para a pasta react
, substituindo a seção scripts
pelo seguinte:
"scripts": {
"dev": "vite react",
"build": "tsc && vite build react && touch public/.keep",
"lint": "eslint react --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview react"
},
Atualize react/vite.config.ts
para fazer o build usando a pasta react/src
e publicar o conteúdo gerado na pasta public
do Rails (que sempre estará limpa, exceto pelo arquivo .keep
), adicionando no final o seguinte dentro da função defineConfig
:
// Cole essas linhas dentro de defineConfig
build: {
emptyOutDir: true,
outDir: '../public'
},
Também atualize o arquivo tsconfig.json
substituindo a linha "include": ["src"],
por:
"include": ["react/src"],
Configuração o Foreman para o Frontend
Como o Foreman já está instalado, vamos apenas incluir o script dev
no Foreman (ou seja, adicione ao final do Procfile
):
web: npm run dev
A configuração do frontend está pronta! Agora vamos fazer algumas requisições para o backend.
Requisições para a API
Instale o axios para fazermos requisições HTTP a nossa API:
npm install --save axios
Edite o arquivo react/src/main.tsx
e configure a URL base padrão do axios
, colocando o seguinte conteúdo acima da linha ReactDOM.createRoot
:
// ... importações existentes acima
import axios from 'axios';
axios.defaults.baseURL = import.meta.env.VITE_APP_API_URL || 'http://127.0.0.1:3000';
Se você usar a API em uma URL diferente, defina uma variável de ambiente chamada VITE_APP_API_URL
. Veja como o Vite funciona com as variáveis de ambiente.
Vamos atualizar nosso aplicativo React para listar todos os posts. Substitua o conteúdo inteiro de react/src/App.tsx
por:
import { useState, useEffect } from 'react'
import axios from 'axios'
import './App.css'
type Post = {
id: number,
title: string,
content: string,
}
function App() {
const [posts, setPosts] = useState<Post[]>([])
useEffect(() => {
axios.get('/posts').then(response => setPosts(response.data)).catch(console.error)
}, [])
return (
posts.length === 0
? <p>No posts</p>
: (
<>
<h1>Posts</h1>
{posts.map(post => (
<>
<h2>{post.title}</h2>
<p>{post.content}</p>
</>
))}
</>
)
)
}
export default App
Servindo Páginas Estáticas com o Servidor Rails (Puma)
Em um ambiente de produção, geralmente o frontend será servido pelo nginx. Mas é possível ter o Rails servindo tanto o backend quanto o frontend.
Adicione a gem rails-static-router
ao Gemfile
:
# Serve static index as catch-all route
gem "rails-static-router"
Instale-a:
bundle install
Adicione ao config/routes.rb:
if ENV["RAILS_SERVE_STATIC_FILES"].present?
get "*path", to: static("index.html")
post "*path", to: static("index.html")
end
Scripts
- Iniciar o backend:
rails s
- Iniciar o frontend:
npm run dev
- Iniciar ambos (já que o
foreman
tá instalado):foreman start
- Empacotar para produção:
npm run build
Se você quiser testar usando o rails server
como servidor web de produção (normalmente seria o nginx) para testar:
- Execute primeiro o script “Compilar para produção”
- Em seguida, execute:
RAILS_SERVE_STATIC_FILES=1 RAILS_ENV=production rails s