Laden Sie die Javascript-Bibliotheken „Vanilla“ in Node.js.

108

Es gibt einige Javascript-Bibliotheken von Drittanbietern mit einigen Funktionen, die ich auf einem Node.js-Server verwenden möchte. (Insbesondere möchte ich eine QuadTree-Javascript-Bibliothek verwenden, die ich gefunden habe.) Diese Bibliotheken sind jedoch nur einfache .jsDateien und keine "Node.js-Bibliotheken".

Daher folgen diese Bibliotheken nicht der exports.var_nameSyntax, die Node.js für seine Module erwartet. Soweit ich das bedeutet , verstehen , wenn Sie tun , module = require('module_name');oder module = require('./path/to/file.js');Sie werden mit einem Modul ohne öffentlich zugängliche Funktionen am Ende, usw.

Meine Frage lautet dann: "Wie lade ich eine beliebige Javascript-Datei in Node.js, damit ich ihre Funktionalität nutzen kann, ohne sie neu schreiben zu müssen exports?"

Ich bin sehr neu bei Node.js, also lassen Sie mich bitte wissen, ob mein Verständnis, wie es funktioniert, ein eklatantes Loch enthält.


BEARBEITEN : Ich recherchiere mehr und sehe jetzt, dass das von Node.js verwendete Lademuster des Moduls Teil eines kürzlich entwickelten Standards zum Laden von Javascript-Bibliotheken namens CommonJS ist . Es steht dies direkt auf der Modul-Dokumentseite für Node.js , aber das habe ich bis jetzt verpasst.

Es kann sein, dass die Antwort auf meine Frage lautet: "Warten Sie, bis die Autoren Ihrer Bibliothek dazu gekommen sind, eine CommonJS-Schnittstelle zu schreiben, oder tun Sie es selbst."

Chris W.
quelle

Antworten:

75

Es gibt eine viel bessere Methode als die eval: dievm Modul.

Hier ist zum Beispiel mein execfileModul, das das Skript pathentweder im contextoder im globalen Kontext auswertet :

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;
}

Und es kann so verwendet werden:

> 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

Wo example.jsenthält:

function getSomeGlobal() {
    return someGlobal;
}

Der große Vorteil dieser Methode besteht darin, dass Sie die vollständige Kontrolle über die globalen Variablen im ausgeführten Skript haben: Sie können benutzerdefinierte Globals (via context) übergeben, und alle vom Skript erstellten Globals werden hinzugefügt context. Das Debuggen ist auch einfacher, da Syntaxfehler und dergleichen mit dem richtigen Dateinamen gemeldet werden.

David Wolever
quelle
Hat runInNewContextverwenden Sie den globalen Kontext , wenn context(anders als bezeichnet sandbox, in docs) nicht definiert ist? (Dieser Punkt wurde durch keine Dokumente, die ich gefunden habe, klargestellt)
Steven Lu
Es scheint, dass die Bewertungsmethode < stackoverflow.com/a/9823294/1450294 > von Christopher gut funktioniert , um mit einer Bibliothek eines Drittanbieters zu spielen, die Node oder das CommonJS-Muster nicht kennt . Welche Vorteile kann das vmModul in diesem Fall bieten?
Michael Scheper
2
In meinen Updates finden Sie eine Beschreibung, warum diese Methode besser ist als eval.
David Wolever
1
das total rockt - es ermöglichte mir, meinen webbasierten Nicht-Modul-Code sofort für eine serverseitige Implementierung wiederzuverwenden, die die Ausgaben [nach einem Zeitplan] per E-Mail versendet, anstatt sie auf einer Webseite anzuzeigen. Der gesamte Webcode verwendete das lose erweiterte Modulmuster und die Skriptinjektion - das funktioniert also so gut !!
Al Joslin
Wie können wir dies in Node.js verwenden, wenn example.js von der Bibliothek example1.js abhängt?
Sytolk
80

Hier ist meiner Meinung nach die "richtige" Antwort für diese Situation.

Angenommen, Sie haben eine Skriptdatei namens quadtree.js .

Sie sollten einen Benutzer erstellen node_module, der diese Art von Verzeichnisstruktur hat ...

./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

Alles in deinem ./node_modules/quadtree/quadtree-lib/ Verzeichnis sind Dateien aus Ihrer Drittanbieter-Bibliothek.

Dann ./node_modules/quadtree/index.jslädt Ihre Datei diese Bibliothek einfach aus dem Dateisystem und erledigt die Aufgabe, die Dinge richtig zu exportieren.

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

Jetzt können Sie Ihr quadtreeModul wie jedes andere Knotenmodul verwenden ...

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

Ich mag diese Methode, weil Sie den Quellcode Ihrer Drittanbieter-Bibliothek nicht ändern müssen - daher ist die Wartung einfacher. Alles, was Sie beim Upgrade tun müssen, ist den Quellcode zu überprüfen und sicherzustellen, dass Sie immer noch die richtigen Objekte exportieren.

