Für die Verwendung von Node.js ist ein Import / Export von ES6 erforderlich


930

In einem Projekt, an dem ich zusammenarbeite, haben wir zwei Möglichkeiten, welches Modulsystem wir verwenden können:

  1. Importieren von Modulen mit requireund Exportieren mit module.exportsund exports.foo.
  2. Importieren von Modulen mit ES6 importund Exportieren mit ES6export

Gibt es Leistungsvorteile bei der Verwendung übereinander? Gibt es noch etwas, das wir wissen sollten, wenn wir ES6-Module über Node-Module verwenden würden?


9
node --experimental-modules index.mjsimportErmöglicht die Verwendung ohne Babel und funktioniert in Node 8.5.0+. Sie können (und sollten) Ihre npm-Pakete auch als natives ESModule veröffentlichen , mit Abwärtskompatibilität für den alten requireWeg.
Dan Dascalescu

Antworten:


728

Gibt es Leistungsvorteile bei der Verwendung übereinander?

Beachten Sie, dass es noch keine JavaScript-Engine gibt, die ES6-Module nativ unterstützt. Du hast selbst gesagt, dass du Babel benutzt. Babel konvertiert importund exportdeklariert ohnehin standardmäßig in CommonJS ( require/ module.exports). Selbst wenn Sie die ES6-Modulsyntax verwenden, verwenden Sie CommonJS unter der Haube, wenn Sie den Code in Node ausführen.

Es gibt technische Unterschiede zwischen CommonJS- und ES6-Modulen, z. B. können Sie mit CommonJS Module dynamisch laden. ES6 erlaubt dies nicht, aber dafür befindet sich eine API in der Entwicklung .

Da ES6-Module Teil des Standards sind, würde ich sie verwenden.


16
Ich habe versucht, ES6 importmit zu verwenden, requireaber sie funktionierten anders. CommonJS exportiert die Klasse selbst, während es nur eine Klasse gibt. ES6-Exporte, da es mehrere Klassen gibt, müssen Sie verwenden .ClassName, um die exportierte Klasse abzurufen. Gibt es andere Unterschiede, die sich tatsächlich auf die Implementierung
auswirken

78
@Entei: Scheint, als ob Sie einen Standardexport wollen, keinen benannten Export. module.exports = ...;ist äquivalent zu export default .... exports.foo = ...ist gleichbedeutend mit export var foo = ...;
Felix Kling

10
Es ist erwähnenswert, dass Babel, obwohl es letztendlich importin CommonJS in Node transpiliert wird, das zusammen mit Webpack 2 / Rollup (und jedem anderen Bundler, der das Schütteln von ES6-Bäumen ermöglicht) verwendet wird, eine Datei erstellen kann, die erheblich kleiner ist als die entsprechenden Code-Node-Crunches durch die Verwendung requiregenau aufgrund der Tatsache, dass ES6 eine statische Analyse von Import / Export ermöglicht. Während dies für Node (noch) keinen Unterschied macht, kann dies sicherlich der Fall sein, wenn der Code letztendlich als einzelnes Browser-Bundle angezeigt wird.
Lee Benson

5
es sei denn, Sie müssen einen dynamischen Import durchführen
chulian

3
ES6-Module sind in der neuesten Version V8 und kommen auch in anderen Browsern hinter Flags an. Siehe: medium.com/dev-channel/…
Nexii Malthus

180

Es gibt verschiedene Verwendungsmöglichkeiten / Funktionen, die Sie in Betracht ziehen sollten:

Benötigen:

  • Sie können dynamisch laden, wenn der Name des geladenen Moduls nicht vordefiniert / statisch ist oder wenn Sie ein Modul nur dann bedingt laden, wenn es "wirklich erforderlich" ist (abhängig von einem bestimmten Codefluss).
  • Das Laden ist synchron. Das heißt, wenn Sie mehrere requires haben, werden diese einzeln geladen und verarbeitet.

ES6-Importe:

  • Sie können benannte Importe verwenden, um selektiv nur die Teile zu laden, die Sie benötigen. Das kann Speicherplatz sparen.
  • Der Import kann asynchron sein (und im aktuellen ES6 Module Loader tatsächlich) und eine etwas bessere Leistung erzielen.

