L'utilisation de Node.js nécessite une importation / exportation ES6

930

Dans un projet sur lequel je collabore, nous avons deux choix sur le système de modules que nous pouvons utiliser:

  1. Importation de modules à l'aide requireet exportation à l'aide de module.exportset exports.foo.
  2. Importation de modules à l'aide d'ES6 importet exportation à l'aide d'ES6export

Y a-t-il des avantages en termes de performances à utiliser l'un par rapport à l'autre? Y a-t-il autre chose que nous devrions savoir si nous devions utiliser des modules ES6 sur des modules Node?

kpimov
la source
9
node --experimental-modules index.mjsvous permet d'utiliser importsans Babel et fonctionne dans Node 8.5.0+. Vous pouvez (et devez) également publier vos packages npm en tant que ESModule natif , avec une compatibilité descendante pour l'ancienne requireméthode.
Dan Dascalescu

Réponses:

728

Y a-t-il des avantages en termes de performances à utiliser l'un par rapport à l'autre?

Gardez à l'esprit qu'il n'y a pas encore de moteur JavaScript qui supporte nativement les modules ES6. Vous avez dit vous-même que vous utilisez Babel. Babel convertit importet exportdéclare par défaut CommonJS ( require/ module.exports). Donc, même si vous utilisez la syntaxe du module ES6, vous utiliserez CommonJS sous le capot si vous exécutez le code dans Node.

Il existe des différences techniques entre les modules CommonJS et ES6, par exemple CommonJS vous permet de charger les modules dynamiquement. ES6 ne le permet pas, mais il existe une API en développement pour cela .

Étant donné que les modules ES6 font partie de la norme, je les utiliserais.

Felix Kling
la source
16
J'ai essayé d'utiliser ES6 importavec requiremais ils fonctionnaient différemment. CommonJS exporte la classe elle-même alors qu'il n'y a qu'une seule classe. Les exportations ES6 comme il existe plusieurs classes, vous devez donc utiliser .ClassNamepour obtenir la classe exportée. Y a-t-il d'autres différences qui
affectent
78
@Entei: Il semble que vous souhaitiez une exportation par défaut, pas une exportation nommée. module.exports = ...;est équivalent à export default .... exports.foo = ...est équivalent à export var foo = ...;
Felix Kling
10
Il convient de noter que même si Babel transpile finalement importvers CommonJS dans Node, utilisé avec Webpack 2 / Rollup (et tout autre bundle qui permet le tremblement de l'arborescence ES6), il est possible de se retrouver avec un fichier qui est nettement plus petit que le code équivalent. en utilisant requireexactement parce que ES6 permet une analyse statique des importations / exportations. Bien que cela ne fasse pas de différence pour Node (pour l'instant), c'est certainement le cas si le code finit par devenir un ensemble de navigateur unique.
Lee Benson
5
sauf si vous devez faire une importation dynamique
chulian
3
Les modules ES6 sont dans la dernière version V8 et arrivent également dans d'autres navigateurs derrière des drapeaux. Voir: medium.com/dev-channel/…
Nexii Malthus
180

Il y a plusieurs utilisations / capacités que vous voudrez peut-être considérer:

