Configurando um projeto Django com React

Configurando um projeto Django com React

O Django é um dos frameworks de desenvolvimento web mais completos disponíveis, com quase tudo o que você precisa para colocar uma aplicação no ar. Ele gerencia tudo, desde o banco de dados até o HTML final enviado ao cliente. Porém, com o advento das single page apps, é cada vez mais comum criar aplicações que utilizam o Django somente para fornecer uma API que responde dados em formato JSON, consumidos por aplicações desenvolvidas nos mais variados frameworks Javascript. Esta arquitetura que separa o front do back-end permite um desacoplamento das duas áreas, com times que podem desenvolver em seus domínios de forma totalmente independente. Além de possibilitar que diversos apps clientes interajam com uma mesma API, garantindo ao mesmo tempo integridade de dados e regras de negócio e uma diversidade de interfaces de usuário.

Dois projetos diferentes porém geram um trabalho a mais: são dois deploys separados, dois ambientes para configurar. Uma forma de simplificar este cenário é usar os recursos que o próprio Django já nos oferece para servir arquivos estáticos. Afinal, a aplicação front-end nada mais é do que um conjunto de arquivos deste tipo.

O objetivo deste post é mostrar passo-a-passo como criar um setup básico para um projeto Django incluindo toda a estrutura para montar uma app em React gerenciada pelo Webpack. Vou mostrar como configurar o Django para fazer este papel de servir tanto a API quanto a aplicação do front.

Para quem tem mais experiência com o ambiente do Python, entender o emaranhado das bibliotecas do front-end pode ser um grande desafio. Há várias formas possíveis de combinar os mesmos recursos para chegar num resultado parecido e as referências que encontramos por aí não costumam ser muito claras sobre o por quê de cada coisa.

Na primeira parte desse post eu pretendo mostrar como fazer o setup necessário no Django para disponibilizar a aplicação do front através da estrutura que ele já tem para servir arquivos estáticos. Além disso, vamos montar um esqueleto básico de uma aplicação em React, focando no propósito de cada elemento dele. Você não vai encontrar aqui uma explicação detalhada de como funciona o React, apenas alguns conceitos sobre o framework necessários para entender a configuração do ambiente. A intenção desse post é possibilitar que alguém que tenha mais experiência no back-end consiga integrar o front com mais facilidade, sem se perder na teia das bibliotecas que compõem o ambiente de desenvolvimento. A partir daí, você pode procurar por outros tutoriais ou buscar mais informações na própria documentação da ferramenta que é bastante completa e bem compreensível.

Django e arquivos estáticos

Numa arquitetura de projeto usando apenas o Django, com a template engine do próprio framework, os estáticos, como arquivos .css e imagens, são armazenados em diretórios especificados na variável de configuração STATICFILES_DIRS. Basta definir este parâmetro e o Django se encarrega de servir os arquivos.

Quando precisamos usar estes recursos nos templates, chamamos a template tag load_static que gera a URL correta com o caminho para o arquivo. No HTML final, a URL aparece com o caminho definido pelo parâmetro STATIC_URL, que por padrão vem setado como /static/.

Um template que contenha uma imagem, por exemplo, seria renderizado da seguinte forma:

O que vamos fazer neste passo-a-passo nada mais é do que configurar um projeto Django para usar uma template tag específica para carregar arquivos gerados pelo Webpack, que vai inserir os links para os recursos de scripts e estilos em um template. Esta página renderizada a partir do template vai conter toda a aplicação Javascript desenvolvida em um diretório separado dentro do projeto Django.

Configurando o projeto

Passo 1

O setup necessário é somente uma instalação padrão do Django, opcionalmente com o REST Framework para a criação da API que servirá os recursos do back-end, e o pacote Django Webpack Loader.

Vamos criar dois novos diretórios além dos criados pelo Django: assets para armazenar todos os nossos recursos do front-end e templates para conter a página inicial da aplicação.

Passo 2

No settings.py, o arquivo de configurações do projeto, vamos adicionar o REST Framework, caso queira usá-lo para construir a API, e o Webpack Loader às INSTALLED_APPS.

project/settings.py

INSTALLED_APPS = [
  ...
  'rest_framework',
  'webpack_loader',
  ...
]

Adicionamos também o diretório de templates à variável de TEMPLATES para que o Django encontre nossos arquivos HTML e o diretório assets ao STATICFILES_DIRS para que os estáticos sejam servidos.

project/settings.py

