Entendendo e configurando um projeto TypeScript

Neste post, discutimos um pouco sobre o TypeScript, desde conceitos básicos até a configuração do projeto. Abordamos o tsconfig.json, ts-node, nodemon, debug no VS Code e exploramos vantagens e desvantagens da linguagem. É uma ótima leitura para quem está começando com TypeScript.

À esquerda há a ilustração de um bloco de código TypeScript, seguido de uma seta apontamento para um outro bloco de código Ja
Entendendo e configurando um projeto TypeScript

Como consta na documentação oficial, o TypeScript é um superset do JavaScript, ou para facilitar o entendimento, podemos dizer que o JavaScript está contido no TypeScript, ou seja, é um subconjunto do TypeScript. Então todo código JavaScript é válido em TypeScript, mas o inverso não é verdadeiro.

TypeScript is a language that is a superset of JavaScript: JS syntax is therefore legal TS.

Neste texto, pretendo compartilhar um pouco sobre as razões por trás do uso do TypeScript e, além disso, explicar como configurar um projeto do zero usando os pacotes TypeScript, ts-node e nodemon.

Breve introdução ao TypeScript

O TypeScript foi anunciado pela Microsoft em outubro de 2012, liderado por Anders Hejlsberg, criador do C# e Turbo Pascal. Surgiu para superar as limitações do JavaScript em aplicações de grande escala, oferecendo tipagem estática e recursos de programação orientada a objetos. Isso visava reduzir erros em tempo de execução devido à falta de verificação de tipos no JavaScript, tornando mais fácil para os desenvolvedores construir e manter aplicações complexas.

Para ilustrar, abaixo está um pequeno exemplo de código em TypeScript:

function multiply(a: number, b: number): number {
    return a * b;
}

Se você copiar e colar este código no console do seu DevTools, vai pegar o seguinte erro:

SyntaxError: Unexpected token ':'. Expected a ')' or a ',' after a parameter declaration.

O motivo do erro é simples, os navegadores não sabem como lidar com um código TypeScript. O mesmo vale para o Node.js. Portanto, para executar este código, precisamos antes, transformá-lo em um código JavaScript em um processo chamado de transpilação (transpiling).

Ilustração de um código em TypeScript e sua versão transformada em JavaScript
Exemplo da conversão de TypeScript para JavaScript
💡
Utilize o link a seguir para digitar seu código TypeScript e visualizar o JavaScript: https://www.typescriptlang.org/play

Pegando o primeiro exemplo, mais acima, o resultado é um código JavaScript, que agora pode ser executado em um navegador ou Node.js, veja a seguir:

function multiply(a, b) {
    return a * b;
}

Além de ser fortemente tipado, ou seja, precisamos especificar o tipo de cada variável/propriedade/retorno, o TypeScript oferece um mecanismo de verificação com base na tipagem. Por exemplo, considerando o código abaixo, durante o processo de compilação com o tsc, a CLI do TypeScript, seria gerado o seguinte erro:

Argument of type 'string' is not assignable to parameter of type 'number'
function multiply(a: number, b: number): number {
    return a * b;
}

console.log(multiply(5, 10));
console.log(multiply('5', 10));

O erro deve-se ao fato de que o parâmetro "a" é do tipo number. Porém, a última linha de código passa o valor '5' que é uma string para o parâmetro.

Além das vantagens que já foram citadas, podemos destacar:

  • Compatibilidade com diversas versões do JavaScript: O TypeScript permite configurar a geração de código JavaScript em uma versão específica, tornando-se útil quando precisamos gerar código em versões mais antigas, como o ES5.
  • Melhoria na legibilidade do código: Graças ao uso de tipos, interfaces, classes e outras funcionalidades, o TypeScript possibilita escrever código mais claro e compreensível. Isso organiza o código de maneira mais intuitiva, facilitando sua compreensão.
  • Facilidade de refatoração: No ambiente de desenvolvimento, como o VS Code, é possível realizar refatorações com facilidade. Por exemplo, ao clicar em uma propriedade e pressionar a tecla F2 para renomeá-la, o novo nome é propagado de forma automática ao longo do código que faz uso dessa propriedade. Isso aumenta a produtividade e reduz a probabilidade de erros durante a refatoração.
VS Code mostrando 3 arquivos. Quando renomeamos a propriedade de uma interface, a novo nome também é alterado nos outros 2 arquivos que usam essa propriedade.
Renomeando uma propriedade no VS Code

Agora vamos explorar como configurar um projeto do zero utilizando TypeScript.

Configurando um projeto TypeScript

Abaixo utilizei o seguinte comando para criar o projeto:

# cria um diretório chamado "teste"
mkdir teste

# entra no diretório
cd teste

# inicializa o projeto npm
npm init -y

# Fornece declarações de tipos para o Node.js,
# o que permite que o editor de código compreenda
# as APIs e sugira automaticamente os nomes de
# funções, propriedades e outros elementos.
npm install @types/node --save-dev

# instala a dependência typescript
npm install typescript --save-dev

# cria o arquivo tsconfig.json
npx tsc --init

# retorno do comando acima:
Created a new tsconfig.json with:
                                                                                                                     TS
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true

Para testar vamos criar um arquivo chamado index.ts com o seguinte conteúdo:

import { createServer, IncomingMessage, ServerResponse } from "http";

const server = createServer();
server.on("request", (_: IncomingMessage, res: ServerResponse) => {
  const responseStatus: number = 200;
  res.writeHead(responseStatus, { "Content-Type": "application/json" });
  res.end(
    JSON.stringify({
      data: "Hello World!",
    }),
  );
});

