Fala turma, para reforçar os conhecimentos que tive estudando EcmaScript modules, resolvi fazer um pequeno monorepo para praticar. Vamos conseguir ver na prática como funciona o suporte do NodeJS e dos browsers para o ESM, incluindo utilizar o ESM com o CommonJS, e também como utilizar o ESM com o TypeScript.
Exemplo 1: "type": "module"
Vamos começar com o exemplo mais utilizado. Eu criei uma pasta chamada esm-node-type-module
para esse primeiro exemplo, dentro dela vamos criar um arquivo package.json
com o seguinte conteúdo:
{ "name": "esm-node-type-module", "version": "1.0.0", "description": "Exemplo de uso do ESM com o type module", "main": "index.js", "scripts": { "start": "node index.js" }, "author": "Caio Fuzatto", "license": "MIT" }
Depois vamos criar os arquivos index.js
e module1.js
com o seguinte conteúdo:
// index.js import { test1 } from './module1.js'; test1();
Uma das primeiras coisas que vamos notar é a extensão do arquivo deve ser especificada no import, diferente do "ESM transpilado" que conseguia inferir a extensão do arquivo. Expliquei tudo sobre isso no post Entendendo EcmaScript Modules.
// module1.js export const test1 = () => { console.log('Estamos dentro de um módulo ESM'); }
Agora vamos rodar o comando yarn start
e ver o resultado:
Opa, erro ao carregar o módulo. Mas felizmente o NodeJS nos dá duas dicas do que fazer, nesse primeiro exemplo vamos utilizar a primeira dica e adicionar "type": "module"
no nosso package.json
. Agora é só rodar novamente e ver o resultado:
$ node index.js Estamos dentro de um módulo ESM
Compatibilidade com o CommonJS
O NodeJS também trouxe um suporte aos módulos ESM com o CommonJS, ou seja, podemos utilizar o ESM com o CommonJS. Para isso vamos criar um arquivo module2.cjs
com o seguinte conteúdo:
const test2 = async () => { await new Promise((resolve) => setTimeout(resolve, 2000)); console.log("Estamos dentro de um módulo CommonJS"); }; module.exports = { test2 };
Observe que a extensão do arquivo é
.cjs
, isso é necessário para o NodeJS entender que esse arquivo é um módulo CommonJS.
E agora vamos importar esse módulo no nosso index.js
:
import { test1 } from './module1.js'; import { test2 } from './module2.cjs'; test1(); await test2();
Fiz uma função
async
para você ver que o ESM suporta o uso de await no top-level de módulos. Incrível né?
Agora vamos rodar o comando yarn start
e ver o resultado, que após 2 segundos será o seguinte:
$ node index.js Estamos dentro de um módulo ESM Estamos dentro de um módulo CommonJS
Exemplo 2: Extensão .mjs
Vamos agora para o segundo exemplo, que é o uso da extensão .mjs
. Como já estamos familiarizados, vamos direto agora em?
Para isso vamos criar uma pasta chamada esm-node-mjs
e dentro dela vamos criar um arquivo package.json
com o seguinte conteúdo:
{ "name": "esm-node-mjs", "version": "1.0.0", "description": "Exemplo de uso do ESM com a extensão .mjs", "main": "index.mjs", "scripts": { "start": "node index.mjs" }, "author": "Caio Fuzatto", "license": "MIT" }
Observe que nesse exemplo já estamos utilizando a extensão
.mjs
na propriedademain
e no scriptstart
.
Agora vamos copiar os arquivos index.js
e module1.js
do exemplo anterior e renomear para index.mjs
e module1.mjs
respectivamente. Já o module2.cjs
vamos renomear para module2.js
. Além disso é importante ajustar os imports na index né? Confira como ficou:
import { test1 } from './module1.mjs'; import { test2 } from './module2.js'; test1(); await test2();
Percebeu que inverteu o jogo? Agora os módulos ESM precisam ter a extensão .mjs
e os CommonJS utilizam a .js
, enquanto no outro exemplo era o contrário. Agora vamos rodar o comando yarn start
e temos o mesmo resultado:
$ node index.mjs Estamos dentro de um módulo ESM Estamos dentro de um módulo CommonJS
Exemplo 3: ESM no frontend
Vamos agora para o terceiro exemplo, que é o uso do ESM no frontend. Para isso vamos criar uma pasta chamada esm-frontend
e dentro dela vamos criar um arquivo app.js
com o seguinte conteúdo:
import { test1 } from './module1.js'; document.getElementById('content').textContent = await test1();
Bem semelhante aos exemplos de NodeJS, a diferença aqui é que estamos utilizando o
document
para alterar o conteúdo de um elemento HTML.
Agora vamos criar nosso módulo, para isso vamos criar um arquivo module1.js
com o seguinte conteúdo:
export const test1 = async () => { console.log("Estamos dentro de um módulo ESM | ", new Date().toISOString()); await new Promise((resolve) => setTimeout(resolve, 2000)); console.log("Conteúdo carregou | ", new Date().toISOString()); return "Fui gerado por um módulo ESM"; };
E por fim, a página HTML que vai carregar o nosso módulo. Para isso vamos criar um arquivo index.html
com o seguinte conteúdo:
<!DOCTYPE html> <head> <title>ESM no Frontend</title> </head> <body> <div id="content">Carregando...</div> <script type="module" src="app.js"></script> </body>
Observe que estamos utilizando o atributo
type="module"
para indicar que o arquivoapp.js
é um módulo ESM.
Para testar (e não tomar erro de CORS) vou utilizar a lib HTTP-SERVE. Você pode rodar npx http-serve
dentro da pasta esm-frontend
e acessar a URL http://localhost:8080
para ver o resultado:
Exemplo 4: ESM nativo e Typescript
Vamos agora para o quarto exemplo, que é o uso do ESM nativo com o TypeScript. Para isso vamos criar uma pasta chamada esm-typescript
e dentro dela vamos criar um arquivo package.json
com o seguinte conteúdo:
{ "name": "esm-node-type-module", "version": "1.0.0", "description": "Exemplo de uso do ESM com o type module", "main": "index.js", "license": "MIT", "author": "Caio Fuzatto", "scripts": { "start": "ts-node-esm index.ts" }, "type": "module", "devDependencies": { "ts-node": "^10.9.1", "typescript": "^5.2.2" } }
Observe que estamos utilizando o
ts-node-esm
para rodar o TypeScript com o ESM nativo. Você pode ver mais sobre no Github do ts-node
Agora vamos criar nosso arquivo module1.ts
com o seguinte conteúdo:
export const test1 = async () => { await new Promise((resolve) => setTimeout(resolve, 2000)); console.log("Estamos dentro de um módulo ESM nativo no Typescript"); };
Até aqui tudo ok né? Agora vamos criar o index.ts
com o seguinte conteúdo:
import { test1 } from './module1.js'; await test1();
Observe que estamos importando o módulo
module1.js
e nãomodule1.ts
. Isto é porque o TypeScript é transpilado para JavaScript, e o Node.js vai executar o código JavaScript transpilado, não o código TypeScript original.
Agora vamos rodar o comando yarn start
e ver o resultado:
$ ts-node-esm index.ts Estamos dentro de um módulo ESM nativo no Typescript
Conclusão
Bom pessoal, espero que tenham gostado do conteúdo. Conseguimos ver várias formas de utilizar o ESM nativo do NodeJS e dos browsers, e também como utilizar o ESM com o CommonJS. Vale sempre a pena avaliar a situação do projeto para escolher qual desses caminhos seguir, se precisar de ajuda da uma olhada no meu post Entendendo o EcmaScript Modules. Você pode ver o código completo no Github. Até a próxima!
Top comments (0)