Außerdem ist das Require-Modulsystem nicht standardbasiert. Es ist sehr unwahrscheinlich, dass es zum Standard wird, wenn ES6-Module existieren. In Zukunft wird es native Unterstützung für ES6-Module in verschiedenen Implementierungen geben, was hinsichtlich der Leistung vorteilhaft sein wird.


16
Was lässt Sie denken, dass ES6-Importe asynchron sind?
Felix Kling

5
@FelixKling - Kombination verschiedener Beobachtungen. Bei Verwendung von JSPM (ES6 Module Loader ...) ist mir aufgefallen, dass beim Ändern des globalen Namespace bei einem Import der Effekt bei anderen Importen nicht beobachtet wird (da diese asynchron auftreten. Dies kann auch im transpilierten Code gesehen werden). Da dies das Verhalten ist (1 Import wirkt sich nicht auf andere aus), gibt es keinen Grund, dies nicht zu tun, sodass es möglicherweise von der Implementierung abhängt
Amit

35
Sie erwähnen etwas sehr Wichtiges: den Modullader. Während ES6 die Import- und Exportsyntax bereitstellt, definiert es nicht, wie Module geladen werden sollen. Der wichtige Teil ist, dass die Deklarationen statisch analysierbar sind, so dass Abhängigkeiten bestimmt werden können, ohne den Code auszuführen. Dies würde es einem Modullader ermöglichen, ein Modul entweder synchron oder asynchron zu laden. ES6-Module selbst sind jedoch nicht synchron oder asynchron.
Felix Kling

5
Der @ FelixKling ES6-Modullader wurde im OP markiert, daher gehe ich davon aus, dass er für die Antwort relevant ist. Außerdem habe ich festgestellt, dass Asynchronität auf der Grundlage von Beobachtungen aktuelles Verhalten sowie die Möglichkeit in der Zukunft (in jeder Implementierung) ist, sodass dies ein relevanter zu berücksichtigender Punkt ist. Glaubst du, es ist falsch?
Amit

10
Ich denke, es ist wichtig, das Modulsystem / die Syntax nicht mit dem Modullader zu verschmelzen. Wenn Sie beispielsweise für Node entwickeln, kompilieren Sie wahrscheinlich requiretrotzdem ES6-Module , sodass Sie das Modulsystem und den Loader von Node trotzdem verwenden.
Felix Kling

41

Die Hauptvorteile sind syntaktisch:

  • Deklarativere / kompaktere Syntax
  • ES6-Module machen UMD (Universal Module Definition) grundsätzlich überflüssig - entfernen im Wesentlichen das Schisma zwischen CommonJS und AMD (Server vs Browser).

Es ist unwahrscheinlich, dass Sie mit ES6-Modulen Leistungsvorteile erzielen. Sie benötigen weiterhin eine zusätzliche Bibliothek, um die Module zu bündeln, auch wenn die ES6-Funktionen im Browser vollständig unterstützt werden.


4
Können Sie klarstellen, warum man einen Bundler benötigt, auch wenn Browser die volle Unterstützung für ES6-Module haben?
E. Sundin

1
Entschuldigung, bearbeitet, um mehr Sinn zu machen. Ich meinte, dass die Import / Export-Modul-Funktion in keinem Browser nativ implementiert ist. Ein Transpiler ist noch erforderlich.
Snozza

16
Es scheint mir ein bisschen widersprüchlich. Wenn es volle Unterstützung gibt, was ist dann der Zweck des Bundlers? Fehlt etwas in der ES6-Spezifikation? Was würde der Bundler tatsächlich tun, das in einer vollständig unterstützten Umgebung nicht verfügbar ist ?
E. Sundin

1
Wie @snozza sagte ... "Die Import / Export-Modul-Funktion ist in keinem Browser naiv implementiert. Ein Transpiler ist noch erforderlich"
Robertmain

