web-dev-qa-db-fra.com

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

Il y a des bibliothèques Javascript tierces qui ont des fonctionnalités que j'aimerais 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 que des fichiers .js Simples et non des "bibliothèques Node.js".

En tant que telles, ces bibliothèques ne suivent pas la syntaxe exports.var_name Que Node.js attend pour ses modules. Pour autant que je sache, 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 alors "Comment puis-je charger un fichier javascript arbitraire dans Node.js de telle sorte que je puisse utiliser ses fonctionnalités sans avoir à le réécrire pour qu'il fasse exports?"

Je suis très nouveau sur Node.js, alors faites-moi savoir s'il y a un trou flagrant dans ma compréhension de son fonctionnement.


[~ # ~] edit [~ # ~] : En cherchant davantage et je vois maintenant que le modèle de chargement de module utilisé par Node.js est en fait partie d'une norme récemment développée pour le chargement de bibliothèques Javascript appelée CommonJS . Il est indiqué sur la page de documentation du module pour Node.js , mais cela m'a manqué jusqu'à présent.

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

104
Chris W.

Il existe une bien meilleure méthode que d'utiliser eval: le module vm .

Par exemple, voici mon module execfile, qui évalue le script à path dans context ou dans 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 il peut être utilisé comme ceci:

> 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 gros 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 des globaux personnalisés (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.

73
David Wolever

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

Supposons que vous ayez un fichier de script appelé quadtree.js.

Vous devez créer un node_module Personnalisé qui possède ce type 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 répertoire ./node_modules/quadtree/quadtree-lib/ Sont des fichiers de votre bibliothèque tierce.

Ensuite, votre fichier ./node_modules/quadtree/index.js Chargera simplement cette bibliothèque à partir du système de fichiers et effectuera le travail d'exportation des 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 module quadtree comme n'importe quel autre module de noeud ...

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

J'aime cette méthode car il n'est pas nécessaire de changer le code source de votre bibliothèque tierce - il est donc plus facile à maintenir. Tout ce que vous devez faire lors de la mise à niveau est de regarder leur code source et de vous assurer que vous exportez toujours les objets appropriés.

79
Chris W.

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

30
Christopher Weiss

AFAIK, c'est bien ainsi que les modules doivent être chargés. Cependant, au lieu de clouer toutes les fonctions exportées sur l'objet exports, vous pouvez également les clouer sur 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 a déjà son propre espace de noms, par ex. jQuery (pas que vous puissiez utiliser ça dans un environnement côté serveur):

this.jQuery = jQuery;

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

Edit : James Herdman a un Nice writeup sur node.js pour les débutants, qui le mentionne également.

5
Martijn

Je ne suis pas sûr de finir par l'utiliser, car c'est une solution plutôt hacky, mais une solution consiste à créer un petit importateur de mini-module comme celui-ci ...

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 souhaitez utiliser les fonctionnalités de votre bibliothèque, vous devrez choisir manuellement les noms à exporter.

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

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

Lorsque vous souhaitez utiliser ses fonctionnalités 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 cela fonctionnerait bien dans la pratique.

3
Chris W.

J'ai pu le faire fonctionner en mettant à jour leur script, très facilement, en ajoutant simplement module.exports = le cas échéant...

Par exemple, j'ai pris leur fichier et j'ai copié dans './libs/apprise.js'. Alors où ça commence

function apprise(string, args, callback){

J'ai attribué la fonction à module.exports = Ainsi:

module.exports = function(string, args, callback){

Ainsi, je peux importer la bibliothèque dans mon code comme ceci:

window.apprise = require('./libs/apprise.js');

Et j'étais prêt à partir. YMMV, c'était avec webpack .

2
John Mee

Une fonction include(filename) simple avec une meilleure messagerie d'erreur (pile, nom de fichier, etc.) pour eval, en cas d'erreur:

var fs = require('fs');
// circumvent nodejs/v8 "bug":
// https://github.com/PythonJS/PythonJS/issues/111
// http://perfectionkills.com/global-eval-what-are-the-options/
// e.g. a "function test() {}" will be undefined, but "test = function() {}" will exist
var globalEval = (function() {
    var isIndirectEvalGlobal = (function(original, Object) {
        try {
            // Does `Object` resolve to a local variable, or to a global, built-in `Object`,
            // reference to which we passed as a first argument?
            return (1, eval)('Object') === original;
        } catch (err) {
            // if indirect eval errors out (as allowed per ES3), then just bail out with `false`
            return false;
        }
    })(Object, 123);
    if (isIndirectEvalGlobal) {
        // if indirect eval executes code globally, use it
        return function(expression) {
            return (1, eval)(expression);
        };
    } else if (typeof window.execScript !== 'undefined') {
        // if `window.execScript exists`, use it
        return function(expression) {
            return window.execScript(expression);
        };
    }
    // otherwise, globalEval is `undefined` since nothing is returned
})();

function include(filename) {
    file_contents = fs.readFileSync(filename, "utf8");
    try {
        //console.log(file_contents);
        globalEval(file_contents);
    } catch (e) {
        e.fileName = filename;
        keys = ["columnNumber", "fileName", "lineNumber", "message", "name", "stack"]
        for (key in keys) {
            k = keys[key];
            console.log(k, " = ", e[k])
        }
        fo = e;
        //throw new Error("include failed");
    }
}

Mais cela devient encore plus sale avec nodejs: vous devez spécifier ceci:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

Sinon, vous ne pouvez pas utiliser de variables globales dans les fichiers inclus avec include(...).

0
lama12345