server.listen(3000, () => console.log("Executando..."));

index.ts

Para converter o arquivo index.ts em um arquivo index.js, basta executar o comando npx tsc. Colei o resultado logo abaixo:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = require("http");
const server = (0, http_1.createServer)();
server.on("request", (_, res) => {
    const responseStatus = 200;
    res.writeHead(responseStatus, { "Content-Type": "application/json" });
    res.end(JSON.stringify({
        data: "Hello World!",
    }));
});
server.listen(3000, () => console.log("Executando..."));

index.js

Para executar: node index.js

Na esquerda mostra a aplicação sendo executada e na direita o resultado de uma requisição
Na esquerda mostra a aplicação sendo executada e na direita o resultado de uma requisição

Então sempre que precisarmos executar um código TypeScript, será necessário gerar o JavaScript e depois executá-lo. Considerando que ao longo do desenvolvimento vamos alterar o código diversas vezes, não seria muito produtivo executar estes dois comandos toda vez que quisermos testar algo. Para simplificar esse processo, podemos utilizar a biblioteca ts-node.

ts-node is a TypeScript execution engine and REPL for Node.js.
https://typestrong.org/ts-node/docs/

Configurando o ts-node

A biblioteca ts-node permite a execução simplificada de código TypeScript com apenas um comando, como por exemplo, ts-node index.ts. Por trás dos panos, ela cuida do processo de transpilação e execução do código.

Para instalar a dependência:

npm install ts-node --save-dev

Agora podemos executar a aplicação com um único comando:

npx ts-node index.ts

Agora ficou um pouco mais fácil executar um código em TypeScript. No entanto, podemos aprimorar ainda mais o ambiente de desenvolvimento com a utilização da biblioteca nodemon.

Configurando o nodemon

Podemos utilizar a biblioteca nodemon para monitorar alterações em arquivos com a extensão ".ts". Então sempre que uma modificação é detectada, o nodemon reexecuta o comando npx ts-node index.ts automaticamente.

npm install nodemon --save-dev

Cadastrando o script start:dev no arquivo package.json:

{
  "name": "typescript",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start:dev": "npx nodemon -e ts --exec \"npx ts-node index.ts\"",
    "build": "npx tsc"
  },
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^20.11.24",
    "nodemon": "^3.1.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}

package.json

Agora podemos simplesmente utilizar o comando npm run start:dev para iniciar a aplicação e conforme modificarmos os arquivos com a extensão .ts o nodemon irá chamar novamente o comando npx ts-node index.ts. Isto ajuda bastante durante o desenvolvimento.

VS Code mostrando um código em execução. Quando o código é alterado, a aplicação reinicia automaticamente
Exemplo do uso do nodemon

Até este ponto, utilizamos três bibliotecas: typescript, ts-node e nodemon. As duas últimas não são obrigatórias, mas são úteis.

Explorando o tsconfig.json

Logo no início da construção deste projeto, executamos o comando npx tsc --init que criou um arquivo chamado tsconfig.json.

The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript project. The tsconfig.json file specifies the root files and the compiler options required to compile the project.

https://www.typescriptlang.org/docs/handbook/tsconfig-json.html

Este arquivo permite que você especifique várias opções, como versão do ECMAScript, diretórios de saída, módulos a serem usados, configurações de tipo, entre outras.

Para ilustrar, vamos criar um novo arquivo chamado tsconfig.dev.json, alterar o tsconfig.json e o package.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "commonjs",
    "strict": true,
    "sourceMap": true,
    "outDir": "./dist"
  },
  "include": ["*.ts"],
  "exclude": ["node_modules"]
}

tsconfig.dev.json

{
  "extends": "./tsconfig.dev.json",
  "compilerOptions": {
    "sourceMap": false,
    "removeComments": true
  }
}

tsconfig.json

No arquivo package.json vamos alterar para incluir os dois scripts:

{
  "name": "typescript",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start:dev": "npx nodemon -e ts --exec \"npx ts-node --project tsconfig.dev.json index.ts\"",
    "start:prod": "node dist/index.js",
    "build": "npx tsc"
  },
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^20.11.24",
    "nodemon": "^3.1.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}

package.json

Agora podemos executar os seguintes comandos:

# Para ambiente de desenvolvimento
npm run start:dev

# Para ambiente de produção (executar após o build)
npm run start:prod

# Para compilar
npm run build

Debugando o código no VS Code para Node.js

Por fim, vamos falar rapidamente sobre o debug. O processo para efetuar o debug no VS Code é bem simples:

  1. certifique-se de que os arquivos source maps estão sendo gerados. Essa opção está no tsconfig.json (sourceMap: true)
  2. Adicione o breakpoint na linha desejada
  3. Pressione ctrl + shift + p (no mac cmd + shift + p) e digite debug: debug npm script. No nosso exemplo selecione a opção npm run start:dev.
Animação mostrando os passos para depurar um código TypeScript no VS Code
Depurando um código TypeScript

Considerações

Em resumo, o TypeScript é uma ferramenta poderosa que pode ajudar a escrever código JavaScript mais robusto, seguro e fácil de manter. No entanto, é importante considerar a curva de aprendizado e a necessidade de conhecimento da equipe antes de adotar o TypeScript em um projeto.

Particularmente, acredito que o TypeScript realmente contribui para a escrita de um código mais limpo e legível. No entanto, é essencial que a equipe possua conhecimento para aproveitar ao máximo essa solução.

Para não estender muito o texto, deixei para abordar no futuro um pouco sobre os source maps, arquivos *.d.ts e outros recursos.

Links interessantes: