L'uso di Node.js richiede l'importazione / esportazione ES6


930

In un progetto a cui sto collaborando, abbiamo due scelte su quale sistema di moduli possiamo usare:

  1. Importare moduli usando requireed esportare usando module.exportse exports.foo.
  2. Importazione di moduli tramite ES6 imported esportazione tramite ES6export

Ci sono vantaggi prestazionali nell'usare l'uno rispetto all'altro? C'è qualcos'altro che dovremmo sapere se dovessimo usare i moduli ES6 su quelli Node?


9
node --experimental-modules index.mjsti permette di usare importsenza Babele e funziona in Nodo 8.5.0+. Puoi (e dovresti) anche pubblicare i tuoi pacchetti npm come ESModule nativo , con retrocompatibilità alla vecchia requiremaniera.
Dan Dascalescu il

Risposte:


728

Ci sono vantaggi prestazionali nell'usare l'uno rispetto all'altro?

Tieni presente che non esiste ancora un motore JavaScript che supporti nativamente i moduli ES6. Ti sei detto che stai usando Babele. Babel converte importe exportdichiara comunque in CommonJS ( require/ module.exports) di default. Quindi, anche se si utilizza la sintassi del modulo ES6, si utilizzerà CommonJS sotto il cofano se si esegue il codice in Nodo.

Esistono differenze tecniche tra i moduli CommonJS ed ES6, ad esempio CommonJS consente di caricare i moduli in modo dinamico. ES6 non lo consente, ma è in fase di sviluppo un'API per questo .

Dato che i moduli ES6 fanno parte dello standard, li userei.


16
Ho provato ad usare ES6 importcon requirema hanno lavorato in modo diverso. CommonJS esporta la classe stessa mentre esiste solo una classe. Esporta ES6 come se ci fossero più classi, quindi devi usare .ClassNameper ottenere la classe esportata. Ci sono altre differenze che
incidono

78
@Entei: sembra che tu voglia un'esportazione predefinita, non un'esportazione denominata. module.exports = ...;è equivalente a export default .... exports.foo = ...è equivalente a export var foo = ...;
Felix Kling,

