Vous devez savoir que Javascript (mieux vaut dire EcmaScript) ne spécifie aucune fonction pour lire et écrire des fichiers.

En fait, Javascript est juste le langage utilisé par de nombreux environnements (le navigateur, ou NodeJS, sont des exemples d’environnements) qui offrent plus d’objets et de fonctions pour travailler.

Node a été le premier environnement à offrir un moyen d’organiser le code en modules en utilisant une fonction spéciale appelée require(). Comment cela fonctionne-t-il ? Essayons de l’implémenter à partir de zéro.

Voici un exemple de require au travail:

//test.jsmodule.exports = "Hello World";
//main.jsconst test = require("./test.js"); console.log(test) 

Ecrivons cette fonction require.

Que doit faire une fonction require()

une fonction requireest attendue pour :

  • lire le contenu d’un fichier javascript dans une chaîne
  • évaluer ce code
  • enregistrer la fonction/objet exporté dans un cache pour une utilisation ultérieure (ne lire les fichiers qu’une fois)

Disclaimer

Nous n’allons pas reconstruire tout le NodeJS en un seul post. En fait, je ne mettrai pas en œuvre beaucoup de vérifications NodeJS et de rires, nous sommes seulement intéressés à comprendre comment les choses fonctionnent.

Nous aurons toujours besoin de la vraie fonction require pour charger le module fs. Je ne triche pas, c’est juste que ce post doit se terminer tôt ou tard 🙂

fonction myRequire()

voici le code:

//file setup.jsconst fs = require('fs');myRequire.cache = Object.create(null); //(1)function myRequire(name) { if (!(name in myRequire.cache)) { let code = fs.readFileSync(name, 'utf8'); //(2) let module = {exports: {}}; //(3) myRequire.cache = module; //(4) let wrapper = Function("require, exports, module", code); //(5) wrapper(myRequire, module.exports, module); //(6) } return myRequire.cache.exports; //(7)}...

Avez-vous oublié de déclarer la variable myRequire?

Non. En Javascript, les fonctions déclarées avec le mot clé function sont évaluées avant tout autre code (les fonctions sont « hissées ») donc elles peuvent être référencées même avant d’être déclarées.

De plus, les fonctions peuvent avoir des propriétés (c’est du javascript !) donc vous pouvez ajouter la propriété cache à la fonction myRequire (étape 1).

Enfin, nous créons la propriété cache avec Object.create. Avec cette fonction nous pouvons spécifier le prototype de l’objet, nous avons choisi de ne pas spécifier de prototype. Pourquoi ? De cette façon, nous ne salissons pas les autres fonctions ou propriétés déclarées par le runtime. Voici une explication.

Retournons à myRequire . Si le fichier que nous importons n’est pas dans le cache, nous lisons le fichier sur le disque (étape 2).

Puis nous déclarons un objet module vide avec une seule propriété, exports (étape 3).

Nous ajoutons ce module vide au cache, en utilisant le nom du fichier comme clé, et la magie opère (étape 4).

Le constructeur Function

En JS, nous pouvons évaluer une chaîne de code js de deux manières. La première façon est via la fonction eval(), qui est un peu dangereuse (elle gâche la portée) donc il est fortement déconseillé de l’utiliser.

La deuxième façon d’évaluer le code que nous avons dans une chaîne est via le constructeur Function. Ce constructeur prend une chaîne avec les arguments et une chaîne avec le code. De cette façon, tout a sa propre portée et ne gâche pas les choses pour les autres.

Donc, fondamentalement, nous créons une nouvelle fonction avec ces variables (étape 5) : require, exports, et module. Pensons un instant au premier exemple de ce billet, le fichier test.js : il devient

function(require, exports, module) { module.exports = "Hello World" }

et le deuxième fichier, main.js:

function(require, exports, module) { const test = require("./test.js"); console.log(test) }

Les variables qui semblaient « globales » dans les fichiers sont en effet passées comme arguments de fonction.

Dernière étape : exécution de la fonction

Nous avons créé (étape 6) une variable wrapper qui contient une fonction, mais la fonction n’est jamais exécutée. Nous faisons cela à la ligne:

wrapper(myRequire, module.exports, module); 

Notez que la deuxième variable (qui devrait être exports) est juste un handle vers module.exports ; les créateurs de NodeJS ont pensé que cela aurait pu aider à écrire moins de code…

Lorsque Node exécute la fonction, tout ce qui a été « exporté » (votre API publique) est lié au cache.

(Vous vous souvenez de la ligne myRequire.cache = module; ? Quand elle a été trouvée pour la première fois par le compilateur, elle pointait vers un objet factice { exports: {} } ; maintenant, elle contient votre module.)

NOTE. Puisque nous passons myRequire à la fonction wrapper, nous pouvons désormais utiliser require dans nos fichiers de test, mais notre require est appelé. Ajoutez un console.log si vous ne me faites pas confiance 😉

Enfin… myRequire renvoie le truc exported que vous avez déclaré (étape 7), et que nous avons enregistré dans le cache pour ne pas avoir à réévaluer ce code à nouveau.

Considérations finales

Un exemple de ce code peut être trouvé ici, ainsi que quelques logs de console qui expliquent ce qui se passe.

L’idée de cet article vient de l’explication de cette fonction au chapitre 10 (Modules). Le livre (Eloquent Javascript) est excellent, mais j’avais envie de mieux comprendre, et d’essayer avec un débogueur, ce que je ne pouvais pas comprendre avec mon seul esprit.

Vous devriez absolument lire le livre si vous voulez mieux comprendre javascript.

  • Share on Facebook
  • Tweet
  • Share on LinkedIn

Tags : javascript – nodejs

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.