Artigo original: How to enable ES6 (and beyond) syntax with Node and Express

Já tentou criar aplicações de front-end usando a sintaxe da ES6 e, ao tentar aprender desenvolvimento de back-end com Node.js e Express,  percebeu que não podia usar coisas como import from e export default?  Se isso já aconteceu com você, você está no lugar certo! Este é um guia passo a passo de como configurar seus ambientes de desenvolvimento e de produção, preparar os scripts e, como bônus, adicionar testes!

Sumário dos tópicos

Como funciona? Uma versão geral do que precisamos

Para permitir uma experiência semelhante à do desenvolvimento de front-end quando estivermos desenvolvendo aplicações para o back-end, aqui temos uma visão geral dos processos que ocorrem em seu projeto.

Transpilador de código da ES6+ para a ES5

Precisamos de um pacote que traduza a sintaxe da ES6 e versões superiores para o código da ES5. O código da ES5 é o estilo de sintaxe do JS que o node.js consegue ler, como module.exports ou var module = require('module'). Observe que, nos dias de hoje, quase 99% da sintaxe da ES6 e versões superiores pode ser usada no Node.js. Esse é o ponto onde o pacote chamado babel se destaca.

O Babel recebe um arquivo js, converte seu código e retorna como resultado um novo arquivo.

Script que remove os arquivos

Sempre que mudamos algo em nosso código, alimentamos o novo código no transpilador, que retorna sempre uma cópia. É por isso que precisamos de um script que remova os arquivos antigos antes que uma cópia nova e transpilada seja fornecida. Para isso, existe um pacote chamado rimraf. O Rimraf exclui os arquivos. Demonstraremos isso mais tarde.

Observador de mudanças nos arquivos

Ao programar para o Node.js, o reinício automático do nosso servidor não vem configurado por padrão, como ocorre quando fazemos um projeto usando create-react-app ou vue-cli. É por isso que instalaremos um pacote chamado nodemon, que executa algo sempre que alterarmos um arquivo em nosso código. Tiraremos proveito do nodemon para reiniciar o servidor sempre que um arquivo for alterado.

Essa é a visão geral de como o processo funciona internamente. Com isso, vamos ver agora como configurar nosso projeto.

Pré-requisitos

Antes de começarmos, precisamos configurar algumas coisas.

  1. Não se esqueça de ter o Node.js e o npm instalados. Recomendo instalar a versão LTS ou a versão estável mais recente. Você pode instalar essa versão pela página do Node.js ou pelo NVM (Node Version Manager)
  2. É preciso ter um conhecimento básico dos comandos do terminal. A maioria dos comandos está no tutorial de todo modo. Assim, você não precisa se preocupar com eles.
  3. Não deixe de manter seu terminal aberto e tenha instalado seu editor de textos favorito.

Era isso! Podemos começar!

Instalação do Express

Usando o gerador do Express, criaremos um novo projeto com o código gerado, moveremos alguns arquivos e faremos a conversão do código para a sintaxe do ES6. Precisamos fazer essa conversão cedo, pois precisamos de uma maneira de verificar se nosso código em ES6 funciona.

Configuração do projeto

Execute o comando abaixo em seu terminal. Você pode colocar em nome-do-seu-projeto o nome que você quiser. A flag --no-view significa que não usaremos engines de modelo, como o handlebars, ejs ou pug para o "esqueleto" de nossa aplicação com Express.

npx express-generator nome-do-seu-projeto --no-view

Depois de criar sua aplicação, você precisa ir para o diretório dela. No Windows Powershell e nos terminais do Linux, use:

cd nome-do-seu-projeto

Em seguida, abra o editor de texto que você quiser. Eu uso o VSCode, então eu tenho meu terminal e editor de texto abertos ao mesmo tempo, mas você pode usar o editor de textos que quiser.

Instalação dos pacotes, movimentação e exclusão dos arquivos

Após prepararmos o projeto, precisamos fazer a instalação das dependências e mover algumas pastas. Execute o comando abaixo para instalar o Express e outros pacotes.

