Carica le librerie Javascript "Vanilla" in Node.js


108

Esistono alcune librerie Javascript di terze parti che hanno alcune funzionalità che vorrei utilizzare in un server Node.js. (In particolare, voglio usare una libreria javascript QuadTree che ho trovato.) Ma queste librerie sono solo .jsfile semplici e non "librerie Node.js".

Come tali, queste librerie non seguono la exports.var_namesintassi che Node.js si aspetta per i suoi moduli. Per quanto ho capito, significa che quando lo fai module = require('module_name');o module = require('./path/to/file.js');ti ritroverai con un modulo senza funzioni accessibili pubblicamente, ecc.

La mia domanda quindi è "Come faccio a caricare un file javascript arbitrario in Node.js in modo tale da poter utilizzare la sua funzionalità senza doverlo riscrivere in modo che funzioni exports?"

Sono molto nuovo su Node.js quindi per favore fammi sapere se c'è qualche buco evidente nella mia comprensione di come funziona.


EDIT : Cercando di più sulle cose e ora vedo che il modello di caricamento del modulo utilizzato da Node.js è in realtà parte di uno standard sviluppato di recente per il caricamento delle librerie Javascript chiamato CommonJS . Lo dice direttamente nella pagina della documentazione del modulo per Node.js , ma fino ad ora mi mancava.

Potrebbe finire per essere che la risposta alla mia domanda è "aspetta che gli autori della tua libreria arrivino a scrivere un'interfaccia CommonJS o fallo tu stesso".


Risposte:


75

C'è un metodo molto migliore rispetto all'utilizzo eval: il vmmodulo.

Ad esempio, ecco il mio execfilemodulo, che valuta lo script pathin uno contexto nel contesto globale:

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

E può essere usato in questo modo:

> 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

Dove example.jscontiene:

function getSomeGlobal() {
    return someGlobal;
}

Il grande vantaggio di questo metodo è che hai il controllo completo sulle variabili globali nello script eseguito: puoi passare a globali personalizzate (tramite context) e tutte le globali create dallo script verranno aggiunte a context. Il debug è anche più semplice perché errori di sintassi e simili verranno riportati con il nome file corretto.


Non runInNewContextutilizzare il contesto globale, se context(altrimenti denominata sandbox, in documenti) non è definito? (questo punto non è stato chiarito da nessun documento che ho trovato)
Steven Lu

Sembra che, allo scopo di giocare con una libreria di terze parti che ignora Node o il pattern CommonJS, il metodo eval di Christopher < stackoverflow.com/a/9823294/1450294 > funzioni bene. Quali vantaggi può vmoffrire il modulo in questo caso?
Michael Scheper

2
Vedere i miei aggiornamenti per una descrizione del motivo per cui questo metodo è migliore di eval.
David Wolever

1
questo è assolutamente eccezionale - mi ha permesso di riutilizzare immediatamente il mio codice non modulo basato sul web per un'implementazione lato server che invia gli output [in base a una pianificazione] invece di visualizzarli su una pagina web. Tutto il codice web utilizzava il modello di modulo a espansione libera e l'iniezione di script, quindi funziona così bene !!
Al Joslin

Come possiamo usarlo in Node.js se example.js dipende dalla libreria example1.js?
sytolk

80

Ecco quella che penso sia la risposta "più giusta" per questa situazione.

Supponi di avere un file di script chiamato quadtree.js.

Dovresti creare un custom node_moduleche abbia questo tipo di struttura di directory ...

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

Tutto nella tua ./node_modules/quadtree/quadtree-lib/directory sono file dalla tua libreria di terze parti.

Quindi il tuo ./node_modules/quadtree/index.jsfile caricherà quella libreria dal filesystem e farà il lavoro di esportare le cose correttamente.

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

Ora puoi usare il tuo quadtreemodulo come qualsiasi altro modulo nodo ...

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

Mi piace questo metodo perché non è necessario modificare il codice sorgente della libreria di terze parti, quindi è più facile da mantenere. Tutto quello che devi fare durante l'aggiornamento è guardare il loro codice sorgente e assicurarti di esportare ancora gli oggetti corretti.


