Compte tenu de l’ES2015, l’dependency injection et l’abstraction de la bibliothèque, à quoi devrait ressembler mon module idéal en 2016?

Sinon, pour une chose, je serais tout à fait à bord pour écrire tous mes modules comme

import A from './a.js'; var B = function(){ //use A }; export default B; 

puis en utilisant un compilateur pour le construire dans un navigateur ou un format de serveur.

Mon seul problème avec ce qui précède est cependant la spécification explicite de ./a.js dans l’ import .

Je comprends pourquoi les spécifications ont été appliquées de cette manière 1 , pour être en faveur de l’parsing statique . Mais il y a deux raisons très pratiques pour lesquelles la cuisson à la fois du nom de fichier d’ un module et de son chemin est un problème.

  1. Comme nous l’ avons déjà mentionné , lorsque vous recyclez fréquemment des modules d’un projet à l’autre, il est très probable que vous ne pourrez pas maintenir un chemin cohérent vers cette ressource dans votre arborescence de projets. Faire un appel d’ import myModule from './../../vendor/lib/dist/mod.js' comme import myModule from './../../vendor/lib/dist/mod.js' dans le code d’un module ne me semble pas parfaitement à l’épreuve du temps.
  2. Outre le chemin lui-même, spécifier le nom du fichier vous lie également. Quelque chose comme cela semble assez innocent:

    import $ from 'vendor/jquery.js'

    Mais qu’en est-il du jour où je veux utiliser Zepto au lieu de jQuery ? J’ai trouvé l’abstraction, particulièrement autour des bibliothèques de fournisseurs, extrêmement utile pour traiter des bases de code volumineuses, des développeurs avisés et un écosystème JavaScript en constante évolution. Je voudrais peut-être importer React comme bibliothèque de composants aujourd’hui, mais qu’en est-il de demain? De plus, que faire si je vais utiliser le même module à la fois sur le client et le serveur, mais j’ai besoin de différentes versions d’une bibliothèque dépendante?

J’insiste sur une abstraction robuste (mais claire et cohérente) dans mes équipes. Souvent, l’abstraction a pris la forme d’une sorte de placement de noms. Je fantasme un peu à ce sujet:

 //BAD: Bakes React into my component modules import ComponentLib from './React.js'; //GOOD: Leaves me free to use any React-like library import ComponentLib from 'vendor.lib.component'; 

vendor.lib.component , à la manière de Java, a été enregistré quelque part auparavant.

Notez que contrairement à cette question , mon objective n’est pas d’avoir un contrôle dynamic sur mes importations. Je ne veux pas de flexibilité dans le temps d’exécution, je voudrais de la flexibilité au moment de la construction . Je devrais pouvoir soustraire un framework dépendant pour un autre, ou pour un simulacre, ou pour quelque chose qui fonctionnera dans un environnement particulier, sans avoir à se soucier des dépendances que mes modules appellent ou en essayant de dupliquer un répertoire fou arbre pour chaque produit de construction, je suis après.

Des questions similaires ont conduit à la suggestion d’une bibliothèque qui exploite les spécifications du système , comme SystemJS . Vous pouvez alors utiliser quelque chose comme jspm pour introduire une carte de module pour obtenir l’abstraction. Mais au moment où je le fais, j’écris tous mes modules différemment:

 System.import('a', function(A){ //use 'A' }); 

Est-ce soudainement l’avenir? Si oui, pourquoi ne pas continuer à utiliser AMD? Pourquoi même déranger les modules ES2015 et les transstackurs si je ne fais que revenir à l’utilisation d’une API de chargement d’asynchronisme?

Plus impressionnant, je ne vois pas beaucoup de mention de la prise en charge d’une norme API de chargeur de module dans la spécification ES2017 .

( EDIT: Question révisée pour répondre aux normes d’une réponse sans opinion )

Compte tenu de tout ce qui précède, je demande à la communauté – comment puis-je écrire un module JavaScript qui (i) respecte la norme ES2015, (ii) ne référence pas un module dépendant par son nom de fichier ou son chemin, et (iii) ne s’appuie pas sur des outils / configurations intermédiaires étendus qui rendraient prohibitif le partage du module avec plusieurs équipes.

Note 1 Comme @zeroflagL l’a noté dans les commentaires, la spécification n’indique pas explicitement qu’un module doit être spécifié en tant que chemin, juste une chaîne (voir ModuleSpecifier – http://www.ecma-international.org/ecma-262/ 6.0 / # table-41 ). Cependant, il existe également une instruction claire pour prendre en compte les références circulaires, impliquant une sorte d’parsing statique ( http://www.ecma-international.org/ecma-262/6.0/#sec-imports ), avec des chemins de fichiers apparemment le contexte de référence de choix à ce point. Donc, nous ne pouvons pas blâmer la spécification d’être rigide ici, plutôt le contraire. Il nous incombe alors de développer des implémentations plus robustes d’ import / ModuleSpecifier menant à un standard secondaire.

Pour moi, cela semble être l’un des plus grands problèmes non résolus de la communauté JS. Il n’y a pas de meilleures pratiques en matière de gestion de la dépendance et d’dependency injection (du moins à ma connaissance).

Il y a une longue discussion sur ce sujet: Ai-je besoin d’injecter des dépendances dans NodeJS, ou comment gérer …? , mais la plupart des solutions semblent fonctionner uniquement dans des cas spécifiques et nécessitent de changer la façon dont vous écrivez les modules. Et le plus choquant est que beaucoup de réponses affirment que vous n’avez même pas besoin d’ID.

Ma propre solution à ce problème est cette infrastructure DI minimale , qui vous permet de définir des modules une fois, et vous les connecterez avec des dépendances appropriées.

Je sais que vous demandez une solution que vous pouvez utiliser spécifiquement dans le cas d’un compilateur JS, mais puisque vous demandez une meilleure pratique, je pense que nous devons prendre en compte le jeu complet dans lequel la gestion des dépendances JavaScript a lieu. , y compris différents environnements hôtes possibles, tels que le navigateur et Node.js, les systèmes d’empaquetage frontaux tels que Webpack, Browserify et jspm, et, dans une certaine mesure, les technologies voisines telles que HTML et HTTP / HTTP2.

Histoire des modules ES

Il y a une raison pour laquelle vous ne trouverez pas d’API de chargeur dans une spécification ECMA: la résolution de dépendance n’a pas été finalisée lorsque la date limite pour la spécification ECMA2015 est arrivée et il a été décidé que la spécification ECMA ne décrirait que la syntaxe du module. L’environnement (par exemple navigateur, node.js, compilateur / transstackr JS) serait chargé de résoudre les modules via leurs spécificateurs, via un hook appelé HostResolveImportedModule , que vous pourrez trouver dans les spécifications ECMA2015 et ECMA2017 .

La spécification de la sémantique du modèle de module ES2015 est maintenant entre les mains du WHATWG. Deux développements notables ont été le fruit de leurs efforts:

  1. Ils ont déterminé que les modules en HTML peuvent être désignés par .
  2. Il existe un brouillon pour une spécification de chargeur , qui tiendra compte de différents environnements hôtes et systèmes d’emballage frontaux. Cela ressemble à la spécification qui aidera finalement à répondre à votre question, mais malheureusement, elle est loin d'être terminée et n'a pas été mise à jour depuis un certain temps.

Implémentations actuelles

Navigateur

Avec l'ajout de la , tout semble en place pour une implémentation simplifiée des modules ES6 dans le navigateur. La façon dont cela fonctionne maintenant est sans aucun respect pour la performance (voir ceci et ce commentaire). En outre, il ne prend en charge aucun système d’emballage frontal. Il est clair que le concept de modules doit être étendu pour qu’il soit utilisable sur tous les sites Web de production et que, par conséquent, les éditeurs de navigateurs n’ont pas pris la peine de l’implémenter dans leurs navigateurs.

Node.js

Node.js est une toute autre histoire, car il a implémenté les modules de style CommonJS dès le départ. Actuellement, aucun support pour les modules ES2015 n'est présent. Deux suggestions distinctes ont été faites pour incorporer les modules ES6 dans Node.js aux côtés des modules CommonJS. Cet article traite de ces suggestions en détail.

compilateurs et systèmes d'emballage

  • Webpack 2 prend en charge les modules ES2015. En outre, il peut être ajusté pour utiliser la résolution de dépendance personnalisée .
  • Babel prend en charge les modules ES2015 et a la possibilité de les convertir en modules AMD, CommonJS, SystemJS et UMD. Il semble que les gestionnaires de paquets autres que Webpack prennent en charge les modules ES2015 via Babel.

Conclusion

Ainsi, lorsque vous demandez la meilleure façon d'écrire des modules, vous pouvez constater que c'est très difficile. Il est préférable de trouver une solution compatible avec tous les environnements hôtes possibles car:

  • Vous voudrez peut-être partager votre code entre différents environnements.
  • Il existe un risque que certaines classes de développeurs (par exemple, les développeurs front-end) adoptent les modules ES2015, tandis que d’autres (par exemple les développeurs Node.js) s’en tiennent à une autre solution (par exemple, les modules CommonJS). Cela déprécierait davantage la possibilité de code inter-environnement.

Mais vous verrez que les différents environnements ont des exigences différentes. En ce sens, la meilleure réponse à votre question pourrait être la suivante: il n’existe actuellement aucun moyen de rédiger des modules couvrant vos préoccupations en matière d’abstraction.

Solution

Cependant, si je devais écrire des modules ES2015 compilés en JS, je restrais loin des chemins relatifs et utiliserais toujours des chemins absolus depuis la racine du projet comme identifiant des modules, ce qui ne me semble pas problématique. . En fait, Java reflète les espaces de noms et la structure de ses répertoires de la même manière. J'utiliserais Webpack ou Babel pour comstackr mon code source vers un code exécutable dans les environnements JS actuels.

En ce qui concerne votre autre problème, si je veux pouvoir remplacer les bibliothèques de fournisseurs, je créerai probablement un module qui alias les bibliothèques de fournisseurs avec des noms que je vais utiliser en interne. Un peu comme:

 // /lib/libs.js import jQuery from 'vendor/jquery.js' export const $ = jQuery; 

Tous les autres modules importeront alors $ depuis lib / libs.js et vous pourrez changer de librairie en changeant la référence en un seul endroit.

Si vous voulez suivre les meilleures pratiques, suivez le guide de style JavaScript d’AirBnb. À mon avis, le meilleur et le plus complet guide de style JavaScript

https://github.com/airbnb/javascript#classes–constructors

Importer

Cela semble mauvais pour la réutilisation des modules: import myModule from './../../vendor/lib/dist/mod.js'

Publiez votre module sur NPM (qui peut également être un NPM privé ou auto-hébergé) et importez comme cela import myModule from 'my-module';

Définissez éventuellement votre NODE_PATH en tant que dossier racine et faites référence aux modules relativement depuis la racine.

Dans package.json

 'start': 'NODE_PATH=. node index.js' // in Windows 'start': 'NODE_PATH=. && node index.js' 

Maintenant, importez comme ça:

 import myModule from 'vendor/lib/dist/mod.js' 

Les variables

var ne fait pas partie d’ES6. Utilisation:

  • constant – lorsque la valeur de la variable ne change pas, également les objects et les importations. Même si les parameters de l’object changent, c’est toujours un const.

  • let – quand la valeur de la variable change c’est- for(let = i; i < 100; i++) dire for(let = i; i < 100; i++)

  • De par ma propre expérience, définissez toujours const comme valeur par défaut et ne changez que pour let plaindre l' ESLint (par exemple, utilisez ESLint http://eslint.org/ )

Des classes

Il existe maintenant un moyen approprié de définir des classes en JavaScript

 class B { constructor() { } doSomething() { } } 

Votre exemple mis à jour:

 import A from './a'; Class B { constructor() { } doSomething() { } }; export default B; 

Si vous souhaitez étendre A:

 import A from './a'; Class B extends A{ constructor(argumentA, argumentB) { super(argumentA, argumentB); this.paramA = argumentA; } }; export default B; 

Conseils

  • Utilisez Webpack avec NPM comme outil de construction. Ne pas utiliser Gulp ou Grunt
  • Utilisez Babel pour transposer votre code (le chargeur JSX peut ne pas suffire)
  • Apprenez à ne pas utiliser jQuery du tout, mais choisissez plutôt les bons polyfills et les outils pour les tâches à effectuer à partir de NPM
  • Il y a des tonnes de repos repères bien écrits sur github, alors volez du meilleur. Voici quelques réactions que j'utilise.

Ma réponse est essentiellement:

Le modèle / bibliothèque demandé est AirBnb JavaScript styleguide et oubliez jQuery