npm install

Enquanto aguarda pela instalação das dependências, faça o seguinte:

  • crie uma pasta server/
  • Coloque bin/ , app.js  e routes/ dentro da pasta server/.
  • Renomeie www, encontrado em bin, para www.js
  • Deixe a pasta public/ no diretório raiz do seu projeto.

A estrutura de arquivos deve ter essa aparência:

image-1
Essa deve ser a aparência de nossa estrutura de arquivos. A pasta public/ está na raiz e todos os arquivos .js estão dentro da pasta server/.

Agora, como modificamos a estrutura de arquivos, nosso script de inicialização do servidor (start) não funcionará. Em nosso arquivo package.json, renomearemos o script start para server, encontrado em um objeto JSON chamado "scripts"

// package.json
{
  "name": "nome-do-seu-projeto",
  // ...outros detalhes
  "scripts": {
    "server": "node ./server/bin/www"
  }
}

Você verá que modificamos o caminho do arquivo, de ./bin/www para ./server/bin/www, pois movemos os arquivos para server/. Mais tarde, usaremos o script start.

Experimente! Tente executar o servidor digitando npm run server em seu terminal e vá para localhost:3000 em seu navegador.

Conversão para o código da ES6

Conversão do código de alto nível para usar os imports da ES6

A conversão do código gerado para ES6 é um pouco entediante. Por isso, vou apenas colocar o código aqui. Fique à vontade para copiar e colar.

Código para bin/www.js:

// bin/www.js
/**
 * Dependências dos módulos.
 */
import app from '../app';
import debugLib from 'debug';
import http from 'http';
const debug = debugLib('nome-do-seu-projeto:server');
// ...código gerado abaixo.

Quase todas as nossas modificações são na parte superior e inferior dos arquivos. Estamos deixando o resto do código gerado como ele estava.

Código para routes/index.js e routes/users.js:

// routes/index.js e users.js
import express from 'express';
var router = express.Router();
// ...outras coisas abaixo
export default router;

Código para app.js:

// app.js
import express from 'express';
import path from 'path';
import cookieParser from 'cookie-parser';
import logger from 'morgan';
import indexRouter from './routes/index';
import usersRouter from './routes/users';
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '../public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
export default app;

No app.js , por termos deixado public/ no diretório raiz do projeto, precisamos mudar o caminho estático do Express e colocá-lo uma pasta acima. Observe que o caminho para 'public' virou '../public' .

app.use(express.static(path.join(__dirname, '../public')));

Pronto. Convertemos o código. Vamos agora começar com nossos scripts.

Configuração dos scripts

Ao configurar os scripts, cada um deles tem uma função diferente. Reutilizamos cada script do npm e, para nossos ambientes de desenvolvimento e de produção, temos uma configuração diferente (quase idêntica, como veremos a seguir). É por isso que precisamos compor nossos scripts para que possamos usá-los sem digitar repetidamente as mesmas coisas sempre.

Instalar npm-run-all

Como alguns comandos do terminal não funcionarão no cmd do Windows, precisamos instalar um pacote chamado npm-run-all, de modo que esse script funcione em qualquer ambiente. Execute este comando no diretório raiz do seu projeto.

npm install --save npm-run-all

Instalar o babel, o nodemon e o rimraf

O Babel é o transpilador do JavaScript moderno. Um transpilador significa que seu código JavaScript moderno será transformado no código antigo que o Node.js consegue entender. Execute o comando abaixo no terminal no diretório raiz do seu projeto. Usaremos a versão mais recente do babel (Babel 7+).

Lembre-se de que o Nodemon é o pacote observador de arquivos e que o Rimraf é o pacote de remoção de arquivos.

npm install --save @babel/core @babel/cli @babel/preset-env nodemon rimraf

Adicionar o script de transpilação

Antes de o babel começar a converter código, precisamos informá-lo quais partes do código ele precisa traduzir. Observe que há muitas configurações disponíveis, pois o babel pode converter diversas sintaxes do JS para cada tipo diferente de finalidade. Por sorte, não precisamos pensar sobre isso, já que existe uma opção padrão. Usaremos a configuração padrão, chamada preset-env (a que instalamos anteriormente) em nosso arquivo package.json para informar ao Babel em que formato estaremos transpilando o código.

