Entendendo webpack e criando uma aplicação em React com ele

March 29, 2017

Nesse post vamos falar um pouco de webpack. Falaremos sobre o que é o webpack, para que serve, como configurá-lo e usá-lo e vamos aplicar isso tudo para criar uma aplicação em React utilizando algumas tecnologias mais modernas, como Babel para transpilar, Sass, entre outros.

O que é o webpack?

O webpack hoje é uma ferramenta muito usada em diversos projetos de Javascript. Vamos então responder a nossa primeira importante pergunta. O que é o webpack? Bem, o webpack é um bundler de código. Beleza, e o que é um bundler? Um bundler junta vários módulos, com todas as dependências necessárias, em um ou alguns poucos módulos, criando o chamado “dependency graph”.

Você pode então usar require ou import para utilizar os módulos, arquivos estáticos (arquivos CSS, imagens, por exemplo) e o que mais você precisar, aonde forem necessários. Além disso, você também pode processar arquivos para utilizar, por exemplo, Sass em seus estilos ou Babel, para usar as funções mais novas do Javascript que ainda não são 100% suportadas por todos os navegadores.

Instalação

Vou considerar que você já tenha o NodeJs instalado. Caso não tenha, clique aqui para verificar como instalar no seu sistema operacional.

Em primeiro lugar, vamos usar o npm init para criar o arquivo package.json e dar início ao nosso projeto. Este arquivo reúne diversas informações gerais sobre o projeto, como nome, versão, além das dependências do projeto com relação a outras libraries e pacotes. Assim, é possível instalar todas as dependências executando apenas npm install na pasta onde este arquivo se encontra. Pode dar um nome para o projeto e o restante apertar Enter para todas as opções. Ao final, digite yes para confirmar que está tudo correto.

npm init

A instalação do webpack é bem simples. Podemos instalar através do npm install. Vamos usar a opção --save-dev que atualiza o package.json com as dependências de development.

npm install webpack@2 --save-dev

A versão do webpack que utilizarei nos exemplos é a versão 2, então fique atento pois muitas configurações são diferentes da versão 1 para a 2.

Começando a usar

Para usufruir do webpack, precisamos criar um arquivo com o nome webpack.config.js, na pasta raiz do seu projeto (a mesma onde está o arquivo package.json). Este arquivo definirá todas as configurações necessárias ao webpack. Neste arquivo, basicamente, definimos um objeto, onde cada key representa uma determinada configuração do webpack. Partiremos do seguinte código:

module.exports = {

}

Agora, vamos começar a parte legal, que é como o webpack realmente funciona. Basicamente, o webpack é apoiado em quatro conceitos principais. Vamos a eles.

Conceito 1: Entry (Entrada)

Para criar o “dependency graph” da sua aplicação, o webpack precisa de um ponto de entrada. Este ponto de entrada seria o arquivo, ou os arquivos, iniciais, a partir dos quais ele vai buscando aquilo que ele deve empacotar. O entry é um array, onde cada item é um arquivo de entrada.

Vamos definir em nossa configuração o arquivo de entrada app.jsx (vamos usar .jsx para já preparar a criação de uma aplicação React), que ficará dentro da pasta app que criaremos mais a frente:

module.exports = {
    entry: [
        './app/app.jsx'
    ],
}

Conceito 2: Output (Saída)

Definimos a entrada, e agora precisamos definir a saída. No output, definimos onde o webpack deve colocar o pacote que ele vai gerar através da sua entrada. Esta definição é um objeto, com as chaves path, que será a pasta onde o pacote será colocado, e filename, que, obviamente, será o nome do arquivo. Vamos ao exemplo, onde o pacote gerado terá o nome de bundle.js e ficará na pasta public, relativa à pasta onde o arquivo de configuração se encontra. Lembre-se de importar o path com o comando require para utilizá-lo:

var path = require('path');

module.exports = {
    entry: [        
        './app/app.jsx'
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'public')
    }
}

O output possui uma série de outras configurações possíveis, mas por enquanto vamos ficar com estas duas, as mais básicas e necessárias.

Conceito 3: Loaders

O webpack trata cada arquivo de sua aplicação como módulo, e ele entende que todos os recursos do seu site devem ser problema dele, e não do navegador. Desta forma, os loaders servem para transformar cada arquivo em módulos, para que possam ser importados e utilizados ao longo do seu projeto. A configuração dos loaders serve para:

  • Identificar o que deve ser transformado por um determinado loader
  • Transformar este arquivo para que possa ser adicionado ao “dependency graph”

