113 votes

Charger les bibliothèques Javascript "Vanilla" dans Node.js

Il y a quelques bibliothèques Javascript tierces qui ont des fonctionnalités que je voudrais utiliser dans un serveur Node.js. (Plus précisément, je veux utiliser une bibliothèque Javascript QuadTree que j'ai trouvée.) Mais ces bibliothèques ne sont qu'une simple .js et non "les bibliothèques Node.js".

En tant que telles, ces bibliothèques ne suivent pas le principe de la exports.var_name que Node.js attend pour ses modules. D'après ce que je comprends, cela signifie que lorsque vous faites module = require('module_name'); ou module = require('./path/to/file.js'); vous vous retrouverez avec un module sans fonctions accessibles au public, etc.

Ma question est donc la suivante : "Comment puis-je charger un fichier javascript arbitraire dans Node.js de manière à pouvoir utiliser ses fonctionnalités sans avoir à le réécrire pour qu'il le fasse ? exports ?"

Je suis très novice en matière de Node.js, alors n'hésitez pas à me faire savoir s'il y a un trou flagrant dans ma compréhension de son fonctionnement.


EDIT : En faisant plus de recherches, je vois maintenant que le modèle de chargement de modules que Node.js utilise fait en fait partie d'un standard récemment développé pour le chargement des bibliothèques Javascript appelé CommonJS . C'est écrit juste sur le page de documentation du module pour Node.js mais j'ai manqué ça jusqu'à maintenant.

Il se peut que la réponse à ma question soit "attendez que les auteurs de votre bibliothèque se décident à écrire une interface CommonJS ou faites-le vous-même".

0 votes

81voto

Chris W. Points 7182

Voici ce que je pense être la réponse la plus "juste" dans cette situation.

Disons que vous avez un fichier script appelé quadtree.js .

Vous devez construire un node_module qui a cette sorte de structure de répertoire...

./node_modules/quadtree/quadtree-lib/
./node_modules/quadtree/quadtree-lib/quadtree.js
./node_modules/quadtree/quadtree-lib/README
./node_modules/quadtree/quadtree-lib/some-other-crap.js
./node_modules/quadtree/index.js

Tout dans votre ./node_modules/quadtree/quadtree-lib/ sont des fichiers de votre bibliothèque tierce.

Alors votre ./node_modules/quadtree/index.js chargera simplement cette bibliothèque à partir du système de fichiers et fera le travail d'exporter les choses correctement.

var fs = require('fs');

// Read and eval library
filedata = fs.readFileSync('./node_modules/quadtree/quadtree-lib/quadtree.js','utf8');
eval(filedata);

/* The quadtree.js file defines a class 'QuadTree' which is all we want to export */

exports.QuadTree = QuadTree

Vous pouvez maintenant utiliser votre quadtree comme tout autre module de nœud...

var qt = require('quadtree');
qt.QuadTree();

J'aime cette méthode car il n'est pas nécessaire de modifier le code source de votre bibliothèque tierce, ce qui facilite la maintenance. Tout ce que vous devez faire lors d'une mise à jour est de regarder leur code source et de vous assurer que vous exportez toujours les bons objets.

3 votes

Je viens de trouver votre réponse (je faisais un jeu multijoueur et j'avais besoin d'inclure JigLibJS, notre moteur physique, sur le serveur ainsi que sur le client) ; vous m'avez fait gagner BEAUCOUP de temps et de tracas. Merci !

8 votes

Si vous suivez cette procédure à la lettre, gardez à l'esprit qu'il est assez facile d'effacer accidentellement votre dossier node_modules en utilisant NPM, surtout si vous ne l'enregistrez pas dans SCM. Envisagez définitivement de placer votre bibliothèque QuadTree dans un dépôt séparé, puis npm link l'intégrer dans votre application. Il est alors traité comme s'il s'agissait d'un paquet natif de Node.js.

0 votes

@btown, pourriez-vous développer un peu pour les novices comme moi ce que font exactement SCM et npm link pour éviter le problème potentiel que vous mentionnez ?

78voto

David Wolever Points 34304

Il existe une bien meilleure méthode que celle qui consiste à utiliser eval : le vm module.

Par exemple, voici mon execfile qui évalue le script à path soit dans context ou le contexte global :

var vm = require("vm");
var fs = require("fs");
module.exports = function(path, context) {
  context = context || {};
  var data = fs.readFileSync(path);
  vm.runInNewContext(data, context, path);
  return context;
}

Et ça peut être utilisé comme ça :

> var execfile = require("execfile");
> // `someGlobal` will be a global variable while the script runs
> var context = execfile("example.js", { someGlobal: 42 });
> // And `getSomeGlobal` defined in the script is available on `context`:
> context.getSomeGlobal()
42
> context.someGlobal = 16
> context.getSomeGlobal()
16

example.js contient :

function getSomeGlobal() {
    return someGlobal;
}

Le grand avantage de cette méthode est que vous avez un contrôle total sur les variables globales dans le script exécuté : vous pouvez passer dans des globales personnalisées (via context ), et tous les globaux créés par le script seront ajoutés à context . Le débogage est également plus facile car les erreurs de syntaxe et autres seront signalées avec le nom de fichier correct.

0 votes

Fait runInNewContext utiliser le contexte global si context (autrement appelé sandbox dans la documentation) n'est pas défini ? (ce point n'est pas clair dans les documents que j'ai trouvés).

0 votes

Il semble que, pour jouer avec une bibliothèque tierce ignorant tout de Node ou du modèle CommonJS, la méthode eval de Christopher < stackoverflow.com/a/9823294/1450294 > fonctionne bien. Quels avantages le vm module offre dans ce cas ?

2 votes

Voir mes mises à jour pour une description de la raison pour laquelle cette méthode est meilleure que l'évaluation.

31voto

Christopher Weiss Points 242

La méthode la plus simple est la suivante : eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); Cela fonctionne très bien pour les tests dans le shell interactif.