10
Vale la pena notare che anche se Babel alla fine traspila importa CommonJS in Node, usato insieme a Webpack 2 / Rollup (e qualsiasi altro bundler che consente lo scuotimento dell'albero ES6), è possibile finire con un file che è significativamente più piccolo dell'equivalente codice Node crunches utilizzando requireesattamente a causa del fatto che ES6 consente un'analisi statica delle importazioni / esportazioni. Anche se questo non farà la differenza per Node (ancora), sicuramente può farlo se il codice finirà per essere un singolo pacchetto di browser.
Lee Benson,

5
a meno che non sia necessario eseguire un'importazione dinamica
chulian,

3
I moduli ES6 sono nell'ultimo V8 e arrivano anche in altri browser dietro le bandiere. Vedi: medium.com/dev-channel/…
Nexii Malthus

180

Esistono diversi usi / funzionalità che potresti prendere in considerazione:

Richiedere:

  • È possibile avere un caricamento dinamico in cui il nome del modulo caricato non è predefinito / statico o in cui si carica in modo condizionale un modulo solo se è "veramente richiesto" (a seconda di un determinato flusso di codice).
  • Il caricamento è sincrono. Ciò significa che se si dispone di più messaggi require, questi vengono caricati ed elaborati uno per uno.

Importazioni ES6:

  • È possibile utilizzare le importazioni con nome per caricare selettivamente solo i pezzi necessari. Questo può risparmiare memoria.
  • L'importazione può essere asincrona (e attualmente nel caricatore di moduli ES6, in effetti lo è) e può offrire risultati leggermente migliori.

Inoltre, il sistema del modulo Require non è basato su standard. È altamente improbabile che diventi standard ora che esistono moduli ES6. In futuro ci sarà il supporto nativo per i moduli ES6 in varie implementazioni, il che sarà vantaggioso in termini di prestazioni.


16
Cosa ti fa pensare che le importazioni ES6 siano asincrone?
Felix Kling, l'

5
@FelixKling - combinazione di varie osservazioni. Usando JSPM (ES6 Module Loader ...) ho notato che quando un'importazione ha modificato lo spazio dei nomi globale, l'effetto non è osservato all'interno di altre importazioni (perché si verificano in modo asincrono .. Questo può essere visto anche nel codice transpiled). Inoltre, dal momento che è il comportamento (1 importazione non influisce altri) non c'è ragione per non farlo, in modo che possa essere a carico di attuazione
Amit

35
Citi qualcosa di molto importante: il caricatore di moduli. Mentre ES6 fornisce la sintassi di importazione ed esportazione, non definisce come caricare i moduli. La parte importante è che le dichiarazioni sono staticamente analizzabili, in modo che le dipendenze possano essere determinate senza eseguire il codice. Ciò consentirebbe a un caricatore di moduli di caricare un modulo in modo sincrono o asincrono. Ma i moduli ES6 da soli non sono sincroni o asincroni.
Felix Kling, l'

5
@FelixKling Il caricatore del modulo ES6 è stato taggato nell'OP, quindi suppongo che lo renda rilevante per la risposta. Ho anche affermato che sulla base di osservazioni asincrone è il comportamento attuale, nonché la possibilità in futuro (in qualsiasi implementazione), quindi è un punto rilevante da considerare. Pensi che sia sbagliato?
Entro l'

10
Penso che sia importante non confondere il sistema / la sintassi del modulo con il caricatore del modulo. Ad esempio, se sviluppi per il nodo, probabilmente stai comunque compilando i moduli ES6 require, quindi stai comunque utilizzando il sistema di moduli e il caricatore di Node.
Felix Kling, l'

41

I principali vantaggi sono sintattici:

  • Sintassi più dichiarativa / compatta
  • I moduli ES6 renderanno sostanzialmente obsoleta la UMD (Universal Module Definition), rimuovendo sostanzialmente lo scisma tra CommonJS e AMD (server vs browser).

È improbabile che tu possa vedere alcun vantaggio in termini di prestazioni con i moduli ES6. Avrai comunque bisogno di una libreria aggiuntiva per raggruppare i moduli, anche quando c'è il pieno supporto per le funzionalità ES6 nel browser.


4
Potresti chiarire perché uno ha bisogno di un bundler anche quando i browser hanno il pieno supporto del modulo ES6?
E. Sundin,

1
Ci scusiamo, modificato per avere più senso. Intendevo dire che la funzione dei moduli di importazione / esportazione non è implementata nativamente in nessun browser. È ancora necessario un transpiler.
snozza,

16
Mi sembra un po 'contraddittorio. Se esiste un supporto completo, qual è lo scopo del bundler? C'è qualcosa che manca nelle specifiche ES6? Cosa farebbe effettivamente il bundler che non è disponibile in un ambiente completamente supportato ?
E. Sundin,

1
Come ha detto @snozza ... "la funzione dei moduli di importazione / esportazione non è implementata in modo ingenuo in nessun browser. È ancora necessario un transpiler"
robertmain,

2
Non hai più bisogno di librerie extra. Dalla v8.5.0 (rilasciata più di un anno fa), node --experimemntal-modules index.mjsti consente di utilizzare importsenza Babel. Puoi (e dovresti) anche pubblicare i tuoi pacchetti npm come ESModule nativo, con retrocompatibilità alla vecchia requiremaniera. Molti browser supportano anche le importazioni dinamiche in modo nativo.
Dan Dascalescu il

38

Ci sono vantaggi prestazionali nell'usare l'uno rispetto all'altro?

La risposta attuale è no, perché nessuno dei motori di browser attuali implementa import/exportdallo standard ES6.

Alcuni grafici di confronto http://kangax.github.io/compat-table/es6/ non ne tengono conto, quindi quando vedi quasi tutti i green per Chrome, fai solo attenzione. importla parola chiave di ES6 non è stata presa in considerazione.

In altre parole, i motori di browser attuali, incluso V8, non possono importare un nuovo file JavaScript dal file JavaScript principale tramite alcuna direttiva JavaScript.

(Potremmo essere ancora a pochi bug di distanza o anni di distanza fino a quando V8 lo implementerà secondo le specifiche ES6.)

Questo documento è ciò di cui abbiamo bisogno e questo documento è ciò a cui dobbiamo obbedire.

E lo standard ES6 ha affermato che le dipendenze del modulo dovrebbero essere lì prima di leggere il modulo come nel linguaggio di programmazione C, dove avevamo .hfile (header) .

Questa è una struttura buona e ben collaudata, e sono sicuro che gli esperti che hanno creato lo standard ES6 lo avevano in mente.

Questo è ciò che consente a Webpack o ad altri bundler di pacchetti di ottimizzare il pacchetto in alcuni casi speciali e ridurre alcune dipendenze dal pacchetto che non sono necessarie. Ma nei casi in cui abbiamo dipendenze perfette questo non accadrà mai.

Ci vorrà del tempo prima che il import/exportsupporto nativo diventi attivo e la requireparola chiave non andrà da nessuna parte per molto tempo.

Che cosa è require?

Questo è il node.jsmodo di caricare i moduli. ( https://github.com/nodejs/node )

Il nodo utilizza metodi a livello di sistema per leggere i file. Fondamentalmente fai affidamento su questo durante l'utilizzo require. requireterminerà con alcune chiamate di sistema come uv_fs_open(dipende dal sistema finale, Linux, Mac, Windows) per caricare il file / modulo JavaScript.

Per verificare che ciò sia vero, prova a utilizzare Babel.js e vedrai che la importparola chiave verrà convertita in require.

inserisci qui la descrizione dell'immagine


2
In realtà, c'è un settore in cui le prestazioni potrebbe essere migliorata - Dimensioni fascio. L'uso importin un processo di compilazione Webpack 2 / Rollup può potenzialmente ridurre la dimensione del file risultante mediante moduli / codice inutilizzati che "scuotono l'albero", che potrebbero altrimenti finire nel pacchetto finale. File di dimensioni inferiori = più veloce da scaricare = più veloce da avviare / eseguire sul client.
Lee Benson,

2
il ragionamento era che nessun browser attuale sul pianeta terra consente la import parola chiave in modo nativo. O questo significa che non è possibile importare un altro file JavaScript da un file JavaScript. Questo è il motivo per cui non è possibile confrontare i vantaggi in termini di prestazioni di questi due. Ma ovviamente strumenti come Webpack1 / 2 o Browserify possono gestire la compressione. Sono testa a testa: gist.github.com/substack/68f8d502be42d5cd4942
prosti

4
Stai trascurando il 'tremito degli alberi'. In nessun punto del tuo link gist si discute di tremore. L'uso dei moduli ES6 lo abilita, perché importe exportsono dichiarazioni statiche che importano un percorso di codice specifico, mentre requirepossono essere dinamici e quindi raggrupparsi in codice che non viene utilizzato. Il vantaggio in termini di prestazioni è indiretto: Webpack 2 e / o Rollup possono potenzialmente comportare dimensioni di bundle più piccole che sono più veloci da scaricare e quindi appaiono più scattanti per l'utente finale (di un browser). Funziona solo se tutto il codice è scritto nei moduli ES6 e quindi le importazioni possono essere analizzate staticamente.
Lee Benson,

2
Ho aggiornato la risposta @LeeBenson, penso che se consideriamo il supporto nativo dai motori di browser che non possiamo ancora confrontare. Quella che viene fornita come utile tre opzioni di agitazione usando il Webpack, può essere raggiunta anche prima di impostare i moduli CommonJS, poiché per la maggior parte delle applicazioni reali sappiamo quali moduli dovrebbero essere utilizzati.
prosti

1
La tua risposta è totalmente valida, ma penso che stiamo confrontando due diverse caratteristiche. Tutto import/export viene convertito in require, concesso. Ma ciò che accade prima di questo passaggio potrebbe essere considerato un miglioramento delle prestazioni. Esempio: se lodashè scritto in ES6 e tu import { omit } from lodash, il pacchetto finale conterrà SOLO 'ometti' e non le altre utilità, mentre un semplice require('lodash')importerà tutto. Ciò consentirà di aumentare le dimensioni del pacchetto, impiegare più tempo a scaricare e quindi ridurre le prestazioni. Questo è valido solo nel contesto di un browser, ovviamente.
Lee Benson,

31

L'uso dei moduli ES6 può essere utile per "agitare l'albero"; cioè abilitando Webpack 2, Rollup (o altri bundler) per identificare percorsi di codice che non sono usati / importati, e quindi non farlo nel bundle risultante. Ciò può ridurre in modo significativo la dimensione del file eliminando il codice che non ti servirà mai, ma con CommonJS è raggruppato per impostazione predefinita perché Webpack e altri non hanno modo di sapere se è necessario.

Questo viene fatto usando l'analisi statica del percorso del codice.

Ad esempio, usando:

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

... dà al bundler un suggerimento che package.anotherPartnon è necessario (se non è importato, non può essere usato, giusto?), quindi non si preoccuperà di raggrupparlo.

Per abilitare questo per Webpack 2, è necessario assicurarsi che il proprio transpiler non sputi i moduli CommonJS. Se stai usando il es2015plug-in con babel, puoi disabilitarlo a tuo .babelrcpiacimento così:

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

Rollup e altri potrebbero funzionare in modo diverso: visualizza i documenti se sei interessato.


2
ottimo anche per lo scuotimento degli alberi 2ality.com/2015/12/webpack-tree-shaking.html
prosti

25

Quando si tratta di caricamento asincrono o forse pigro, allora import ()è molto più potente. Vedi quando richiediamo il componente in modo asincrono, quindi lo usiamo importin qualche modo asincrono come in constvariabili usando await.

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

O se vuoi usare require()allora,

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

La cosa è in import()realtà asincrona in natura. Come menzionato da Neehar Venugopal in ReactConf , è possibile utilizzarlo per caricare dinamicamente i componenti di reazione per l'architettura lato client.

Inoltre è molto meglio quando si tratta di routing. Questa è l'unica cosa speciale che rende il registro di rete per scaricare una parte necessaria quando l'utente si collega a un sito Web specifico al suo componente specifico. ad esempio, la pagina di accesso prima della dashboard non scarica tutti i componenti della dashboard. Perché ciò che è necessario, ad esempio il componente di accesso, verrà scaricato solo.

Lo stesso vale per export: ES6 exportsono esattamente gli stessi di CommonJS module.exports.

NOTA : se si sta sviluppando un progetto node.js, è necessario utilizzare rigorosamente require()poiché il nodo genererà un errore di eccezione come invalid token 'import'se si utilizzasse import. Quindi il nodo non supporta le dichiarazioni di importazione.

AGGIORNAMENTO - Come suggerito da Dan Dascalescu : Dalla v8.5.0 (rilasciata a settembre 2017), node --experimental-modules index.mjsti consente di utilizzare importsenza Babele. Puoi (e dovresti) anche pubblicare i tuoi pacchetti npm come ESModule nativo, con retrocompatibilità alla vecchia requiremaniera.

Vedi questo per ulteriori informazioni su dove utilizzare le importazioni asincrone - https://www.youtube.com/watch?v=bb6RCrDaxhw


1
Quindi sarà necessario sincronizzare e attendere?
Baklazan,

1
Posso dire di fatto!
Incontra Zaveri il

15

La cosa più importante da sapere è che i moduli ES6 sono, in effetti, uno standard ufficiale, mentre i moduli CommonJS (Node.js) non lo sono.

Nel 2019, i moduli ES6 sono supportati dall'84% dei browser. Mentre Node.js li mette dietro un flag --experimental-modules , esiste anche un comodo pacchetto di nodi chiamato esm , che semplifica l'integrazione.

Un altro problema che potresti incontrare tra questi sistemi di moduli è la posizione del codice. Node.js presuppone che l'origine sia mantenuta in una node_modulesdirectory, mentre la maggior parte dei moduli ES6 sono distribuiti in una struttura di directory piatta. Questi non sono facili da riconciliare, ma possono essere fatti hackerando il tuo package.jsonfile con script pre e post installazione. Ecco un esempio di modulo isomorfo e un articolo che spiega come funziona.


8

Personalmente uso import perché, possiamo importare i metodi richiesti, i membri usando import.

import {foo, bar} from "dep";

Nome file: dep.js

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

Il merito va a Paul Shan. Maggiori informazioni .



6
puoi fare lo stesso con request!
Suisse,

4
const {a,b} = require('module.js'); funziona anche ... se esporti aeb
BananaAcid

module.exports = { a: ()={}, b: 22 }- La seconda parte di @BananaAcid risponde
Seth McClaine il

7

A partire dal momento dell'importazione ES6, l'esportazione viene sempre compilata in CommonJS , quindi non vi è alcun vantaggio nell'utilizzare l'uno o l'altro. Sebbene l'uso di ES6 sia raccomandato poiché dovrebbe essere vantaggioso quando viene rilasciato il supporto nativo dai browser. Il motivo è che puoi importare i parziali da un file mentre con CommonJS devi richiedere tutto il file.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Di seguito è un uso comune di quelli.

Esportazione predefinita ES6

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

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

ES6 esporta multipli e importa multipli

// 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

0

Non so perché (probabilmente ottimizzazione - caricamento lento?) Funziona così, ma ho notato che importpotrebbe non analizzare il codice se i moduli importati non vengono utilizzati.
Quale potrebbe non essere previsto comportamento in alcuni casi.

Prendi l'odiata classe Foo come nostra dipendenza campione.

foo.ts

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

Per esempio:

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"
})();

D'altro canto:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.