Dentro do arquivo package.json, crie um objeto "babel" e coloque nele a configuração abaixo:

// package.json
{  
  // ...conteúdo acima
  "babel": {
    "presets": ["@babel/preset-env"]
  },
}

Depois dessa configuração, estamos prontos para testar se o babel converte de fato o código. Adicione um script chamado transpile ao seu package.json:

// package.json
"scripts": {
    "start": "node ./server/bin/www",
    "transpile": "babel ./server --out-dir dist-server",
}

O que fizemos? Primeiro, precisamos executar o comando babel da cli, especificar os arquivos para serem convertidos (neste caso, os arquivos que estão em server/) e colocar o conteúdo transpilado em uma pasta diferente, chamada dist-server no diretório raiz do projeto.

Faça o teste executando este comando:

npm run transpile

Você verá uma nova pasta aparecer.

image-1-1
Uma nova pasta chamada dist-server apareceu em função do script que executamos.

Funcionou! ✅ Como você pode ver, há uma pasta com a mesma estrutura de pastas da pasta server, mas com o código convertido dentro dela. Legal, não? A seguir, veremos se nosso servidor está em execução!

Script clean

Para termos uma cópia limpa sempre que transpilarmos nosso código para arquivos novos, precisaremos de um script que remova os arquivos antigos. Adicione o script clean ao seu package.json.

"scripts": {
  "server": "node ./dist-server/bin/www",
  "transpile": "babel ./server --out-dir dist-server",
  "clean": "rimraf dist-server"
}

Esse script do npm que fizemos removerá o conteúdo da pasta dist-server/

Agora, para combinar transpile e clean, adicione um script chamado build , que combina os dois processos.

// scripts
"build": "npm-run-all clean transpile"

Executando o script dev

Agora que fizemos o script de build, precisamos executar nosso servidor de desenvolvimento (dev). Adicionamos um script chamado dev ao nosso package.json. Ele é responsável por definir nosso ambiente Node como o ambiente de desenvolvimento, removendo o código transpilado antigo e substituindo-o pelo novo.

"scripts": {
  "build": "npm-run-all clean transpile"
  "server": "node ./dist-server/bin/www",
  "dev": "NODE_ENV=development npm-run-all build server",
  "transpile": "babel ./server --out-dir dist-server",
  "clean": "rimraf dist-server"
}

Observe que alteramos novamente o arquivo que estamos executando no nosso script server. Extamos executando o caminho do arquivo com o código transpilado, encontrado em dist-server/.

Adicionando os script de produção

Se temos um script dev, que define o ambiente do Node como ambiente de desenvolvimento, também teremos um script prod, definindo o ambiente como ambiente de produção. Usamos essa configuração na hora de fazer o deploy (no Heroku, na AWS, na DigitalOcean etc.). Agora, adicionamos nossos scripts start e prod em nosso package.json.

"scripts": {
  "start": "npm run prod"
  "build": "npm-run-all clean transpile"
  "server": "node ./dist-server/bin/www",
  "dev": "NODE_ENV=development npm-run-all build server",
  "prod": "NODE_ENV=production npm-run-all build server",
  "transpile": "babel ./server --out-dir dist-server",
  "clean": "rimraf dist-server"
}

Definimos o script start padrão como prod, pois o script start está sendo usado sempre pelas plataformas de implantação (deployment, em inglês), como a AWS ou o Heroku, para iniciar um servidor.

Experimente executar npm start ou npm run prod .

// package.json
...
"nodemonConfig": { 
  "exec": "npm run dev",
  "watch": ["server/*", "public/*"],
  "ignore": ["**/__tests__/**", "*.test.js", "*.spec.js"]
},
"scripts": { 
  // ... outros scripts
  "watch:dev": "nodemon"
}

E a reinicialização do servidor sempre que um arquivo for alterado?

