Desenvolvimento local para produção, o que muda?

Webpack e React — Entendendo o processo de deploy

Hoje em dia, durante o desenvolvimento de aplicação Reacts, é comum usarmos o webpack-dev-server para um server http simples, podendo até usar o middleware para Express, facilitando nossa vida com HMR e outras ferramentas para desenvolvimento.

Porém, quando chega o momento de fazer o deploy da sua aplicação, seja para um ambiente de teste ou de produção, você geralmente se pergunta:

E agora, o que eu preciso?

Entendendo o seu servidor corretamente

Primeiro, você precisa saber qual o tipo de servidor / host você irá usar. Existe uma regra geral — se o seu app precisa de renderização no servidor, dê preferência para um servidor node. Em outros casos, você pode fazer o deploy da sua aplicação em qualquer setup — apache, nginx, express ou usar uma CDN como host, como Amazon S3. Nesse artigo irei focar na última opção, nós não iremos implementar renderização no servidor.

Escolhendo esse método, nosso app — do ponto de vista do servidor — são apenas arquivos estáticos, assim como era em 1990 antes do PHP invadir a cidade. Nosso processo de deploy será básico, vamos imaginar que estamos “apenas colocar os arquivos no servidor”.

Existe alguns casos especiais, que iremos falar mais adiante.


Preparando seu app para produção

Existem várias coisas que você pode arrumar e configurar para gerar um build para produção, porém, o mais importante deles é:

minificar e uglificar os seus assets

Isso irá comprimir o seu JS, deixando-o o menor possível — você pode usar o UglifyJsPlugin (que também irá processar CSS, apenas do nome):

// webpack.config.js
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}

Você pode ver uma extensiva lista de opções que você pode passar para o UglifyJsPlugin no repositório do Github.


Configurando variável de ambiente em modo de produção

Isso irá dizer a todas as suas dependências que o processo atual está sendo rodado em modo de produção, e o resultado deve ser o mais otimizado possível. Você também pode usar essa variável dentro da sua aplicação (para injetar ferramentas de debug apenas em desenvolvimento, mudar endpoints para teste/produção, etc):

Se você usa Webpack 1.x

module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
}

Para Webpack 2.x

module.exports = {
plugins: [
new webpack.DefinePlugin({
new webpack.EnvironmentPlugin({
NODE_ENV: ‘production’
})
})
]
}

Essa configuração irá ser recebida pelas suas dependências, e irá otimizar o tamanho e performance de algumas bibliotecas. Por exemplo, em React, quando rodamos em modo de produção, ele não irá mais avisar que o seu Componente está recebendo uma props errada.

Por isso que é importante não depender de propTypes para validações com fins de segurança, dentro da sua aplicação.


Desabilitando source-maps

Esse é bem simples, tenha certa de não incluir a chave devtool na sua configuração de produção do Webpack. Ou faça uso de cheap-module-source-map, que irá te dizer apenas os nomes de arquivos e linha.

Essa opção ainda irá adicionar a referência ao source-maps no seu arquivo JS, lembre-se que, se você fazer o upload disso para produção, qualquer pessoa pode inspecionar detalhes da sua aplicação. E, caso você ainda use devtool mas não faça o upload do source-maps, isso resultará em um request 404.


Tem como ter isso tudo automático?

Ter, tem. Você pode rodar webpack -p, isso resultará em um processo de build otimizado para produção.

Para projetos pequenos, é até válido. Mas conforme seu projeto cresce, eu recomendo você entender e ter uma configuração manualmente configurada.


E depois de tudo isso?

Depois dessas mudanças, você deve notar uma diferença em tamanho nos arquivos finais gerado pelo Webpack. Permitindo que você sirva arquivos otimizados e menores da sua aplicação.

Agora você pode simplesmente copiar os arquivos gerados para o seu servidor.


Problemas comuns após deploy

Existe alguns problemas comuns que eu percebi participando do chat do Reactiflux, alguns deles são:

Página em branco ao invés da minha aplicação

Podemos chamar isso de “erro geral” — algo terrível aconteceu e sua aplicação falhou antes mesmo de iniciar.

Com calma, abra seu navegador, vá até o developer tools, e verifique se existe algum erro no console — existe uma grande chance da URL do seu bundle estar errada, e você pode não estar carregando o arquivo.

Isso é comum quando você está desenvolvendo localmente e coloca os arquivos em um servidor, e simplesmente espera que eles sejam carregados do http://seu-dominio.com/pasta-assets, porém, em desenvolvimento local você simplesmente usa http://localhost:8080, então, a URL do seu bundle se parece com:

<script src="/bundle.js"></script>

Isso é fácilmente resolvido atualizando manualmente as referências no seu index.html ou, como é recomendado no Webpack, utilizar o module.output.publicPath, para atualizar as URLs, includingo imagens e outros assets que você tiver.

react-router não funciona se eu digitar a URL manualmente

Outro caso bem comum, relacionado ao browserHistory, nesse caso, sua aplicação irá precisar de uma configuração diferente no servidor. Básicamente, quando você digita a URL manualmente, por padrão, o servidor irá tentar procurar por um arquivo, com aquele caminho digitado, no disco do servidor — se não for encontrado, ele irá mostrar um erro 404.

O que você realmente quer que aconteça internamente, é que todas as URLs sejam redirecionadas para o index da sua aplicação.

Você pode conferir melhor a documentação do react-router v3 sobre isso (não se preocupe, a validação ainda funciona para react-router v4).

Aqui vai algumas configurações comum, começando pelo Apache:

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

E aqui para nginx:

server {
...
location / {
try_files $uri /index.html;
}
}

Tem também para Express:

const express = require('express')
const path = require('path')
const port = process.env.PORT || 8080
const app = express()
app.use(express.static(__dirname + '/public'))
app.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
})
app.listen(port)
console.log("Server started on port " + port)

Não consigo ver mudanças após deploy de uma nova versão da minha aplicação

Isso é causado pelo fato de navegadores realizarem cache de assets por padrão. Isso pode ser resolvido limpando o cache, porém, você não espera que os seus usuários façam isso. Um jeito fácil de se resolver é usando uma técnica chamada cache busting, que garante que o nome (ou hash) do arquivo requisitado seja diferente caso haja alguma mudança.

Isso pode ser resolvido com no Webpack, utilizando a chave output.filename com a opção [hash]:

module.exports = {
output: {
filename: "[name].[hash].js"
}
}

Desse modo, seu comando de build irá gerar um novo de arquivo único, por exemplo, bundle.fr56ob123gb.js, você pode conferir nesse link as opções disponíveis no Webpack.

Requisições AJAX não funcionam mais

É 99.9% de chance do seu problema ser relacionado com CORS.

Abra o seu developer tools e verifique a aba Network (ou o console), para ver se o seu request falhou com algum erro do tipo.

CORS é um mecanismo de servidor para garantir que seus assets não sejam requisitados por domínios diferentes.

Apesar de ser uma proteção importante, pode ser um problema quando estamos trabalhando com aplicações JS que realizam fetch para uma API em um domínio/porta diferente.

Existem algumas soluções:

  • Tenha certeza que sua API está configurada com CORS para o domínio da sua aplicação
  • Tenha certeza que sua aplicação e API estejam rodando no mesmo domínio, protocolo e porta
  • Crie um proxy que irá rodar no mesmo domínio da aplicação, que aceitará todos os requests e irá se comunicar com sua API por baixo dos panos.

Créditos