O uso do Node.js requer vs. importação / exportação do ES6

930

Em um projeto em que estou colaborando, temos duas opções em qual sistema de módulos podemos usar:

  1. Importando módulos usando requiree exportando usando module.exportse exports.foo.
  2. Importando Módulos Usando ES6 importe Exportando Usando ES6export

Existem benefícios de desempenho em usar um sobre o outro? Há mais alguma coisa que devemos saber se devemos usar os módulos ES6 em relação aos do nó?

kpimov
fonte
9
node --experimental-modules index.mjspermite usar importsem Babel e funciona no Nó 8.5.0+. Você pode (e deve) também publicar seus pacotes npm como ESModule nativo , com compatibilidade com versões anteriores da requiremaneira antiga .
Dan Dascalescu 26/01/19

Respostas:

728

Existem benefícios de desempenho em usar um sobre o outro?

Lembre-se de que ainda não existe um mecanismo JavaScript que suporte nativamente os módulos ES6. Você mesmo disse que está usando Babel. Babel converte importe exportdeclaração para CommonJS ( require/ module.exports) por padrão de qualquer maneira. Portanto, mesmo se você usar a sintaxe do módulo ES6, estará usando o CommonJS oculto se executar o código no Nó.

Existem diferenças técnicas entre os módulos CommonJS e ES6, por exemplo, o CommonJS permite carregar módulos dinamicamente. O ES6 não permite isso, mas há uma API em desenvolvimento para isso .

Como os módulos ES6 fazem parte do padrão, eu os usaria.

Felix Kling
fonte
16
Eu tentei usar ES6 importcom requiremas eles trabalharam de forma diferente. O CommonJS exporta a própria classe enquanto houver apenas uma classe. As exportações do ES6, como existem várias classes, devem ser usadas .ClassNamepara obter a classe exportada. Existem outras diferenças que realmente afeta a implementação
Thellimist
78
@ Entei: Parece que você deseja uma exportação padrão, não uma exportação nomeada. module.exports = ...;é equivalente a export default .... exports.foo = ...é equivalente a export var foo = ...;
Felix Kling
10
Vale ressaltar que, embora o Babel finalmente transpile importpara o CommonJS no Node, usado juntamente com o Webpack 2 / Rollup (e qualquer outro empacotador que permita agitar a árvore ES6), é possível terminar com um arquivo significativamente menor que as trocas de código equivalentes do Node através do usorequire exatamente porque ES6 permite a análise estática de importação / exportação. Embora isso não faça diferença para o Node (ainda), certamente pode ocorrer se o código acabar como um único pacote de navegador.
Lee Benson
5
a menos que você precisa fazer uma importação dinâmica
chulian
3
Os módulos ES6 estão no V8 mais recente e também estão chegando em outros navegadores atrás de sinalizadores. Consulte: medium.com/dev-channel/…
Nexii Malthus
180

Existem vários usos / recursos que você pode querer considerar:

Exigir:

  • Você pode ter carregamento dinâmico onde o nome do módulo carregado não é predefinido / estático, ou onde você carrega condicionalmente um módulo apenas se for "realmente necessário" (dependendo de um determinado fluxo de código).
  • O carregamento é síncrono. Isso significa que, se você tiver vários requires, eles serão carregados e processados ​​um a um.

ES6 Importações:

  • Você pode usar importações nomeadas para carregar seletivamente apenas as peças necessárias. Isso pode economizar memória.
  • A importação pode ser assíncrona (e no atual ES6 Module Loader, na verdade é) e pode ter um desempenho um pouco melhor.

Além disso, o sistema de módulos Exigir não é baseado em padrão. É altamente improvável que se torne padrão agora que existem módulos ES6. No futuro, haverá suporte nativo para os Módulos ES6 em várias implementações, o que será vantajoso em termos de desempenho.