2
Sie benötigen keine zusätzlichen Bibliotheken mehr. Seit Version 8.5.0 (veröffentlicht vor mehr als einem Jahr) node --experimemntal-modules index.mjskönnen Sie importohne Babel verwenden. Sie können (und sollten) Ihre npm-Pakete auch als natives ESModule veröffentlichen, mit Abwärtskompatibilität für den alten requireWeg. Viele Browser unterstützen dynamische Importe auch nativ.
Dan Dascalescu

38

Gibt es Leistungsvorteile bei der Verwendung übereinander?

Die aktuelle Antwort lautet "Nein", da keine der aktuellen Browser-Engines import/exportden ES6-Standard implementiert .

Einige Vergleichstabellen http://kangax.github.io/compat-table/es6/ berücksichtigen dies nicht. Wenn Sie also fast alle Grüns für Chrome sehen, seien Sie vorsichtig. importSchlüsselwort von ES6 wurde nicht berücksichtigt.

Mit anderen Worten, aktuelle Browser-Engines einschließlich V8 können keine neue JavaScript-Datei aus der JavaScript-Hauptdatei über eine JavaScript-Direktive importieren .

(Möglicherweise sind wir nur noch wenige Fehler oder Jahre entfernt, bis V8 dies gemäß der ES6-Spezifikation implementiert.)

Dieses Dokument ist das, was wir brauchen, und dieses Dokument ist das, was wir befolgen müssen.

Und der ES6-Standard sagte, dass die Modulabhängigkeiten vorhanden sein sollten, bevor wir das Modul lesen, wie in der Programmiersprache C, in der wir (Header-) .hDateien hatten.

Dies ist eine gute und gut getestete Struktur, und ich bin sicher, dass die Experten, die den ES6-Standard erstellt haben, dies berücksichtigt haben.

Dies ist , was Webpack oder anderes Paket Bündler ermöglicht das Bündel in einigen zu optimieren Sonderfällen und reduzieren einige Abhängigkeiten von dem Bündel , die nicht benötigt werden. Aber in Fällen, in denen wir perfekte Abhängigkeiten haben, wird dies niemals passieren.

Es wird einige Zeit import/exportdauern, bis der native Support online geht, und das requireKeyword wird lange Zeit nirgendwo hingehen.

Was ist require?