TEMPLATES = [
    {
        'back-end': 'django.template.back-ends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'assets'),
)

A última configuração necessária é a do Webpack Loader. Ele vai precisar de duas informações:

project/settings.py

WEBPACK_LOADER = {
    'DEFAULT': {
        'BUNDLE_DIR_NAME': 'dist/',
        'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
    }
}

Quanto tivermos terminado as configurações do front-end, o projeto terá a seguinte estrutura:

Tanto dist quanto webpack-stats.json serão gerados pelo Webpack.

Criando o template para a aplicação do front

Nosso usuário, quando acessar o endereço do site, vai fazer isso na raíz do domínio e é nesse ponto que vamos disponibilizar a aplicação React. Para isso, vamos adicionar uma entrada à lista de rotas da aplicação com o template que vai receber os arquivos disponibilizados pelo Webpack.

Passo 3

Primeiramente, vamos criar o arquivo HTML do template:

templates/front-end/index.html

{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Django React Webpack</title>
    <!-- Webpack rendered CSS -->
    {% render_bundle 'main' 'css' %}
</head>

<body>
    <div id="react-app"></div>
    <!-- Webpack rendered JS -->
    {% render_bundle 'main' 'js' %}
</body>
</html>

O que temos neste arquivo é:

Depois de terminado o restante da configuração ao final do passo 5 deste post, o HTML renderizado terá um conteúdo parecido com isto:

<!DOCTYPE html>
<html>
  <head>
      <meta charset="UTF-8">
      <title>Django React Webpack</title>
      <!-- Webpack rendered CSS -->
      <link type="text/css" href="/static/dist/main-ce37ab46c0988b0f0827.css" rel="stylesheet">
  </head>

  <body>
      <div id="react-app"><div></div>
      <!-- Webpack rendered JS -->
      <script type="text/javascript" src="/static/dist/main-ce37ab46c0988b0f0827.js"></script>
  </body>
</html>

Para disponibilizar esta página, vamos criar uma TemplateView diretamente no arquivo de URLs:

project/urls.py

from django.contrib import admin
from django.views.generic import TemplateView
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', TemplateView.as_view(template_name='front-end/index.html')),
]

Normalmente criaríamos uma view dento de um arquivo views.py de uma app, mas como ela tem somente o propósito de renderizar um template sem nenhum objeto de contexto ou lógica associada, apenas adicioná-la à rota deixa explícito que ela não está associada a nenhuma app Django.

Por enquanto, se tentarmos rodar o servidor e abrir a página no browser, veremos uma mensagem de erro informando que não foi encontrado o arquivo webpack-stats.json. Então vamos à configuração do front-end.

Iniciando o projeto do front

Para começar uma aplicação em React, um dos processos mais simples é usar o create-react-app, que já instala e configura todas as bibliotecas necessárias para a aplicação. No entanto, a estrutura gerada por ele tem diversos elementos que não seriam necessários para o nosso caso. Além disso, ele esconde por trás de uma interface muito simples o que está sendo feito. Uma das grandes dificuldades para quem desenvolve originalmente para o back-end é entender o papel das diversas bibliotecas da stack de desenvolvimento Javascript, por isto escolhemos não utilizar este recurso.

Vamos começar inicializando um projeto com o NPM (Node Package Manager), o gerenciador de dependências para ambientes Javascript. Ele se encarrega de baixar, instalar e gerenciar versões de pacotes JS. Se você não tem o NPM instalado e tem dúvidas sobre como fazer isso, pode conferir a documentação ou, buscando no Google, seguir um tutorial com as instruções de instalação para diversas plataformas.

Passo 4

Ao rodar esse comando, vão aparecer diversas perguntas sobre o projeto. Se você não quiser repondê-las por enquanto, basta dar Enter em tudo e, se preferir, pode editar estes dados depois. Você pode também rodar npm init -y que tem o mesmo efeito. Ao final do processo, terá sido criado o arquivo package.json com as respostas que você deu , nele ficarão armazenadas todas as versões de pacotes intalados pelo NPM, assim como os scripts que ele pode executar. Também deve aparecer o diretório node_modules, que armazena as bibliotecas baixadas por ele.

Depois disto vamos criar o diretório src dentro do nosso diretório de assets e, dentro dele, o diretório js. Em assets/src/js, criaremos o arquivo index.js, que será o ponto de entrada da aplicação.

Configurando o Webpack

O Webpack é uma ferramenta para compilar recursos estáticos, tanto scripts quanto folhas de estilo, inclusive nos formatos sass e less, por exemplo. A partir de um arquivo de configuração, ele faz o trabalho de vigiar e compilar os recursos de forma que eles possam ser entregues diretamente para o browser.

Além do próprio Webpack, vamos intalar o plugin Webpack Bundle Tracker, que vai manter um registro do status atual do que está sendo gerenciado por ele.

O parâmetro --save-dev do comando de instalação informa ao NPM que esta é uma dependência de desenvolvimento, ou seja, que este código não faz parte do que será compilado e mandado para o cliente no resultado final, mas servirá de suporte para a construção do código e só precisa estar presente no computador que está sendo usado para desenvolver a aplicação.

O arquivo package.json agora deve ter sido preenchido com o atributo devDependencies contendo as versões destes dois pacotes.

package.json

...
"devDependencies": {
  "webpack": "^3.10.0",
  "webpack-bundle-tracker": "^0.2.1"
}
...

Dica: Se você estiver usando GIT como sistema de controle de versões, adicione ao seu .gitignore o diretório node_modules e o arquivo package-lock.json que serão gerados pelo NPM. Além disso, caso não tenha mudado as configurações de banco de dados do Django e esteja usando o SQLite, inclua também o db.sqlite3.

Passo 5

Com o Webpack instalado podemos criar o arquivo de configuração para ele. É daí que ele vai obter todas as informações sobre onde e como compilar os recursos que ele vai gerenciar.

webpack.config.js

var path = require('path');
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');

module.exports = {
  entry:  path.join(__dirname, 'assets/src/js/index'),

  output: {
    path: path.join(__dirname, 'assets/dist'),
    filename: '[name]-[hash].js'
  },

  plugins: [
    new BundleTracker({
      path: __dirname,
      filename: 'webpack-stats.json'
    }),
  ],

}

O que fizemos aqui foi:

Você pode mudar os nomes dos arquivos e diretórios para o que preferir, só lembre-se de mudar também nas configurações do Webpack Loader no settings.py.

Quando começarmos a compilar os arquivos, se houver uma mensagem de erro no webpack-stats.json e estivermos rodando o servidor com o parâmetro de DEBUG = True, a mensagem será exibida como erro 500 da mesma forma como são exibidos os erros do próprio Django.

Voltando ao package.json, vamos adicionar duas entradas de scripts para facilitar o processo de rodar o Webpack:

package.json

...
"scripts": {
  ...
  "start": "./node_modules/.bin/webpack --config webpack.config.js",
  "watch": "npm run start -- --watch"
},
...

Agora, para compilar uma única vez os estáticos, podemos rodar no termnal o comando npm run start. Já para compilar em tempo real, atualizando o resultado de acordo com cada mudança nos arquivos, utilizaremos o npm run watch.

Se observar a árvore de diretórios do projeto, deve ter aparecido um novo chamado dist com um arquivo no formato main-315df4b2f5abf7716c38.js dentro de assets, além do arquivo webpack-stats.json na raíz do projeto.

Como o “watch” e o servidor do Django rodando já podemos abrir o endereço no browser e verificar que a aplicação está no ar.

Dica: Se ao rodar o npm run watch ele terminar o processo e sair dele, ao invés de continuar aberto esperando mudanças nos arquivos, dê uma olhada aqui. Adicione também dist e o webpack-stats.json ao seu .gitignore caso esteja usando o GIT.

Configurando o React

Até o momento, o setup que temos pode servir de base para qualquer framework Javascript que você queira usar para desenvolver sua aplicação. A partir daqui, vamos adicionar as configurações específicas do React.

Repare que a intalação destes pacotes recebeu um parâmetro diferentes das outros até agora: em vez de --save-dev usamos somente --save. Isto porque, ao contrário do Webpack, que é uma dependência de desenvolvimento, o código do React vai ser compilado e disponibilizado no Javascript final entregue ao cliente.

Uma característica do React que difere ele de outros frameworks como Angular ou JQuery é o virtual DOM: ao invés de manipular os elementos já existentes no DOM, ele se encarrega de injetar todo o HTML que aparece para o usuário no elemento raíz da aplicação. É por isto que o template que criamos no Django possui apenas uma div, que receberá todo o conteúdo manipulado pelo React. Assim, ao invés de criar arquivos separados de .html e .js, os componentes do React são na verdade arquivos Javascript que já contêm o HTML que vão renderizar. Para isso, usa-se uma sintaxe especial chamada JSX que depois precisa ser transpilada para virar strings que podem ser lidas pelo interpretador do browser. Quem faz este trabalho de transpilar as sintexes especiais que vamos usar é o Babel.

Além do JSX, vamos adicionar as regras para utilizar a sintaxe do ES6, os recursos mais novos do Javascript que ainda não estão implementados nos browsers. Dessa forma, podemos escrever com a sintaxe nova sem nos preocupar com compatibilidade.

Vamos instalar as bibliotecas do Babel nas dependências de desenvolvimento e criar um arquivo de configuração para ele.

.babelrc

{
    "presets": [
      "es2015", "react"
    ]
}

Agora podemos informar no webpack.config.js as regras que o Webpack deve utilizar quando for compilar os arquivos .js ou .jsx. Vamos definir que todo arquivo com estas extensões devem passar pelo loader do Babel, exceto os que estão no diretório node_modules, onde ficam as dependências do projeto.

webpack.config.js

module.exports = {
    ...
    module: {
      rules: [
        {
          test: /\.jsx?$/,
          loader: 'babel-loader',
          exclude: /node_modules/,
        },
      ],
    },
}

Se estiver com o npm run watch rodando não se esqueça de interromper o processo e rodá-lo novamente toda vez que editar o webpack.config.js.

Com o que temos até aqui, podemos criar o primeiro componente React.

assets/src/js/index.js

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  render () {
    return (
      <h1>Django + React + Webpack + Babel = Awesome App</h1>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('react-app'));

Usamos o ReactDOM para procurar um elemento com o id react-app e renderizar dentro dele o HTML gerado pelo componente App.

Organizando a aplicação

Passo 6

Há diversas possibilidades de organizar a arquitetura de um projeto React. Nós escolhemos aqui a estrutura que separa components e containers. Os primeiros são os blocos que serão usados para montar as páginas e podem ser reaproveitados em lugares diversos, já os outros representam as páginas em si, contendo o estado global do conteúdo visualizado pelo usuário.

Vamos extrair o componente que criamos diretamente na raíz da aplicação e dividi-lo em um container e um componente genérico que pode ser aproveitado em outras páginas.

assets/src/js/components/Title/index.js

import React from 'react';

const Title = ({ text }) => {
  return (
    <h1>{text}</h1>
  )
}

export default Title;

Title agora é apenas um elemento de título que recebe um texto para usar de conteúdo. Quem define o texto específico é o componente que vai usá-lo.

assets/src/js/containers/App/index.js

import React from 'react';

import Title from '../../components/Title';

class App extends React.Component {
  render () {
    const text = 'Django + React + Webpack + Babel = Awesome App';
    return (
      <Title text={text} />
    )
  }
}

export default App;

No ponto de entrada não há mais nenhum componente sendo definido, ele apensas importa App do pacote de containers.

assets/src/js/index.js

import React from 'react';
import ReactDOM from 'react-dom';

import App from './containers/App';

ReactDOM.render(<App />, document.getElementById('react-app'));

Adicionando estilos

Para desenvolver uma aplicação por completo não basta adicionar somente o conteúdo, é preciso adicionar também os estilos. Para compilar os arquivos de CSS, vamos precisar de mais alguns loaders e do plugin Estract Text. Neste projeto, escolhemos usar o SASS como processador de estilos, mas se preferir usar LESS, você pode seguir os exemplos da documentação.

Passo 7

Depois de instalar as dependência, vamos adicionar novas regras para o Webpack compilar os estilos.

webpack.config.js

...
var ExtractText = require('extract-text-webpack-plugin');

module.exports = {
    ...
    plugins: [
      ...
      new ExtractText({
        filename: '[name]-[hash].css'
      }),
    ],

    module: {
      rules: [
        ...
        {
          test: /\.css$/,
          loader: ['style-loader', 'css-loader'],
        },
        {
          test: /\.scss$/,
          use: ExtractText.extract({
            fallback: 'style-loader',
            use: ['css-loader', 'sass-loader']
          })
        },
      ],
    },
}

Definimos aqui o mesmo padrão de nomenclatura para os arquivos de .css que usamos para os .js. Assim, a tag que adicionamos no template do Django {% render_bundle 'main' 'css' %} vai procurar pelos arquivos que começam com o nome main e têm a extensão .css.

Agora podemos adicionar nossa primeira folha de estilos e importá-la no Javascript.

assets/src/scss/index.scss

@import "./title";

assets/src/scss/title.scss

.title {
  color: red;
}

assets/src/js/index.js

...
import '../scss/index.scss';
...

assets/src/js/components/Title/index.js

...
    <h1 className="title">{text}</h1>
...

Lembrando que na sintaxe do JSX não se usa class, que é uma palavra reservada, e sim className para indicar as classes de CSS.

Pronto! Com este setup temos um esqueto de projeto pronto para ser desenvolvido.

É provável que além dessas bibliotecas, você queira adicionar outras ferramentas como o Redux, para gerenciar os estados dos componentes do React, o Bootstrap, para estilos customizados, ou compiladores, para minificar os scripts, por exemplo. O procedimento para isso será o mesmo que usamos para instalar o React e os loaders e plugins do Webpack: todas as bibliotecas importadas e utilizadas dentro do seu código são adicionadas às dependências normais com o --save, já as ferramentas de compilação e transformação do seu código vão para as dependências de desenvolvimento com o --save-dev e suas regras de utilização ficam nas configurações do Webpack. Na documentação de todas elas você vai encontrar exemplos de como configurá-las.