Carica le librerie Javascript “Vanilla” in Node.js

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

In quanto tali, queste librerie non seguono la syntax exports.var_name che Node.js prevede per i suoi moduli. Per quanto ho capito, ciò significa che quando fai module = require('module_name'); o module = require('./path/to/file.js'); finirai con un modulo senza funzioni pubblicamente accessibili, ecc.

La mia domanda quindi è “Come carico un file javascript arbitrario in Node.js in modo tale che possa utilizzare le sue funzionalità senza doverlo riscrivere in modo che faccia le exports ?”

Sono molto nuovo a Node.js quindi per favore fatemi sapere se c’è qualche brutto buco nella mia comprensione di come funziona.


EDIT : Ricercando le cose di più e ora vedo che il modulo che carica il pattern utilizzato da Node.js è in realtà parte di uno standard sviluppato recentemente per caricare le librerie Javascript chiamate CommonJS . Dice questo sulla pagina doc del modulo per Node.js , ma fino ad ora mi mancava.

Può darsi che la risposta alla mia domanda sia “Aspetta che gli autori della tua biblioteca riescano a scrivere un’interfaccia CommonJS o farlo da solo”.

C’è un metodo molto migliore rispetto all’utilizzo di eval : il modulo vm .

Ad esempio, ecco il mio modulo execfile , che valuta lo script sul path in entrambi i context o 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 così:

 > 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.js contiene:

 function getSomeGlobal() { return someGlobal; } 

Il grande vantaggio di questo metodo è che hai il controllo completo sulle variabili globali nello script eseguito: puoi passare in globali personalizzati (tramite il context ) e tutti i globals creati dallo script saranno aggiunti al context . Anche il debugging è più semplice perché gli errori di syntax e simili verranno riportati con il nome file corretto.

Ecco cosa penso sia la risposta “più giusta” per questa situazione.

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

Dovresti creare un node_module personalizzato che 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 della tua libreria di terze parti.

Quindi il tuo file ./node_modules/quadtree/index.js 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 modulo quadtree come qualsiasi altro modulo nodo …

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

Mi piace questo metodo perché non è necessario cambiare il codice sorgente della libreria di terze parti, quindi è più semplice da mantenere. Tutto ciò che devi fare durante l’aggiornamento è guardare il loro codice sorgente e assicurarti di esportare ancora gli oggetti appropriati.

Il modo più semplice è: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); Funziona alla grande per il test nella shell intertriggers.

AFAIK, è così che i moduli devono essere caricati. Tuttavia, invece di virare tutte le funzioni esportate sull’object exports , è ansible aggiungerle anche a this (quello che altrimenti sarebbe l’object globale).

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

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

oppure, quando la libreria esterna ha già il proprio spazio dei nomi, ad esempio jQuery (non è ansible utilizzarlo in un ambiente lato server):

 this.jQuery = jQuery; 

In un ambiente non Node, this si risolverebbe sull’object globale, rendendolo quindi una variabile globale … che già era. Quindi non dovrebbe rompere nulla.

Modifica : James Herdman ha una bella descrizione di node.js per i principianti, che menziona anche questo.

Non sono sicuro se finirò per usarlo perché è una soluzione piuttosto hacky, ma un modo per aggirare questo è build un piccolo importatore di mini-module 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 utilizzare 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 quanto bene tutto ciò funzionerebbe nella pratica però.

Sono stato in grado di aggiungere semplicemente module.exports = allo script che era una funzione nel loro file.

Ad esempio, dove il loro codice, nel file che ho inserito in “./libs/apprise.js”, inizia con la function apprise(string, args, callback){ ho modificato in:

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

Quindi il mio codice legge:

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

E io ero buono per andare. YMMV, questo era con il webpack .

Una semplice funzione include(filename) con un migliore messaggio di errore (stack, nomefile 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/ // eg 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: è necessario specificare questo:

 export NODE_MODULE_CONTEXTS=1 nodejs tmp.js 

Altrimenti non è ansible utilizzare variabili globali nei file inclusi con include(...) .