29
loading...
This website collects cookies to deliver better user experience
<script type="module">
já faz algum tempo, só agora com a depreciação oficial do Node 10 em favor do Node 16 que vamos poder ter este suporte completo no servidor!Veja um exemplo de uso de módulos ESM no browser neste repositório
CommonJS
, com ele temos a sintaxe clássica do require()
no topo de módulos Node.js, mas ele não era suportado oficialmente pelos browsers sem a ajuda de plugins externos como o Browserify e o RequireJS.require()
, mas sim através de uma chave imports
e outra exports
.function foo () { }
module.exports = foo
module
, dentro deste nós estamos definindo uma chave exports
que contém a lista de coisas que vamos exportar para este módulo. Depois, outro arquivo poderá importa-lo como:const foo = require('./foo')
./
caso contrário a resolução de módulos irá buscar nas pastas conhecidas por módulos existentes.module
que será utilizado para descobrir o que podemos ou não importar deste módulo.imports
e exports
, sempre que parseadas, o compilador irá identificar um novo símbolo que será exportado ou importado e adicionar automaticamente à lista de exportação.import foo from './foo.js'
require()
podemos importar vários tipos de arquivos além do .js
, como JSON. O que nos leva para a segunda grande mudança, muitos dos tipos de arquivos que antes eram suportados por importação direta, agora precisam ser lidos via fs.promises.readFile
.require('arquivo.json')
, porém agora não temos mais essa capacidade e precisamos utilizar o módulo de leitura de arquivos para poder ler o JSON nativamente.Existe uma API ainda experimental para poder permitir a funcionalidade no Node.js mas ela vem desativada por padrão, veja mais sobre ela aqui
import {promises as fs} from 'fs';
const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8'))
file:
, node:
e data:
. Isso significa que podemos importar um módulo nativo do Node com:import fs from 'node:fs/promises'
.mjs
, que é muito útil porque não precisamos nos preocupar com a configuração, já que o Node e o JavaScript já sabem resolver este tipo de arquivo.__dirname
dentro de módulos no Node.js. Isto porque, por padrão, módulos possuem um objeto chamado import.meta
, que possui todas as informações daquele módulo, que antes eram populadas pelo runtime em uma variável global, ou seja, temos um estado global a menos para nos preocupar.__dirname
, uma boa opção é utilizar o fileURLToPath
:import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
new URL(import.meta.url)
já que muitas APIs do Node aceitam URLs como parâmetros.async
para executar um await
, mas isso só para módulos! Então coisas deste tipo serão muito comuns:async function foo () {
console.log('Hello')
}
await foo() // Hello
import()
, e estas expressões são suportadas dentro do CJS para carregar módulos que estão escritos em ESM. Então podemos realizar uma importação de um módulo ESM desta forma:// esm.mjs
export function foo () {
return 1
}
// cjs.js
const esm = import('./esm.mjs')
esm.then(console.log) // { foo: [λ: foo], [Symbol(Symbol.toStringTag)]: 'Module' }
import
para um módulo CJS, porém temos que ter em mente que todo módulo CJS vem com um namespace, no caso padrão de um módulo como o abaixo, o namespace será o default
:function foo () { }
module.exports = foo
import {default as cjs} from './cjs.js'
import cjs from './cjs.js'
Se você quiser observar como é um export de um módulo CJS, basta executar um import geral com import * as cjs from './cjs.js'
e logar o resultado no console.
exports.foo = () => {}
exports.bar = () => {}
exports
para um import
nomeado, ou seja, vamos poder fazer isso:import { foo } from './cjs.js'
require
, exports
ou module.exports
filename
e dirname
, ao invés disso temos import.meta.url
fs.promises.readFile
ou então module.createRequire
NODE_PATH
require.resolve
para resolver caminhos relativos, ao invés disso podemos utilizar a montagem de uma URL com new URL('./caminho', import.meta.url)
require.extensions
ou require.cache
import {foo} from './module?query=string'
, isso é interessante para quando temos que fazer um bypass do cache..mjs
ou através da adição da chave type
no package.json
com o valor "module"
, isso vai permitir que você continue usando extensões .js
mas que tenham módulos ao invés de CJS.// Usando CJS
{
"name": "pacote",
"version": "0.0.1",
"description": "",
"main": "index.js",
}
// Usando ESM
{
"name": "pacote",
"version": "0.0.1",
"description": "",
"type": "module",
"exports": "./index.mjs",
}
type
no seu package.json
, basta que você altere a chave "main"
, para exports
como neste exemplo:// Usando CJS
{
"name": "pacote",
"version": "0.0.1",
"description": "",
"main": "index.js",
}
// Usando ESM
{
"name": "pacote",
"version": "0.0.1",
"description": "",
"exports": "./index.mjs",
}
engines
restringindo quais são as versões do Node que podem executar seu pacote sem quebrar, para esta chave use os valores "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
.'use strict'
em algum arquivo, remova-os.require
por import
e a adição das extensões nos nomes de arquivos locais. Como já falamos anteriormente.package.json
como se tivéssemos criando um módulo JS normal. Isso significa fazer esta lista de coisas:"type": "module"
"main": "index.js"
por "exports": "./index.js"
"engines"
com o valor da propriedade "node"
para as versões que mostramos anteriormentetsconfig.json
com tsc --init
e modificá-lo para adicionar uma chave "module": "ES2020"
. Isso já será suficiente para os arquivos finais serem expostos como ESM, porém existem algumas precauções que temos que ter quando escrevemos nossos arquivos em TypeScript:import index from '.'
, sempre use o caminho completo import index from './index.js'
node:
para importar módulos nativos do Node como o fs
.js
, mesmo que estejamos usando .ts
, ou seja, se dentro de um arquivo a.ts
você quiser importar o módulo presente em b.ts
, você precisará de um import do tipo import {b} from './b.js'
.