Chris W.
quelle
3
Ich habe gerade Ihre Antwort gefunden (ein Multiplayer-Spiel zu erstellen und musste JigLibJS, unsere Physik-Engine, sowohl auf dem Server als auch auf dem Client einbinden). Sie haben mir viel Zeit und Ärger gespart. Danke dir!
Stevendesu
8
Wenn Sie dies genau befolgen, denken Sie daran, dass es ziemlich einfach ist, Ihren Ordner node_modules versehentlich mit NPM zu löschen, insbesondere wenn Sie ihn nicht in SCM einchecken. Stellen Sie auf jeden Fall in Betracht, Ihre QuadTree-Bibliothek in einem separaten Repository abzulegen und sie dann npm linkin Ihre Anwendung aufzunehmen. Dann wird es so behandelt, als wäre es ein natives Node.js-Paket.
Btown
@btown, könnten Sie für Neulinge wie mich etwas erweitern, was SCM und npm Link genau tun, um das potenzielle Problem zu verhindern, das Sie erwähnen?
Flion
Ist das wirklich notwendig, wenn ich nur ein Skript einfügen möchte?
Quantumpotato
1
@flion antwortet auf den alten Kommentar für andere ref, da ich sicher bin, dass Sie wissen, dass Sie jetzt antworten. SCM - Source Control Management (zB GIT) und ein Link zu einer schnellen, aber guten Demo von npm link
delp
30

Der einfachste Weg ist: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); Dies funktioniert hervorragend zum Testen in der interaktiven Shell.

Christopher Weiss
quelle
1
Prost Kumpel! Hat viel geholfen
Schöning
Dies ist auch der schnellste Weg, und manchmal ist es schnell und schmutzig, was Sie brauchen. Zwischen dieser und Davids Antwort ist diese SO-Seite eine ausgezeichnete Ressource.
Michael Scheper
5

AFAIK, so müssen Module geladen werden. Anstatt jedoch alle exportierten Funktionen an das exportsObjekt anzuheften, können Sie sie auch anheften this(was sonst das globale Objekt wäre).

Wenn Sie also die anderen Bibliotheken kompatibel halten möchten, können Sie dies tun:

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

oder wenn die externe Bibliothek bereits einen eigenen Namespace hat, z. B. jQuery(nicht, dass Sie diesen in einer serverseitigen Umgebung verwenden können):

this.jQuery = jQuery;

In einer Nicht-Knoten-Umgebung thiswürde das globale Objekt aufgelöst und somit zu einer globalen Variablen gemacht ... was es bereits war. Es sollte also nichts kaputt machen.

Edit : James Herdman hat eine nette Beschreibung über node.js für Anfänger, in der dies ebenfalls erwähnt wird.

Martijn
quelle
Der Trick 'this' klingt nach einer guten Möglichkeit, die Dinge portabler zu machen, sodass Node.js-Bibliotheken außerhalb von Node.js verwendet werden können. Er bedeutet jedoch, dass ich meine Javascript-Bibliotheken manuell ändern muss, um die Syntax von Node.js zu unterstützen .
Chris W.
@ChrisW.: Ja, Sie müssen Ihre Bibliotheken manuell ändern. Persönlich hätte ich mir auch gewünscht, dass ein zweiter Mechanismus externe Dateien enthält, der den globalen Namespace der enthaltenen Datei automatisch in den importierten Namespace konvertiert. Vielleicht könnten Sie eine RFE bei den Node-Entwicklern einreichen?
Martijn
3

Ich bin mir nicht sicher, ob ich das tatsächlich verwenden werde, weil es eine ziemlich hackige Lösung ist, aber eine Möglichkeit, dies zu umgehen, besteht darin, einen kleinen Mini-Modul-Importer wie diesen zu erstellen ...

In der Datei ./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;
}

Wenn Sie dann die Funktionalität Ihrer Bibliothek nutzen möchten, müssen Sie manuell auswählen, welche Namen exportiert werden sollen.

Also für eine Bibliothek wie die Datei ./lib/mylibrary.js...

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

Wenn Sie seine Funktionalität in Ihrem Node.js-Code verwenden möchten ...

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)

Ich weiß allerdings nicht, wie gut das alles in der Praxis funktionieren würde.

Chris W.
quelle
Hey, wow: eine heruntergestimmte (nicht von mir) und eine hochgestimmte Antwort desselben Benutzers für dieselbe Frage! Dafür sollte es ein Abzeichen geben! ;-)
Michael Scheper
2

Ich konnte es zum Laufen bringen, indem ich ihr Skript sehr einfach aktualisierte und einfach hinzufügte module.exports = und gegebenenfalls ...

Zum Beispiel habe ich ihre Datei genommen und nach './libs/apprise.js' kopiert. Dann wo es anfängt

function apprise(string, args, callback){

Ich habe die Funktion folgendermaßen zugewiesen module.exports =:

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

So kann ich die Bibliothek folgendermaßen in meinen Code importieren :

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

Und ich war gut zu gehen. YMMV, das war mit Webpack .

John Mee
quelle
0

Eine einfache include(filename)Funktion mit besserer Fehlermeldung (Stapel, Dateiname usw.) für eval: Fehler:

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");
    }
}

Mit nodejs wird es jedoch noch schmutziger: Sie müssen Folgendes angeben:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

Andernfalls können Sie keine globalen Variablen in Dateien verwenden, die in enthalten sind include(...).

lama12345
quelle