Amit
fonte
16
O que faz você pensar que as importações do ES6 são assíncronas?
Felix Kling
5
@FelixKling - combinação de várias observações. Usando o JSPM (ES6 Module Loader ...) notei que quando uma importação modifica o espaço para nome global, o efeito não é observado em outras importações (porque ocorrem de forma assíncrona. Isso também pode ser visto no código transpilado). Além disso, uma vez que é o comportamento (1 import não afeta outros) há nenhuma razão para não fazê-lo, assim que poderia ser implementação dependentes
Amit
35
Você mencionou algo muito importante: carregador de módulos. Embora o ES6 forneça a sintaxe de importação e exportação, ele não define como os módulos devem ser carregados. A parte importante é que as declarações são estaticamente analisáveis, para que as dependências possam ser determinadas sem executar o código. Isso permitiria que um carregador de módulo carregasse um módulo de forma síncrona ou assíncrona. Mas os módulos ES6 por si só não são síncronos ou assíncronos.
Felix Kling
5
O carregador de módulo @FelixKling ES6 foi marcado no OP, portanto, presumo que seja relevante para a resposta. Também afirmei que, com base em observações assíncronas, é o comportamento atual, bem como a possibilidade no futuro (em qualquer implementação), por isso é um ponto relevante a ser considerado. Você acha que está errado?
Amit
10
Eu acho que é importante não confundir o sistema / sintaxe do módulo com o carregador do módulo. Por exemplo, se você desenvolver para o nó, provavelmente estará compilando os módulos ES6 de requirequalquer maneira, portanto, estará usando o sistema de módulos e o carregador do Node de qualquer maneira.
Felix Kling
41

As principais vantagens são sintáticas:

  • Sintaxe mais declarativa / compacta
  • Os módulos ES6 basicamente tornam obsoleto o UMD (Universal Module Definition) - essencialmente remove o cisma entre CommonJS e AMD (servidor x navegador).

É improvável que você veja quaisquer benefícios de desempenho com os módulos ES6. Você ainda precisará de uma biblioteca extra para agrupar os módulos, mesmo quando houver suporte completo para os recursos do ES6 no navegador.

snozza
fonte
4
Você poderia esclarecer por que é necessário um empacotador, mesmo quando os navegadores têm suporte completo ao módulo ES6?
E. Sundin
1
Desculpas, editadas para fazer mais sentido. Eu quis dizer que o recurso de importação / exportação de módulos não é implementado em nenhum navegador nativo. Um transpiler ainda é necessário.
Snozza 4/07
16
Parece um pouco contraditório para mim. Se houver suporte total , qual é o objetivo do empacotador? Há algo faltando nas especificações do ES6? O que o bundler realmente faria se não estivesse disponível em um ambiente totalmente suportado ?
E. Sundin
1
Como @snozza disse ... "o recurso de módulos de importação / exportação não é implementado em nenhum navegador ingenuamente. Um transpiler ainda é necessário"
robertmain
2
Você não precisa mais de bibliotecas extras. Desde a v8.5.0 (lançada há mais de um ano), node --experimemntal-modules index.mjsvocê pode usar importsem o Babel. Você pode (e deve) também publicar seus pacotes npm como ESModule nativo, com compatibilidade com versões anteriores da requiremaneira antiga . Muitos navegadores também suportam importações dinâmicas nativamente.
Dan Dascalescu 26/09
38

Existem benefícios de desempenho em usar um sobre o outro?

A resposta atual é não, porque nenhum dos mecanismos atuais do navegador é implementado import/exportno padrão ES6.

Alguns gráficos de comparação http://kangax.github.io/compat-table/es6/ não levam isso em consideração. Portanto, quando vir quase todos os greens do Chrome, tenha cuidado. importA palavra-chave do ES6 não foi levada em consideração.

Em outras palavras, os motores atuais do navegador, incluindo V8 não pode importar novo arquivo JavaScript do arquivo principal JavaScript através de qualquer directiva JavaScript.

(Podemos ainda estar a apenas alguns bugs ou anos até que o V8 implemente isso de acordo com a especificação ES6.)

Este documento é o que precisamos e este documento é o que devemos obedecer.

E o padrão ES6 disse que as dependências do módulo deveriam estar lá antes de lermos o módulo como na linguagem de programação C, onde tínhamos .harquivos (cabeçalhos) .

Essa é uma estrutura boa e bem testada, e tenho certeza que os especialistas que criaram o padrão ES6 tinham isso em mente.

É isso que permite que o Webpack ou outros pacotes de pacotes otimizem o pacote em alguns casos especiais e reduzam algumas dependências do pacote que não são necessárias. Mas, nos casos em que temos dependências perfeitas, isso nunca acontecerá.

Isso levará algum tempo até que o import/exportsuporte nativo seja ativado e a requirepalavra - chave não vá a lugar algum por um longo período de tempo.

O que é require?

