Novidades do Node.js (versão 18)
Introdução
Quando desenvolvemos um programa, seja para uso pessoal ou para vários usuários, é sempre importante mantermos as dependências desse projeto atualizadas para que ele acompanhe as mudanças da tecnologia e não se torne um programa fora de uso.
Isso parece se tornar cada vez mais difícil, por exemplo, quando precisamos usar bibliotecas externas para testar nossos códigos ou fazer requisições HTTP. Fora a sensação de estar “usando um canhão para matar uma barata” ao ter que instalar uma biblioteca externa para fazer coisas simples.
E quando falamos de Node.JS logo pensamos que poderíamos ter módulos nativos para lidar com isso. E acredito que a equipe do Node.js também pense assim, pois, na última semana, o Node.js ganhou uma nova versão LTS, a versão 18 que desde o seu lançamento trouxe funcionalidades incríveis que podem tornar nosso código menos dependente de bibliotecas externas e a escrita cada vez mais simples!
Mas, desde o anúncio dessa versão até ela se tornar a versão LTS, você sabe o que ela trouxe de novo?
Então vem comigo.
Entendendo as versões do Node.js
Entender as versões do Node.js é o ponto de partida para sabermos quando teremos novas funcionalidades e prevermos futuras manutenções em nosso código (seja por conta de uma versão que não receberá mais atualizações, seja por uma otimização de uma funcionalidade em uma nova versão). Além de saber se uma versão é estável e seu tempo de manutenção, conceitos vitais para manter o seu projeto.
O Node.js possui um calendário fiel de releases (em português, lançamentos) das versões major, atualizações de versão com potencial para “quebrar” códigos que utilizem as versões anteriores. Se temos, por exemplo, a versão 14.17.5
, estamos falando de atualizações acerca do primeiro número da sequência, o 14
.
E por qual motivo digo que possui um calendário fiel? Isso se dá por sempre termos lançamento de versões major a cada seis meses (em outubro e abril). O que é excelente para devs que usam o Node.js em suas aplicações se adequarem às novas versões.
A tabela adaptada da documentação do Node.js nos informa alguns atributos do cronograma de lançamento das versões do Node.js, em que temos o “apelido” da versão (coluna codename, ou codinome em português), a data de lançamento (coluna Initial Release) e fim da versão (coluna End-of-life), mas vamos focar nas colunas Release e Status, que nos trazem muita informação sobre como funciona o cronograma de versões do Node.js.
Release | Status | Codename | Initial Release | Active LTS Start | Maintenance Start | End-of-life |
---|---|---|---|---|---|---|
14.x | Maintenance | Fermium | 2020-04-21 | 2020-10-27 | 2021-10-19 | 2023-04-30 |
16.x | Maintenance | Gallium | 2021-04-20 | 2021-10-26 | 2022-10-18 | 2023-09-11 |
18.x | Current | - | 2022-04-19 | 2022-10-25 | 2023-10-18 | 2025-04-30 |
19.x | Current | - | 2022-10-18 | - | 2023-04-01 | 2023-06-01 |
20.x | Pending | - | 2023-04-18 | 2023-10-24 | 2024-10-22 | 2026-04-30 |
Release
Agora vamos “destrinchar” alguns termos da tabela apresentada anteriormente. O primeiro deles é referente à coluna “Release”, que se refere ao número da versão major do Node.js. O fato de um release ser par ou ímpar, diz muito sobre o seu comportamento:
Release |
---|
14.x |
16.x |
18.x |
19.x |
20.x |
Versões ímpares são lançadas em outubro e possuem um tempo de vida bem curto (cerca de 6 meses). Elas são criadas para testar funcionalidades e aplicar mudanças mais drásticas (isso quer dizer que não são versões estáveis para produção). Podemos compará-las com as versões beta, onde são testadas várias funcionalidades, sendo que o conjunto de funcionalidades aprovado se torna uma versão par.
Versões pares: como você deve estar imaginando, são as versões que possuem vida longa (cerca de 3 anos), lançadas em abril. As versões pares recebem continuamente atualizações de bugs, segurança, erros críticos e outras. Por esses motivos (atualizações constantes e longa vida), essas versões são consideradas estáveis.
Porém, como dito anteriormente, a cada seis meses é lançada uma nova versão major e, com isso, o status das versões se altera e isso traz um novo significado.
Status
Quando uma versão é lançada, ela recebe o status Current
(“Atual”, em português), ou seja, é considerada a última versão do Node.js, permanecendo com esse status durante seis meses, até o lançamento de uma nova versão. Isso vale tanto para uma versão ímpar (como a v19
, que permanece como current
de 10/2022 até 04/2023) quanto a versão par (como a v20
, que permanece como current
de 04/2023 até 10/2023), conforme podemos ver na tabela abaixo:
Release | Status | Initial Release | Active LTS Start | Maintenance Start | End-of-life |
---|---|---|---|---|---|
19.x | Current | 2022-10-18 | - | 2023-04-01 | 2023-06-01 |
20.x | Pending | 2023-04-18 | 2023-10-24 | 2024-10-22 | 2026-04-30 |
Em uma versão ímpar, após seis meses do seu lançamento (Initial Release), a versão recebe o status ‘Maintenance’ (Manutenção, em português), recebendo assim atualizações por 2 meses até chegar o fim de sua vida (End-of-life), quando receberá o status unstable
e se tornará uma versão descontinuada (passa a não receber mais atualizações).
Em uma versão par, como mostrado na tabela acima após os 6 meses de lançamento, o status mudará para active
(ativo, em português) que é quando uma versão terá um suporte de manutenção durante um longo prazo; ou seja, as famosas versões LTS que você encontra ao baixar o Node.js:
As versões LTS (Long-term support ou suporte a longo prazo, em português) permanecem com esse status por mais 1 ano e ainda possuem 18 meses de manutenção, o que nos traz uma garantia de cerca de 3 anos de atualizações para essa versão desde o seu lançamento.
Ao entrar em manutenção, a versão receberá apenas atualizações críticas de desempenho, bugs ou segurança.
Esse status, diferente das versões ímpares, dura 18 meses. A versão continua sendo considerada estável e pronta para a produção. Porém, você pode receber alguns avisos que em breve ela chegará ao seu último status, de unstable
, que significa que a versão chegou ao fim de sua vida e não receberá mais atualizações.
Devido a esse cronograma super preciso, teremos sempre 3 versões em manutenção e 1 versão como LTS. Em resumo, o status atual das versões é:
- Desde o lançamento da versão 18, a versão 10 chegou ao fim de sua vida. Daí você já sabe que qualquer versão anterior a 10 também está descontinuada;
- Na semana em que escrevemos este artigo (dia 25/10/2022), a versão 18 se tornou a versão LTS e assim permanecerá até entrar em manutenção daqui a 1 ano;
- A versão 19 continuará como
current
até 04/2023, onde será lançada a versão 20 do Node.js.
Vale se atentar que com o lançamento dessa nova versão em abril de 2023, a versão 12 do Node chegará ao fim de sua vida e a própria documentação do Node.js recomenda que você atualize seus projetos para uma versão LTS.
Mas vamos falar então das novidades da versão 18? Bora lá!
O que vem de novo
fetch (experimental)
Na versão 17 do Node.js foi anunciado que a API fetch que já existia nos navegadores chegou ao Node.js, como um módulo experimental, e com isso tivemos uma série de mudanças na forma de fazer requisições HTTP, e você pode conferir mais sobre essa revolução nesse artigo.
A novidade na versão 18 é que, diferente da versão 17, na qual era necessária uma flag para ativar essa funcionalidade, ela agora está disponível no escopo global por padrão!
Veja como está simples fazer uma requisição:
const res = await fetch('https://nodejs.org/api/documentation.json');
if (res.ok) {
const data = await res.json();
console.log(data);
}
Agora você pode realizar requisições de uma forma simples, sem se preocupar com possíveis alterações em bibliotecas externas e consequentemente com comprometimento de suas aplicações.
Caso o Node.js tenha reclamado sobre o await, inclua no seu arquivo package.json a linha "type": "module".
Além do fetch, as variáveis FormData, Headers, Request e Response se tornaram globais.
Outras APIs globais
- Um novo tipo de Buffer, o Blob, foi adicionado às APIs globais;
- Pensando em comunicação assíncrona, o BroadcastChannel também está exposto globalmente;
Ambas APIs não são mais experimentais.
- Foi adicionada uma API experimental do Web Streams permitindo acesso a streams recebidos pela rede para serem processados de acordo com a necessidade.
Test runner nativo
Talvez a novidade mais legal da versão 18 é a possibilidade de escrever testes sem a necessidade de uma biblioteca externa.
Graças ao módulo node:test
(ainda experimental) teremos um test runner do próprio Node.js!
Talvez tenham surgido algumas dúvidas como: “Mas já não conseguimos fazer testes com o módulo assert
?”, “já não existem bibliotecas como o jest, mocha e outros para testes?” e a principal “o que faz um test runner?”
Calma! Vamos esclarecer aqui cada uma dessas dúvidas.
Com o módulo nativo assert do Node.js nós conseguimos avaliar se dada sentença está conforme o esperado ou não:
import assert from "assert";
function soma(a,b) {
return a + b;
}
assert.ok(soma(1,2) === 3, "soma conforme o esperado")
assert.ok(soma(1,2) === 4, "1 + 2 não é 4, um AssertionError foi lançado")
Do exemplo acima, temos que o primeiro caso o método assert.ok() retorna o valor true
e nada acontece. No segundo caso, o método assert.ok()
retorna o valor false
e a pessoa recebe um AssertionError.
Caso queira saber o que é o AssertionError e demais erros que enfrentamos no Node.js, recomendo a leitura deste artigo.
Como você pode ver no código acima, é um simples teste que criei em um único arquivo. Mas o que acontece quando temos um número muito grande de arquivos? Como podemos automatizar e organizar a execução dos nossos testes?
Daí que temos bibliotecas externas famosas com Jest, Mocha, dentre outras que organizam os casos de teste e os executam para nós. E é exatamente esse o papel de um test runner, gerenciar a execução de diferentes testes de forma eficiente e organizada.
Com essa nova atualização do Node.js, o módulo node:test
implementa um test runner nativo, diminuindo assim a barreira para devs usarem testes e tendo uma maior padronização, visto que cada biblioteca tem sua sintaxe, sua forma de comunicar com APIs e seus métodos.
Lembrando que é uma funcionalidade experimental, então ainda é cedo para afirmar que essa funcionalidade irá substituir tais bibliotecas de terceiros que existem há anos! Cena para os próximos capítulos as próximas atualizações do Node.js.
Com o módulo node:test
podemos criar testes desta forma já familiar para quem usa frameworks de teste como o Jest:
import test from "node:test";
import assert from "assert";
function soma(a, b) {
return a + b;
}
test("1 + 1 = 2", () => {
const resultado = soma(1, 1);
const esperado = 2;
assert.strictEqual(resultado, esperado);
});
Você pode executar este código direto com o comando node <nome do arquivo.js>
no terminal; o test runner também pode ser chamado com a flag node --test
caso existam no diretório arquivos nomeados de acordo com a convenção .test.
(por exemplo, index.test.js
).
No caso acima, a saída no terminal será algo parecido com isso:
<user@computador>$ node --test
TAP version 13
# Subtest: /home/<nome do diretório>/index.test.js
ok 1 - /home/<nome do diretório>/index.test.js
---
duration_ms: 46.794508
...
1..1
# tests 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 51.208806
Este output do relatório de testes segue o protocolo TAP (Test Anything Protocol), uma forma mais “limpa” e padronizada de exibir relatórios de testes e bem diferente do que se costuma ver, por exemplo, no Jest. Você pode ver com mais detalhes a que se propõe o TAP no link indicado acima.
Esse módulo nos traz a possibilidade de fazer testes síncronos, assíncronos, subtestes e várias outras facilidades que você pode conferir na própria documentação, que está bem detalhada.
E algo que me chamou a atenção é que, diferente de módulos nativos como assert e fs, em que o uso do prefixo node é optativo, no módulo de teste do Node.js é obrigatória a importação do módulo com o prefixo node:
import test from 'node:test';
Isso pode ser o início do que chamamos de prefix-only core modules, onde os módulos nativos obrigatoriamente devem ter um prefixo, no caso, node:.
Isso é algo que divide a opinião na comunidade, pois alguns enxergam positiva a possibilidade de criar módulos com o mesmo nome, visto que os módulos com o prefixo terão prioridades sobre os userland modules, módulos criados pela comunidade. Outros enxergam como possíveis armadilhas, visto que, por um descuido do usuário, ele pode fazer a importação de um módulo malicioso que possui o mesmo nome que um módulo nativo.
Atualização da V8 para a versão 10.1
V8 é o interpretador JavaScript, também chamado de máquina virtual JavaScript e é através dele que conseguimos executar códigos JavaScript no Node.js. De forma bem simplista, é como se o Node.js fosse um carro e o motor para esse carro funcionar fosse a V8. A nova versão da V8 traz a possibilidade de usarmos os seguintes recursos:
- O método
findLast()
, que percorre um array na ordem inversa e retorna o valor do primeiro elemento que satisfaz a função de teste fornecida. Se nenhum elemento satisfizer a função de teste,undefined
é retornado;
No exemplo abaixo estamos usando o findLast()
para buscar o último número para do vetor arrayDeNumeros
.
const arrayDeNumeros = [1,2,3,4,5,6,7];
const ultimoElementoPar = arrayDeNumeros.findLast((element) => element % 2 === 0);
console.log(ultimoElementoPar);
//Teremos como saída o valor 6
- Semelhante à funcionalidade anterior, temos o
findLastIndex()
que irá retornar o índice do elemento de um vetor que atenda a função de teste fornecida. Se nenhum elemento satisfizer a função de teste,undefined
é retornado.
No exemplo abaixo, vamos percorrer uma lista de frutas e buscar o índice do vetor onde temos a última ocorrência da fruta banana
. Perceba que banana
está na terceira posição (índice igual a 2) e na sétima posição (índice igual a 6). Teremos como o valor 6, sendo o índice da última aparição da fruta “banana” no array.
const arrayDeFrutas = ["uva", "maçã", "banana", "abacaxi", "abacate", "morango", "banana", "melão"];
const fruta = arrayDeFrutas.findLastIndex((element) => element === "banana");
console.log(fruta);
- Melhorias na API Intl.Locale;
- Foi adicionada a função Intl.supportedValuesOf(code) que retorna a matriz de identificadores suportados na v8 para as APIs internacionais e permite que se descubra facilmente qual valor é suportado pela implementação;
- Desempenho aprimorado para inicialização de propriedades de classe e métodos privados (a inicialização deles agora é tão rápida quanto os armazenamentos de propriedades comuns).
Conclusão
Neste artigo vimos as principais novidades do Node.js versão 18 desde o seu lançamento. Uma versão cheia de boas novas como test runner nativo, fetch global, novos métodos para trabalharmos com arrays e diversas melhorias relacionadas ao desempenho da V8. Existem alguns outros ajustes feitos que você pode conferir nos releases do Node.js.
Aqui na Alura, você pode dar o seu primeiro mergulho em programação back-end com a formação JavaScript para back-end, onde você verá desde as partes fundamentais de qualquer linguagem de programação até o consumo de APIs e criação de bibliotecas com Node.js. E para mergulhos em regiões mais profundas com o Node.js, confira a formação Node.js com Express para aprender a construir backends para sites escaláveis.