Um último script para completar nossa configuração de desenvolvimento. Precisamos adicionar um script observador de arquivos que execute um comando sempre que uma alteração ocorrer em um arquivo. Adicione um objeto JSON chamado "nodemonConfig" ao seu package.json. É lá que armazenaremos o que será informado ao observador o que ele deve fazer quando um arquivo for alterado.

Além disso, adicione um script chamado watch:dev ao seu package.json

// package.json
...
"nodemonConfig": { 
  "exec": "npm run dev",
  "watch": ["server/*", "public/*"],
  "ignore": ["**/__tests__/**", "*.test.js", "*.spec.js"]
},
"scripts": { 
  // ... outors scripts
  "watch:dev": "nodemon"
}

A configuração do nodemon contém definições relacionadas a:

  • Qual comando deve ser executado sempre que um arquivo for alterado. Em nosso caso, o comando é npm run dev
  • Que pastas e arquivos devem ser observados
  • Que arquivos devem ser ignorados

Saiba mais sobre a configuração do nodemon aqui (texto em inglês).

Agora que temos nosso observador de arquivos, você agora pode executar npm run watch:dev, digitar o código e salvar seu arquivo. Sempre que você visitar localhost:3000 , verá as alterações. Experimente!

Bônus: adicione testes!

Para adicionar testes ao nosso projeto, simplesmente instale o Jest a partir do npm, adicione algumas configurações e um script chamado test ao arquivo package.json

npm i -D jest

Código do objeto chamado "jest" e do script de teste para o package.json:

// package.json
...
"jest": { 
  "testEnvironment": "node"
},
"scripts": {
  // ..outros scripts 
  "test": "jest"
}

Confira, faça um arquivo chamado sample.test.js, escreva alguns testes e e execute o script!

npm run test

image-2
Imagem de exemplo da execução de npm run test.

Versão resumida

Aqui temos os passos simplificados de como habilitar a ES6 no Node.js. Também incluirei o repositório para que você possa copiar e inspecionar todo o código.

  • Crie um projeto usando o comando de terminal express nome-do-seu-projeto.
  • Mova bin/, routes/ e app para uma nova pasta, chamada src/  e converta o código para ES6. Não se esqueça de renomear bin/www para www.js
  • Instale todas as dependências e dependências de desenvolvimento
npm i npm-run-all @babel/cli @babel/core @babel/preset-env nodemon rimraf --save
npm i -D jest
  • Adicione os scripts abaixo ao arquivo package.json
"scripts": { 
  "start": "npm run prod", 
  "build": "npm-run-all clean transpile", 
  "server": "node ./dist-server/bin/www", 
  "dev": "NODE_ENV=development npm-run-all build server", 
  "prod": "NODE_ENV=production npm-run-all build server", 
  "transpile": "babel ./server --out-dir dist-server", 
  "clean": "rimraf dist-server", 
  "watch:dev": "nodemon", 
  "test": "jest" 
}
  • Adicione as configurações do babel, do nodemon e do jest no arquivo package.json
"nodemonConfig": {
  "exec": "npm run dev",
  "watch": [ "server/*", "public/*" ],
  "ignore": [ "**/__tests__/**", "*.test.js", "*.spec.js" ] 
}, 
"babel": { 
  "presets": [ "@babel/preset-env" ]
},
"jest": {
  "testEnvironment": "node"
},

Observações e avisos

Observe que essa configuração pode não ser a ideal para todas as situações, especialmente para projetos grandes (com mais de mil arquivos de código). A etapa de transpilação e exclusão pode tornar lento o seu ambiente de desenvolvimento. Além disso, os módulos da ES6 chegarão ao Node em breve. Mesmo assim, este é um bom material educacional para que você entenda como transpilar arquivos internamente ao desenvolver aplicações de front-end :)

Conclusão

Bem, espero que você tenha aprendido bastante. Obrigado por ler até aqui.

Feliz programação!

Confira o repositório completo aqui.

Links do autor:

Twitter - freeCodeCamp -  Portfólio - Github