Esta é a node.jsmaneira de carregar módulos. ( https://github.com/nodejs/node )

O nó usa métodos no nível do sistema para ler arquivos. Você basicamente depende disso ao usar require. requireterminará em algumas chamadas do sistema uv_fs_open(depende do sistema final, Linux, Mac, Windows) para carregar o arquivo / módulo JavaScript.

Para verificar se isso é verdade, tente usar o Babel.js e você verá que a importpalavra - chave será convertida em require.

insira a descrição da imagem aqui

prosti
fonte
2
Na verdade, há uma área em que o desempenho pode ser melhorado - tamanho do pacote. O uso importem um processo de compilação Webpack 2 / Rollup pode reduzir potencialmente o tamanho do arquivo resultante, 'agitando a árvore' módulos / códigos não utilizados, que de outra forma poderiam acabar no pacote final. Tamanho menor do arquivo = mais rápido para baixar = mais rápido para iniciar / executar no cliente.
Lee Benson
2
o raciocínio era que nenhum navegador atual no planeta Terra permite a import palavra - chave nativamente. Ou isso significa que você não pode importar outro arquivo JavaScript de um arquivo JavaScript. É por isso que você não pode comparar os benefícios de desempenho desses dois. Mas é claro que ferramentas como Webpack1 / 2 ou Browserify podem lidar com compactação. Eles são pescoço a pescoço: gist.github.com/substack/68f8d502be42d5cd4942
prosti
4
Você está com vista para 'tremer de árvore'. Em nenhum lugar do seu link principal é discutido o abalo de árvores. O uso de módulos ES6 o habilita, porque importe exportsão declarações estáticas que importam um caminho de código específico, enquanto requirepodem ser dinâmicas e, portanto, agrupar o código que não é usado. O benefício de desempenho é indirect-- Webpack 2 e / ou Rollup pode potencialmente resultar em tamanhos de pacote menores que são mais rápidos para download, e, portanto, aparecem snappier para o usuário final (de um navegador). Isso funciona apenas se todo o código for escrito nos módulos ES6 e, portanto, as importações podem ser analisadas estaticamente.
Lee Benson
2
Atualizei a resposta @LeeBenson, acho que se considerarmos o suporte nativo dos mecanismos do navegador, ainda não podemos comparar. O que é uma opção útil de três agitações usando o Webpack, também pode ser alcançado antes mesmo de definirmos os módulos CommonJS, já que para a maioria das aplicações reais sabemos quais módulos devem ser usados.
Prosti
1
Sua resposta é totalmente válida, mas acho que estamos comparando duas características diferentes. Tudo import/export é convertido em require, concedido. Mas o que acontece antes desta etapa pode ser considerado um aprimoramento do "desempenho". Exemplo: se lodashestiver escrito no ES6 e você import { omit } from lodash, o pacote final conterá SOMENTE 'omitir' e não os outros utilitários, enquanto um simples require('lodash')importará tudo. Isso aumentará o tamanho do pacote, levará mais tempo para baixar e, portanto, diminuirá o desempenho. Isso é válido apenas no contexto do navegador, é claro.
Lee Benson
31

O uso de módulos ES6 pode ser útil para 'trepidação de árvores'; ou seja, habilitar o Webpack 2, Rollup (ou outros empacotadores) para identificar caminhos de código que não são usados ​​/ importados e, portanto, não fazem parte do pacote resultante. Isso pode reduzir significativamente o tamanho do arquivo, eliminando o código que você nunca precisará, mas o CommonJS é fornecido por padrão porque o Webpack et al não têm como saber se é necessário.

Isso é feito usando a análise estática do caminho do código.

Por exemplo, usando:

import { somePart } 'of/a/package';

... fornece ao bundler uma dica que package.anotherPartnão é necessária (se não for importada, não pode ser usada - certo?), para não incomodá-lo.

Para habilitar isso no Webpack 2, você precisa garantir que seu transpiler não esteja cuspindo módulos CommonJS. Se você estiver usando o es2015plug-in com babel, poderá desativá-lo da seguinte .babelrcmaneira:

{
  "presets": [
    ["es2015", { modules: false }],
  ]
}

O pacote cumulativo e outros podem funcionar de maneira diferente - visualize os documentos se você estiver interessado.

Lee Benson
fonte
2
também grande para árvore balançando 2ality.com/2015/12/webpack-tree-shaking.html
prosti
25

Quando se trata de carregamento assíncrono ou talvez lento, import ()é muito mais poderoso. Veja quando exigimos o componente de maneira assíncrona, depois o usamos de importmaneira assíncrona como na constvariável using await.

const module = await import('./module.js');

Ou se você quiser usar require()então,

const converter = require('./converter');

A coisa é import()realmente assíncrona por natureza. Conforme mencionado por neehar venugopal no ReactConf , você pode usá-lo para carregar dinamicamente componentes de reação para a arquitetura do lado do cliente.

Também é muito melhor quando se trata de roteamento. Essa é a única coisa especial que faz com que o log de rede faça o download de uma parte necessária quando o usuário se conecta a um site específico ao seu componente específico. por exemplo, a página de login antes do painel não faria o download de todos os componentes do painel. Porque o que é necessário é atual, isto é, o componente de login, que somente será baixado.

O mesmo vale para export: ES6 exportsão exatamente os mesmos que para CommonJS module.exports.

NOTA - Se você estiver desenvolvendo um projeto node.js., será necessário usá-lo estritamente, require()pois o nó gerará um erro de exceção como invalid token 'import'se fosse usar import. Portanto, o nó não suporta instruções de importação.

ATUALIZAÇÃO - Conforme sugerido por Dan Dascalescu : Desde a v8.5.0 (lançada em setembro de 2017), node --experimental-modules index.mjsvocê pode usar importsem Babel. Você pode (e deve) também publicar seus pacotes npm como ESModule nativo, com compatibilidade com versões anteriores dos antigosrequire maneira .

Veja isso para obter mais informações sobre onde usar importações assíncronas - https://www.youtube.com/watch?v=bb6RCrDaxhw

Conheça Zaveri
fonte
1
Então, a exigência será sincronizada e esperará?
baklazan
1
Pode dizer de fato!
Conheça Zaveri
15

O mais importante a saber é que os módulos ES6 são, de fato, um padrão oficial, enquanto os módulos CommonJS (Node.js.) não são.

Em 2019, os módulos ES6 são suportados por 84% dos navegadores. Enquanto o Node.js os coloca atrás de um sinalizador --experimental-modules , também há um pacote de nós conveniente chamado esm , que facilita a integração.

Outro problema que você provavelmente encontrará entre esses sistemas de módulos é a localização do código. O Node.js assume que a origem é mantida em um node_modulesdiretório, enquanto a maioria dos módulos ES6 é implantada em uma estrutura de diretórios simples. Isso não é fácil de conciliar, mas pode ser feito invadindo seu package.jsonarquivo com scripts de pré e pós-instalação. Aqui está um exemplo de módulo isomórfico e um artigo explicando como ele funciona.

isysd
fonte
8

Eu pessoalmente uso a importação porque, podemos importar os métodos necessários, membros usando a importação.

import {foo, bar} from "dep";

Nome do arquivo : dep.js

export foo function(){};
export const bar = 22

O crédito é para Paul Shan. Mais informações .

chandoo
fonte
1
Ótima escolha! Você também está publicando seus pacotes npm como ESModule nativo, com compatibilidade com versões anteriores da requiremaneira antiga ?
Dan Dascalescu 26/09/19
6
Você pode fazer o mesmo com exigir!
Suisse
4
const {a,b} = require('module.js'); funciona tão bem ... se você exportar aeb
BananaAcid
module.exports = { a: ()={}, b: 22 }- A segunda parte da @BananaAcid respones
Seth McClaine
7

No momento, na importação do ES6, a exportação é sempre compilada no CommonJS ; portanto, não há benefício em usar um ou outro. Embora o uso do ES6 seja recomendado, pois deve ser vantajoso quando o suporte nativo dos navegadores é lançado. O motivo é que você pode importar parciais de um arquivo enquanto no CommonJS você precisa exigir todo o arquivo.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Abaixo está o uso comum deles.

Padrão de exportação do ES6

// hello.js
function hello() {
  return 'hello'
}
export default hello

// app.js
import hello from './hello'
hello() // returns hello

O ES6 exporta vários e importa vários

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
export { hello1, hello2 }

// app.js
import { hello1, hello2 } from './hello'
hello1()  // returns hello1
hello2()  // returns hello2

CommonJS module.exports

// hello.js
function hello() {
  return 'hello'
}
module.exports = hello

// app.js
const hello = require('./hello')
hello()   // returns hello

CommonJS module.exports multiple

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
module.exports = {
  hello1,
  hello2
}

// app.js
const hello = require('./hello')
hello.hello1()   // returns hello1
hello.hello2()   // returns hello2
Hasan Sefa Ozalp
fonte
0

Não sei por que (provavelmente otimização - carregamento lento?) Está funcionando assim, mas notei que importtalvez não seja possível analisar o código se os módulos importados não forem usados.
O que pode não ser o comportamento esperado em alguns casos.

Tome a classe Foo odiada como nossa dependência de amostra.

foo.ts

export default class Foo {}
console.log('Foo loaded');

Por exemplo:

index.ts

import Foo from './foo'
// prints nothing

index.ts

const Foo = require('./foo').default;
// prints "Foo loaded"

index.ts

(async () => {
    const FooPack = await import('./foo');
    // prints "Foo loaded"
})();

Por outro lado:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
l00k
fonte