A configuração dos loaders é feita dentro da chave rules, que ficará dentro da chave modules. Dentro desta chave, definimos na propriedade test o que deve ser transformado, e dentro da propriedade use, o que será usado para transformar estes determinados arquivos. Vejamos um exemplo onde usaremos o babel-loader para transpilar todos os arquivos .js e .jsx de nosso projeto. Vamos usar os presets babel-preset-react, babel-preset-es2015 e babel-preset-stage-0. Por fim, o pacote babel-core também é necessário. Desta forma, teremos acesso à novas funções do Javascript que ainda não são suportadas por todos os browsers, toda a funcionalidade do React e mais. Primeiro, precisamos instalar todos estes pacotes através do npm:

npm install --save-dev babel-loader babel-preset-react babel-preset-es2015 babel-preset-stage-0 babel-core

Agora, faremos a configuração no arquivo webpack.config.js:

var path = require('path');

module.exports = {
    entry: [
        './app/app.jsx'
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'public')
    },    
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/, 
                exclude: [/node_modules/],
                use: [{
                    loader: 'babel-loader',
                    options: { presets: ['react', 'es2015', 'stage-0'] }
                }],
            },            

        ]
    }
};

Reparem que usamos a propriedade exclude para indicar que não deve ser considerado pelo loader a pasta node_modules, que é onde ficam instalados os módulos do projeto instalados através do npm.

Vamos entender o que fizemos acima. O webpack utiliza o regex definido na propriedade test para definir os arquivos que serão transformados, e então usa o babel-loader com as opções definidas na propriedade options, que neste caso particular, serão os presets do Babel mais sobre eles aqui) que escolhemos para usar em nosso projeto.

Da mesma forma, existe o style-loader, onde podemos definir que arquivos .scss sejam transformados para que você possa utilizar Sass em seu projeto. Para este caso, precisamos dos pacotes style-loader, css-loader e sass-loader. O style-loader adiciona CSS no DOM, o css-loader interpreta imports e url() nos arquivos, e o sass-loader compila o Sass para CSS a ser interpretado pelo navegador. O sass-loader necessita do pacote node-sass, então também vamos instalá-lo:

npm install --save-dev style-loader css-loader sass-loader node-sass

Agora, vamos adicionar a configuração ao nosso arquivo webpack.config.js:

module.exports = {
    entry: [
        './app/app.jsx'
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'public')
    },    
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/, 
                exclude: [/node_modules/],
                use: [{
                    loader: 'babel-loader',
                    options: { presets: ['react', 'es2015', 'stage-0'] }
                }],
            },
            {
                test: /\.scss$/,
                exclude: [/node_modules/],
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader"
                }, {
                    loader: "sass-loader"
                }]
            }
        ]
    }
};

Neste caso, como podem perceber, usamos o loader três vezes. É importante que sejam aplicados nesta ordem. Agora você pode fazer o require de um arquivo .scss e usar Sass em seu projeto. Mais a frente daremos um exemplo simples com uma aplicação React.

Conceito 4: Plugins

Enquanto os loaders fazem transformação em arquivos individuais, os plugins são normalmente usados para atuar em pedaços do seu código já “empacotado”. Um exemplo vindo da própria documentação do webpack é o webpack.optimize.UglifyJsPlugin(). Este plugin, como o nome já indica, minimiza todo o código Javascript, fazendo o chamado Uglify. Os plugins no webpack são definidos dentro da chave plugins. Para acessar os plugins do Webpack, você deve importar o webpack neste arquivo com o comando require. Vejamos como fica no arquivo de configuração do webpack:

var path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: [
        './app/app.jsx'
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'public')
    },    
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/, 
                exclude: [/node_modules/],
                use: [{
                    loader: 'babel-loader',
                    options: { presets: ['react', 'es2015', 'stage-0'] }
                }],
            },
            {
                test: /\.scss$/,
                exclude: [/node_modules/],
                use: [{
                    loader: "style-loader"
                }, {
                    loader: "css-loader"
                }, {
                    loader: "sass-loader"
                }]
            }
        ]
    },
    plugins: [
        new webpack.optimize.UglifyJsPlugin()
    ]
};

Para uma lista mais completa de plugins do webpack, dá uma olhada aqui.

Bem, agora já temos tudo configurado. Vamos ver como fica nossa aplicação em React.

webpack com React

Em primeiro lugar, vamos usar o npm para instalar o React e o React-DOM, que são os pacotes básicos para se trabalhar com React. Neste caso usamos a opção --save para atualizar o arquivo package.json com as dependências de produção.

npm install --save react react-dom

Vamos aproveitar e instalar o http-server (link do repo) para servir nossos arquivos e testar nosso projeto localmente. Nesse caso, podemos usar a opção -g para instalar globalmente, pois você pode usar o http-server para testar qualquer outro projeto.

npm install -g http-server