Exiger:

  • Vous pouvez avoir un chargement dynamique lorsque le nom du module chargé n'est pas prédéfini / statique, ou lorsque vous chargez un module de manière conditionnelle uniquement s'il est "vraiment requis" (en fonction d'un certain flux de code).
  • Le chargement est synchrone. Cela signifie que si vous avez plusieurs requires, ils sont chargés et traités un par un.

Importations ES6:

  • Vous pouvez utiliser des importations nommées pour charger sélectivement uniquement les pièces dont vous avez besoin. Cela peut économiser de la mémoire.
  • L'importation peut être asynchrone (et dans le chargeur de module ES6 actuel, en fait) et peut fonctionner un peu mieux.

De plus, le système de modules requis n'est pas basé sur des normes. Il est hautement improbable de devenir standard maintenant que les modules ES6 existent. À l'avenir, il y aura une prise en charge native des modules ES6 dans diverses implémentations, ce qui sera avantageux en termes de performances.

Amit
la source
16
Qu'est-ce qui vous fait penser que les importations ES6 sont asynchrones?
Felix Kling
5
@FelixKling - combinaison de diverses observations. En utilisant JSPM (ES6 Module Loader ...), j'ai remarqué que lorsqu'une importation modifiait l'espace de nom global, l'effet n'est pas observé dans les autres importations (car elles se produisent de manière asynchrone .. Cela peut également être vu dans le code transpilé). De plus, comme c'est le comportement (1 importation n'affecte pas les autres), il n'y a aucune raison de ne pas le faire, donc cela pourrait dépendre de l'implémentation
Amit
35
Vous mentionnez quelque chose de très important: le chargeur de modules. Bien qu'ES6 fournisse la syntaxe d'importation et d'exportation, il ne définit pas comment les modules doivent être chargés. La partie importante est que les déclarations sont statiquement analysables, de sorte que les dépendances peuvent être déterminées sans exécuter le code. Cela permettrait à un chargeur de module de charger un module de manière synchrone ou asynchrone. Mais les modules ES6 ne sont pas en eux-mêmes synchrones ou asynchrones.
Felix Kling
5
Le chargeur de module @FelixKling ES6 a été marqué dans l'OP, donc je suppose que cela le rend pertinent pour la réponse. J'ai également déclaré que sur la base des observations, l'async est le comportement actuel, ainsi que la possibilité à l'avenir (dans toute mise en œuvre), c'est donc un point pertinent à considérer. Pensez-vous que c'est mal?
Amit
10
Je pense qu'il est important de ne pas confondre le système / syntaxe du module avec le chargeur de module. Par exemple, si vous développez pour le nœud, vous compilerez probablement des modules ES6 de requiretoute façon, vous utilisez donc le système de modules et le chargeur de Node de toute façon.
Felix Kling
41

Les principaux avantages sont syntaxiques:

  • Syntaxe plus déclarative / compacte
  • Les modules ES6 rendront fondamentalement UMD (Universal Module Definition) obsolète - supprime essentiellement le schisme entre CommonJS et AMD (serveur vs navigateur).

Il est peu probable que vous constatiez des avantages en termes de performances avec les modules ES6. Vous aurez toujours besoin d'une bibliothèque supplémentaire pour regrouper les modules, même s'il existe une prise en charge complète des fonctionnalités ES6 dans le navigateur.

snozza
la source
4
Pourriez-vous expliquer pourquoi on a besoin d'un bundler même lorsque les navigateurs prennent en charge le module ES6 complet?
E. Sundin
1
Toutes mes excuses, modifiées pour donner plus de sens. Je voulais dire que la fonctionnalité d'import / export des modules n'est pas implémentée nativement dans les navigateurs. Un transpilateur est toujours requis.
snozza
16
Cela me semble un peu contradictoire. S'il y a un support complet, quel est le but du bundler? Y a-t-il quelque chose qui manque dans la spécification ES6? Que ferait le bundler qui n'est pas disponible dans un environnement entièrement pris en charge ?
E. Sundin
1
Comme l'a dit @snozza ... "la fonctionnalité d'import / export des modules n'est implémentée dans aucun navigateur naïvement. Un transpilateur est toujours requis"
robertmain
2
Vous n'avez plus besoin de bibliothèques supplémentaires. Depuis la v8.5.0 (sortie il y a plus d'un an), node --experimemntal-modules index.mjspermet de l'utiliser importsans Babel. Vous pouvez (et devez) également publier vos packages npm en tant que ESModule natif, avec une compatibilité descendante pour l'ancienne requireméthode. De nombreux navigateurs prennent également en charge les importations dynamiques en natif.
Dan Dascalescu
38

Y a-t-il des avantages en termes de performances à utiliser l'un par rapport à l'autre?

La réponse actuelle est non, car aucun des moteurs de navigateur actuels n'est implémenté à import/exportpartir de la norme ES6.

Certains tableaux de comparaison http://kangax.github.io/compat-table/es6/ ne prennent pas cela en compte, donc lorsque vous voyez presque tous les verts pour Chrome, faites juste attention. importmot-clé de ES6 n'a pas été pris en compte.

En d'autres termes, les moteurs de navigateur actuels, y compris V8, ne peuvent pas importer de nouveau fichier JavaScript à partir du fichier JavaScript principal via une directive JavaScript.

(Il se peut que nous ne soyons encore qu'à quelques bugs ou dans des années jusqu'à ce que V8 implémente cela selon la spécification ES6.)

Ce document est ce dont nous avons besoin, et ce document est ce que nous devons obéir.

Et la norme ES6 a dit que les dépendances du module devraient être là avant de lire le module comme dans le langage de programmation C, où nous avions des .hfichiers (en-têtes) .

Il s'agit d'une structure bonne et bien testée, et je suis sûr que les experts qui ont créé la norme ES6 avaient cela en tête.

C'est ce qui permet à Webpack ou à d'autres groupeurs de packages d'optimiser le bundle dans certains cas spéciaux et de réduire certaines dépendances du bundle qui ne sont pas nécessaires. Mais dans les cas où nous avons des dépendances parfaites, cela ne se produira jamais.

Il faudra un certain temps jusqu'à ce que import/exportle support natif soit mis en ligne, et le requiremot - clé n'ira nulle part longtemps.

Qu'est-ce que c'est require?

C'est une node.jsfaçon de charger des modules. ( https://github.com/nodejs/node )

Le nœud utilise des méthodes au niveau du système pour lire les fichiers. Vous comptez essentiellement sur cela lors de l'utilisation require. requirese terminera par un appel système comme uv_fs_open(dépend du système final, Linux, Mac, Windows) pour charger le fichier / module JavaScript.

Pour vérifier que cela est vrai, essayez d'utiliser Babel.js, et vous verrez que le importmot - clé sera converti en require.

entrez la description de l'image ici

prosti
la source
2
En fait, il y a un domaine où les performances pourraient être améliorées - la taille du bundle. L'utilisation importdans un processus de construction Webpack 2 / Rollup peut potentiellement réduire la taille du fichier résultant en «secouant l'arborescence» des modules / codes inutilisés, qui pourraient sinon se retrouver dans le bundle final. Taille de fichier plus petite = téléchargement plus rapide = lancement / exécution plus rapide sur le client.
Lee Benson
2
le raisonnement était pas de navigateur actuel sur la planète terre permet le import mot - clé nativement. Ou cela signifie que vous ne pouvez pas importer un autre fichier JavaScript à partir d'un fichier JavaScript. C'est pourquoi vous ne pouvez pas comparer les avantages de performances de ces deux. Mais bien sûr, des outils comme Webpack1 / 2 ou Browserify peuvent gérer la compression. Ils sont au coude à coude: gist.github.com/substack/68f8d502be42d5cd4942
prosti
4
Vous négligez les «tremblements d'arbres». Nulle part dans votre lien essentiel, la secousse d'arbre n'est discutée. L' utilisation des modules ES6 permet, car importet exportsont des déclarations statiques qui importent un chemin de code spécifique, alors que requirepeut être dynamique et bundle ainsi dans le code qui est pas utilisé. L'avantage en termes de performances est indirect: Webpack 2 et / ou le correctif cumulatif peuvent potentiellement entraîner des tailles de bundle plus petites, plus rapides à télécharger et par conséquent plus intuitives pour l'utilisateur final (d'un navigateur). Cela ne fonctionne que si tout le code est écrit dans les modules ES6 et que les importations peuvent donc être analysées statiquement.
Lee Benson
2
J'ai mis à jour la réponse @LeeBenson, je pense que si nous considérons le support natif des moteurs de navigateur, nous ne pouvons pas encore comparer. Ce qui vient comme une option de secouage pratique en utilisant le Webpack, peut également être réalisé avant de définir les modules CommonJS, car pour la plupart des applications réelles, nous savons quels modules doivent être utilisés.
prosti
1
Votre réponse est totalement valable, mais je pense que nous comparons deux caractéristiques différentes. Tout import/export est converti en require, accordé. Mais ce qui se passe avant cette étape pourrait être considéré comme une amélioration des «performances». Exemple: si lodashest écrit en ES6 et vous import { omit } from lodash, le pack ultime contiendra UNIQUEMENT 'omis' et pas les autres utilitaires, alors qu'un simple require('lodash')importera tout. Cela augmentera la taille du bundle, prendra plus de temps à télécharger et diminuera donc les performances. Bien sûr, cela n'est valable que dans un contexte de navigateur.
Lee Benson
32

L'utilisation de modules ES6 peut être utile pour «secouer l'arbre»; c'est-à-dire permettre à Webpack 2, Rollup (ou à d'autres bundlers) d'identifier les chemins de code qui ne sont pas utilisés / importés, et par conséquent ne pas en faire le bundle résultant. Cela peut réduire considérablement la taille de son fichier en éliminant le code dont vous n'aurez jamais besoin, mais avec CommonJS est fourni par défaut car Webpack et al n'ont aucun moyen de savoir s'il est nécessaire.

Cela se fait à l'aide d'une analyse statique du chemin du code.

Par exemple, en utilisant:

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

... donne au bundler un indice qui package.anotherPartn'est pas requis (s'il n'est pas importé, il ne peut pas être utilisé, n'est-ce pas?), donc cela ne dérangera pas de le regrouper.