3
Appena trovata la tua risposta (creando un gioco multiplayer e dovevo includere JigLibJS, il nostro motore fisico, sul server e sul client) mi hai fatto risparmiare un SACCO di tempo e fatica. Grazie!
stevendesu

8
Se segui esattamente questo, tieni presente che è abbastanza facile cancellare accidentalmente la tua cartella node_modules usando NPM, specialmente se non la controlli in SCM. Prendi sicuramente in considerazione l'idea di mettere la tua libreria QuadTree in un repository separato, quindi di npm linkinserirla nell'applicazione. Quindi viene gestito come se fosse un pacchetto Node.js nativo.
btown

@btown, potresti espandere un po 'per i neofiti come me cosa fanno esattamente SCM e npm link che prevengono il potenziale problema che hai menzionato?
Flion

È davvero necessario se voglio solo includere una sceneggiatura?
quantumpotato

1
@flion risponde al vecchio commento per altri ref perché sono sicuro che ormai saprai che rispondi. SCM - Source Control Management (ad es. GIT) e un collegamento a una demo rapida ma buona di npm link
delp

30

Il modo più semplice è: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); funziona benissimo per i test nella shell interattiva.


1
Ciao amico! Mi ha aiutato molto
Schoening

Questo è anche il modo più veloce, ea volte veloce e sporco è ciò di cui hai bisogno. Tra questa e la risposta di David, questa pagina SO è un'ottima risorsa.
Michael Scheper

5

AFAIK, è proprio così che devono essere caricati i moduli. Tuttavia, invece di attaccare tutte le funzioni esportate exportsall'oggetto, puoi anche attaccarle this(quello che altrimenti sarebbe l'oggetto globale).

Quindi, se vuoi mantenere le altre librerie compatibili, puoi farlo:

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

o, quando la libreria esterna ha già un proprio spazio dei nomi, ad esempio jQuery(non che è possibile utilizzare che in un ambiente sul lato server):

this.jQuery = jQuery;

In un ambiente non Node, thisrisolverebbe l'oggetto globale, rendendolo così una variabile globale ... che già era. Quindi non dovrebbe rompere nulla.

Modifica : James Herdman ha una bella recensione su node.js per principianti, che menziona anche questo.


Il trucco "questo" sembra un buon modo per rendere le cose più portabili in modo che le librerie Node.js possano essere utilizzate al di fuori di Node.js, ma significa comunque che devo modificare manualmente le mie librerie javascript per supportare la sintassi Node.js .
Chris W.

@ ChrisW .: sì, dovrai modificare manualmente le tue librerie. Personalmente, mi sarebbe piaciuto anche un secondo meccanismo per includere file esterni, uno che convertisse automaticamente lo spazio dei nomi globale del file incluso nello spazio dei nomi importato. Forse potresti presentare una RFE agli sviluppatori di Node?
Martijn

3

Non sono sicuro se finirò per usarlo perché è una soluzione piuttosto hacky, ma un modo per aggirare questo è creare un piccolo importatore di mini-moduli come questo ...

Nel file ./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;
}

Quindi, quando desideri utilizzare le funzionalità della tua libreria, dovrai scegliere manualmente quali nomi esportare.

Quindi per una libreria come il file ./lib/mylibrary.js...

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

Quando vuoi usare la sua funzionalità nel tuo codice 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)

Non so come funzionerebbe tutto bene in pratica però.


Ehi, wow: una risposta sconsigliata (non da me) e votata a favore dello stesso utente per la stessa domanda! Dovrebbe esserci un badge per quello! ;-)
Michael Scheper

2

Sono stato in grado di farlo funzionare aggiornando il loro script, molto facilmente, semplicemente aggiungendo module.exports =dove appropriato ...

Ad esempio, ho preso il loro file e l'ho copiato in "./libs/apprise.js". Poi da dove inizia

function apprise(string, args, callback){

Ho assegnato la funzione a module.exports =così:

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

Quindi sono in grado di importare la libreria nel mio codice in questo modo:

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

Ed ero a posto. YMMV, questo era con webpack .


0

Una semplice include(filename)funzione con una migliore messaggistica di errore (stack, nome file ecc.) Per eval, in caso di errori:

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

Ma diventa anche più sporco con nodejs: devi specificare questo:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

In caso contrario, non è possibile utilizzare variabili globali nei file inclusi con include(...).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.