1 votes

Cheers Mate ! Cela m'a beaucoup aidé

1 votes

C'est aussi le moyen le plus rapide, et parfois, c'est de la rapidité et de la saleté dont on a besoin. Entre cela et la réponse de David, cette page SO est une excellente ressource.

5voto

Martijn Points 6412

AFAIK, c'est en effet de cette manière que les modules doivent être chargés. Cependant, au lieu d'ajouter toutes les fonctions exportées dans le module exports vous pouvez également les ajouter à l'objet this (ce qui serait autrement l'objet global).

Donc, si vous voulez garder les autres bibliothèques compatibles, vous pouvez le faire :

this.quadTree = function () {
  // the function's code
};

ou, lorsque la bibliothèque externe possède déjà son propre espace de noms, par exemple jQuery (non pas que vous puissiez utiliser que dans un environnement côté serveur) :

this.jQuery = jQuery;

Dans un environnement non-Node, this se résoudrait à l'objet global, ce qui en ferait une variable globale... ce qu'elle était déjà. Donc ça ne devrait rien casser.

Modifier : James Herdman a un belle écriture sur node.js pour les débutants, qui mentionne également ceci.

0 votes

L'astuce 'this' semble être un bon moyen de rendre les choses plus portables afin que les bibliothèques Node.js puissent être utilisées en dehors de Node.js, mais cela signifie toujours que je dois changer manuellement mes bibliothèques javascript pour supporter la syntaxe require de Node.js.

0 votes

@ChrisW. : oui, vous devrez modifier manuellement vos bibliothèques. Personnellement, j'aurais aussi aimé avoir un deuxième mécanisme pour inclure des fichiers externes, un qui convertit automatiquement l'espace de nom global du fichier inclus en espace de nom importé. Peut-être pourriez-vous déposer une RFE auprès des développeurs de Node ?

4voto

Chris W. Points 7182

Je ne sais pas si je finirai par l'utiliser parce que c'est une solution plutôt bricolée, mais une façon de contourner le problème est de construire un petit importateur de mini-modules comme ceci...

Dans le fichier ./node_modules/vanilla.js :

var fs = require('fs');

exports.require = function(path,names_to_export) {
    filedata = fs.readFileSync(path,'utf8');
    eval(filedata);
    exported_obj = {};
    for (i in names_to_export) {
        to_eval = 'exported_obj[names_to_export[i]] = ' 
            + names_to_export[i] + ';'
        eval(to_eval); 
    }
    return exported_obj;
}

Ensuite, lorsque vous voudrez utiliser les fonctionnalités de votre bibliothèque, vous devrez choisir manuellement les noms à exporter.

Ainsi, pour une bibliothèque comme le fichier ./lib/mylibrary.js ...

function Foo() { //Do something... }
biz = "Blah blah";
var bar = {'baz':'filler'};

Lorsque vous voulez utiliser sa fonctionnalité dans votre code Node.js...

var vanilla = require('vanilla');
var mylibrary = vanilla.require('./lib/mylibrary.js',['biz','Foo'])
mylibrary.Foo // <-- this is Foo()
mylibrary.biz // <-- this is "Blah blah"
mylibrary.bar // <-- this is undefined (because we didn't export it)

Je ne sais pas si tout cela fonctionnerait bien dans la pratique.

0 votes

Hé, wow : une réponse au vote négatif (pas par moi) et au vote positif par le même utilisateur pour la même question ! Il devrait y avoir un badge pour ça ! ;-)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X