Pour l'activer pour Webpack 2, vous devez vous assurer que votre transpilateur ne crache pas de modules CommonJS. Si vous utilisez le es2015plug-in avec babel, vous pouvez le désactiver .babelrccomme vous le souhaitez:

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

Le cumul et d'autres peuvent fonctionner différemment - consultez les documents si vous êtes intéressé.

Lee Benson
la source
2
également idéal pour secouer les arbres 2ality.com/2015/12/webpack-tree-shaking.html
prosti
25

En ce qui concerne le chargement asynchrone ou peut-être paresseux, import ()c'est beaucoup plus puissant. Voyez quand nous avons besoin du composant de manière asynchrone, puis nous l'utilisons importd'une manière asynchrone comme dans l' constutilisation de variables await.

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

Ou si vous souhaitez utiliser require()alors,

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

La chose est en import()fait asynchrone dans la nature. Comme mentionné par neehar venugopal dans ReactConf , vous pouvez l'utiliser pour charger dynamiquement des composants React pour l'architecture côté client.

De plus, c'est beaucoup mieux quand il s'agit de routage. C'est la seule chose spéciale qui fait que le journal réseau télécharge une partie nécessaire lorsque l'utilisateur se connecte à un site Web spécifique à son composant spécifique. Par exemple, la page de connexion avant que le tableau de bord ne télécharge pas tous les composants du tableau de bord. Parce que ce qui est nécessaire, c'est-à-dire le composant de connexion actuel, ce sera seulement téléchargé.