Auf diese node.jsWeise können Module geladen werden. ( https://github.com/nodejs/node )

Der Knoten verwendet Methoden auf Systemebene, um Dateien zu lesen. Darauf verlassen Sie sich bei der Verwendung grundsätzlich require. requireendet in einem Systemaufruf wie uv_fs_open(abhängig vom Endsystem, Linux, Mac, Windows) zum Laden der JavaScript-Datei / des JavaScript-Moduls.

Um zu überprüfen, ob dies der Fall ist, versuchen Sie, Babel.js zu verwenden, und Sie werden sehen, dass das importSchlüsselwort in konvertiert wird require.

Geben Sie hier die Bildbeschreibung ein


2
Eigentlich gibt es einen Bereich , wo die Leistung könnte verbessert werden - Bündelgröße. Die Verwendung importin einem Webpack 2 / Rollup-Erstellungsprozess kann möglicherweise die resultierende Dateigröße reduzieren, indem nicht verwendete Module / Codes durch Baumschütteln ersetzt werden, die andernfalls im endgültigen Bundle landen könnten. Kleinere Dateigröße = schneller zum Herunterladen = schneller zum Initiieren / Ausführen auf dem Client.
Lee Benson

2
Die Argumentation war, dass kein aktueller Browser auf dem Planeten Erde das import Schlüsselwort nativ zulässt . Dies bedeutet auch, dass Sie keine andere JavaScript-Datei aus einer JavaScript-Datei importieren können. Aus diesem Grund können Sie die Leistungsvorteile dieser beiden nicht vergleichen. Aber natürlich können Tools wie Webpack1 / 2 oder Browserify mit Komprimierung umgehen. Sie sind Hals an Hals: gist.github.com/substack/68f8d502be42d5cd4942
Prosti

4
Sie übersehen "Baumschütteln". Nirgendwo in Ihrem Hauptlink wird über Baumschütteln gesprochen. Mit ES6 Module , die es ermöglicht, da importund exportstatische Erklärungen sind , die einen bestimmten Codepfad importieren, während requirekann dynamisch sein und somit in Code bündeln , die nicht verwendet wird . Der Leistungsvorteil ist indirekt: Webpack 2 und / oder Rollup können möglicherweise zu kleineren Bundle-Größen führen, die schneller heruntergeladen werden können und daher für den Endbenutzer (eines Browsers) schneller erscheinen. Dies funktioniert nur, wenn der gesamte Code in ES6-Modulen geschrieben ist und daher Importe statisch analysiert werden können.
Lee Benson

2
Ich habe die Antwort @LeeBenson aktualisiert. Ich denke, wenn wir die native Unterstützung von Browser-Engines berücksichtigen, können wir sie noch nicht vergleichen. Was mit dem Webpack als praktische Option für drei Schütteln nützlich ist, kann auch erreicht werden, bevor wir die CommonJS-Module einstellen, da wir für die meisten realen Anwendungen wissen, welche Module verwendet werden sollten.
Prosti

1
Ihre Antwort ist völlig gültig, aber ich denke, wir vergleichen zwei verschiedene Merkmale. Alles import/export wird konvertiert require, gewährt. Was jedoch vor diesem Schritt passiert, kann als "Leistungssteigerung" angesehen werden. Beispiel: Wenn lodashes in ES6 geschrieben ist und Sie import { omit } from lodash, enthält das ultimative Bundle NUR "Auslassen" und nicht die anderen Dienstprogramme, während ein einfaches require('lodash')alles importiert. Dadurch wird die Bundle-Größe erhöht, der Download dauert länger und die Leistung wird verringert. Dies gilt natürlich nur im Browserkontext.
Lee Benson

31

Die Verwendung von ES6-Modulen kann nützlich sein, um Bäume zu schütteln. Das heißt, Webpack 2, Rollup (oder andere Bundler) können Codepfade identifizieren, die nicht verwendet / importiert werden, und es daher nicht in das resultierende Bundle schaffen. Dies kann die Dateigröße erheblich reduzieren, indem Code eliminiert wird, den Sie nie benötigen. CommonJS wird jedoch standardmäßig gebündelt, da Webpack et al. Nicht wissen können, ob es benötigt wird.

Dies erfolgt durch statische Analyse des Codepfads.

Zum Beispiel mit:

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

... gibt dem Bundler einen Hinweis, der package.anotherPartnicht erforderlich ist (wenn er nicht importiert wird, kann er nicht verwendet werden - oder?), damit er ihn nicht bündelt.

Um dies für Webpack 2 zu aktivieren, müssen Sie sicherstellen, dass Ihr Transpiler keine CommonJS-Module ausspuckt. Wenn Sie das es2015Plug-In mit babel verwenden, können Sie es .babelrcwie folgt deaktivieren :

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

Rollup und andere funktionieren möglicherweise anders - sehen Sie sich die Dokumente an, wenn Sie interessiert sind.


2
auch ideal
prosti

25

Wenn es um asynchrones oder vielleicht faules Laden geht, import ()ist es viel leistungsfähiger. Sehen Sie, wenn wir die Komponente asynchron benötigen, dann verwenden wir importsie auf asynchrone Weise wie bei der constVerwendung von Variablen await.

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

Oder wenn Sie dann verwenden möchten require(),

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

Die Sache ist import()tatsächlich asynchron in der Natur. Wie von neehar venugopal in ReactConf erwähnt , können Sie damit reaktionsfähige Komponenten für die clientseitige Architektur dynamisch laden.

Auch beim Routing ist es viel besser. Dies ist die einzige Besonderheit, die das Netzwerkprotokoll zum Herunterladen eines erforderlichen Teils macht, wenn der Benutzer eine Verbindung zu einer bestimmten Website zu seiner bestimmten Komponente herstellt. Beispiel: Die Anmeldeseite vor dem Dashboard würde nicht alle Komponenten des Dashboards herunterladen. Da was aktuell benötigt wird, dh Anmeldekomponente, wird das nur heruntergeladen.

Gleiches gilt für export: ES6 exportist genau das gleiche wie für CommonJS module.exports.

HINWEIS - Wenn Sie ein node.js-Projekt entwickeln, müssen Sie es unbedingt verwenden, require()da der Knoten einen Ausnahmefehler auslöst, als invalid token 'import'ob Sie ihn verwenden würden import. Daher unterstützt der Knoten keine Importanweisungen.

UPDATE - Wie von Dan Dascalescu vorgeschlagen : Seit Version 8.5.0 (veröffentlicht im September 2017) node --experimental-modules index.mjskönnen Sie importohne Babel verwenden. Sie können (und sollten) Ihre npm-Pakete auch als natives ESModule veröffentlichen, mit Abwärtskompatibilität für den alten requireWeg.

Weitere Informationen zur Verwendung von asynchronen Importen finden Sie hier - https://www.youtube.com/watch?v=bb6RCrDaxhw


1
Wird die Anforderung also synchronisiert und gewartet?
Baklazan

1
Kann sachlich sagen!
Treffen Sie Zaveri

15

Das Wichtigste ist, dass ES6-Module tatsächlich ein offizieller Standard sind, CommonJS-Module (Node.js) jedoch nicht.

Im Jahr 2019 werden ES6-Module von 84% der Browser unterstützt. Während Node.js sie hinter das Flag --experimental-modules setzt , gibt es auch ein praktisches Knotenpaket namens esm , das die Integration reibungslos macht.

Ein weiteres Problem, auf das Sie wahrscheinlich zwischen diesen Modulsystemen stoßen, ist die Code-Position. Node.js geht davon aus, dass die Quelle in einem node_modulesVerzeichnis gespeichert ist, während die meisten ES6-Module in einer flachen Verzeichnisstruktur bereitgestellt werden. Diese sind nicht einfach zu vereinbaren, können jedoch durch Hacken Ihrer package.jsonDatei mit Skripten vor und nach der Installation durchgeführt werden. Hier ist ein Beispiel für ein isomorphes Modul und ein Artikel, der erklärt, wie es funktioniert.


8

Ich persönlich verwende Import, weil wir die erforderlichen Methoden importieren können, Mitglieder, indem wir Import verwenden.

import {foo, bar} from "dep";

Dateiname: dep.js.

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

Der Kredit geht an Paul Shan. Weitere Infos .


1
Schöne Wahl! Sind Sie auch die Veröffentlichung Ihrer npm Pakete als native ESModule, mit Abwärtskompatibilität für die alte requireArt und Weise?
Dan Dascalescu

6
Sie können das gleiche mit erfordern!
Suisse

4
const {a,b} = require('module.js'); funktioniert auch ... wenn Sie exportieren aundb
BananaAcid

module.exports = { a: ()={}, b: 22 }- Der zweite Teil von @BananaAcid antwortet
Seth McClaine

7

Ab dem ES6-Import wird der Export immer nach CommonJS kompiliert , sodass die Verwendung des einen oder anderen keinen Vorteil bringt . Die Verwendung von ES6 wird zwar empfohlen, da dies bei der Veröffentlichung der nativen Unterstützung durch Browser von Vorteil sein sollte. Der Grund dafür ist, dass Sie Partials aus einer Datei importieren können, während Sie mit CommonJS die gesamte Datei benötigen.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Nachfolgend finden Sie eine allgemeine Verwendung dieser.

ES6-Exportstandard

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

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

ES6 exportiert mehrere und importiert mehrere

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

Ich bin mir nicht sicher, warum (wahrscheinlich Optimierung - verzögertes Laden?) So funktioniert, aber ich habe festgestellt, dass importCode möglicherweise nicht analysiert wird, wenn importierte Module nicht verwendet werden.
Was in einigen Fällen nicht zu erwarten ist.

Nehmen Sie die verhasste Foo-Klasse als unsere Beispielabhängigkeit.

foo.ts

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

Zum Beispiel:

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

Auf der anderen Seite:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.