Agora, vamos criar a estrutura do nosso projeto React. Vou usar a estrutura que costumo utilizar em meus projetos. Vamos criar uma pasta app. Nela, ficará o nosso arquivo de entrada, o app.jsx, conforme especificamos na configuração do webpack. Neste arquivo, teremos um código básico simples para importar o component MainApp, que criaremos no próximo passo, e renderizá-lo em sua aplicação. Também incluiremos um require para o arquivo de estilos app.scss, que também criaremos um pouco mais a frente. Este arquivo de estilos importará outros arquivos, não havendo a necessidade de voltar no arquivo de entrada para importar arquivos .scss um por um.

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

import MainApp from './components/MainApp.jsx';

// App Styles
require('./styles/app.scss')

ReactDOM.render(
  <MainApp />,
  document.getElementById('app')
);

Dentro dela também criaremos a pasta components, onde, como o nome já indica, ficarão nossos componentes. Aí, criaremos um componente simples, cujo nome será MainApp.jsx, com o código básico para um componente React:

import React, { Component } from 'react';

class MainApp extends Component {
    render() {
        return (
            <div>
                MainApp component
            </div>
        );
    }
}

export default MainApp;

Agora, criaremos a pasta styles, que também ficará dentro da pasta app e irá conter os arquivos scss, responsáveis pelo estilo da aplicação. Aí, podemos criar um arquivo app.scss, que será o arquivo central de estilos. Vamos apenas definir uma cor para o body e uma para os elementos p, para checar se está tudo ok. Incluí o seletor do p dentro do seletor do body para mostrar como estamos usando Sass, visto que este “nesting” não é permitido em CSS tradicional. Vou também importar um arquivo específico para o componente MainApp:

@import "components/MainApp";

body {
  background-color: red;
  p {
    color: white;
  }
}

Dentro da pasta styles, criamos a pasta components. Nesta pasta, podemos criar um arquivo .scss específico para cada componente. Esta estrutura permite uma boa separação e organização do seu código. Neste caso, então, vamos criar o _MainApp.scss. Repare no underscore no início do nome do arquivo, necessário para a correta importação conforme definido no arquivo app.scss que acabamos de criar. Vou definir apenas um estilo para o parágrafo, para verificarmos que a importação foi feita corretamente.

p {
  text-decoration: underline;
}

Por fim, precisamos criar a pasta public. Nesta pasta estarão os arquivos que serão servidos para o http-server, e é onde o webpack salvará o bundle com todo o seu código. Vamos então criar o arquivo index.html. É um arquivo HTML simples, importando o arquivo bundle.js que estará na mesma pasta que ele.

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8"/>
</head>

<body>
    <div id="app"></div>

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

</html>

Vamos agora executar o webpack. Basta executar o comando webpack na raiz do seu projeto. O mesmo tomará alguns segundos e então mostrará alguns avisos. Se tudo der certo, você pode verificar na pasta public que o arquivo bundle.js foi criado. A cada mudança feita no código do seu projeto, você tem de rodar o comando novamente. Para evitar isso, você pode rodar o comando webpack -w. Esse comando irá “vigiar” (w, de watch) os arquivos, e a cada mudança feita em seu código, ele irá atualizar o arquivo bundle.js. Finalmente, vamos agora executar o comando http-server e verificar se está tudo certo. A porta padrão do pacote http-server é a porta 8080, então, acesse http://localhost:8080/; se tudo der certo, você deve ver uma página, mais ou menos como a da imagem abaixo:

Print da aplicação em React funcionando corretamente
Print da aplicação em React funcionando corretamente

A combinação de cores ficou bem feia, mas como você pode ver, o componente MainApp foi devidamente renderizado. Os estilos foram aplicados, tanto os definidos no arquivo app.scss (cores do parágrafo e fundo do body) quanto os aplicados no arquivo _MainApp.scss (parágrafo sublinhado).

Isso significa que tudo deu certo. Agora você tem uma aplicação em React, com seu código “empacotado” pelo Webpack e usando as funcionalidades mais modernas do Javascript, além de Sass para seus estilos. Resumindo nossa estrutura de pastas e arquivos, fica da seguinte forma:

├── app
│   ├── app.jsx
│   ├── components
│   │   └── MainApp.jsx
│   └── styles
│       ├── app.scss
│       └── components
│           └── _MainApp.scss
├── package.json
├── public
│   ├── bundle.js
│   └── index.html
├── understanding-webpack-tree.txt
└── webpack.config.js

Note que existem muitas estruturas e formas de organização que funcionam, e esta é apenas uma que eu uso e que funciona bem para mim. Então, não assuma isso como alguma forma de “resposta correta”, pois nesse caso, ela não existe :)

Espero que tenha ajudado. Estou sempre aberto à correções e sugestões.

O código deste post pode ser encontrado no seguinte repositório do Github: https://github.com/felipegalvao/understanding-webpack

Abraços