Il en va de même pour export: ES6 exportsont exactement les mêmes que pour CommonJS module.exports.

REMARQUE - Si vous développez un projet node.js, vous devez alors strictement l'utiliser require()car le nœud générera une erreur d'exception comme invalid token 'import'si vous l'utilisiez import. Le nœud ne prend donc pas en charge les instructions d'importation.

MISE À JOUR - Comme suggéré par Dan Dascalescu : depuis la v8.5.0 (sortie en septembre 2017), node --experimental-modules index.mjsvous permet de l'utiliser importsans Babel. Vous pouvez (et devez) également publier vos packages npm en tant que ESModule natif, avec une compatibilité descendante pour l'ancienne requireméthode.

Voir ceci pour plus de clairance sur l'utilisation des importations asynchrones - https://www.youtube.com/watch?v=bb6RCrDaxhw

Rencontrez Zaveri
la source
1
Est-ce que l'exigence sera synchronisée et attendra?
baklazan
1
Peut dire des faits!
Rencontrez Zaveri
15

La chose la plus importante à savoir est que les modules ES6 sont, en effet, une norme officielle, contrairement aux modules CommonJS (Node.js).

En 2019, les modules ES6 sont pris en charge par 84% des navigateurs. Bien que Node.js les place derrière un indicateur --experimental-modules , il existe également un package de nœuds pratique appelé esm , qui facilite l'intégration.

Un autre problème que vous risquez de rencontrer entre ces systèmes de modules est l'emplacement du code. Node.js suppose que la source est conservée dans un node_modulesrépertoire, tandis que la plupart des modules ES6 sont déployés dans une structure de répertoire plate. Ce ne sont pas faciles à réconcilier, mais cela peut être fait en piratant votre package.jsonfichier avec des scripts de pré et post-installation. Voici un exemple de module isomorphe et un article expliquant comment cela fonctionne.

isysd
la source
8

J'utilise personnellement l'importation parce que, nous pouvons importer les méthodes requises, les membres en utilisant l'importation.

import {foo, bar} from "dep";

FileName: dep.js

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

Le mérite revient à Paul Shan. Plus d'infos .

chandoo
la source
1
Bon choix! Publiez -vous également vos packages npm en tant que module ESM natif, avec une compatibilité descendante pour l'ancienne requireméthode?
Dan Dascalescu
6
vous pouvez faire de même avec require!
Suisse
4
const {a,b} = require('module.js'); fonctionne aussi ... si vous exportez aetb
BananaAcid
module.exports = { a: ()={}, b: 22 }- La deuxième partie de @BananaAcid répond
Seth McClaine
7

À partir de maintenant l'importation ES6, l'exportation est toujours compilée vers CommonJS , il n'y a donc aucun avantage à utiliser l'une ou l'autre. Bien que l'utilisation d'ES6 soit recommandée car elle devrait être avantageuse lors de la prise en charge native des navigateurs. La raison en est que vous pouvez importer des partiels à partir d'un fichier alors qu'avec CommonJS, vous devez exiger tout le fichier.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Ci-dessous est l'utilisation courante de ceux-ci.

Export par défaut ES6

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

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

ES6 exporte plusieurs et importe plusieurs

// 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
la source
0

Je ne sais pas pourquoi (probablement optimisation - chargement paresseux?) Ça fonctionne comme ça, mais j'ai remarqué que le importcode ne peut pas être analysé si les modules importés ne sont pas utilisés.
Ce qui peut ne pas être un comportement attendu dans certains cas.

Prenez la classe Foo détestée comme exemple de dépendance.

foo.ts

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

Par exemple:

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'autre part:

index.ts

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