nodejs - Cargue librerías Javascript "Vanilla" en Node.js
node js modules exports (7)
Hay algunas bibliotecas Javascript de terceros que tienen alguna funcionalidad que me gustaría usar en un servidor Node.js. (Específicamente, quiero usar una biblioteca de JavaScript de QuadTree que encontré). Pero estas bibliotecas son solo archivos .js
sencillos y no "bibliotecas de Node.js".
Como tal, estas bibliotecas no siguen la sintaxis exports.var_name
que Node.js espera para sus módulos. Por lo que yo entiendo eso significa cuando haces module = require(''module_name'');
o module = require(''./path/to/file.js'');
terminará con un módulo sin funciones de acceso público, etc.
Mi pregunta es, entonces, "¿Cómo puedo cargar un archivo javascript arbitrario en Node.js para que pueda utilizar su funcionalidad sin tener que reescribirlo para que no exports
?"
Soy muy nuevo en Node.js así que avíseme si hay algún agujero evidente en mi comprensión de cómo funciona.
EDITAR : investigando más las cosas y ahora veo que el patrón de carga del módulo que usa Node.js es en realidad parte de un estándar recientemente desarrollado para cargar bibliotecas de Javascript llamado CommonJS . Dice este derecho en la página de documentación del módulo para Node.js , pero me perdí eso hasta ahora.
Puede terminar siendo que la respuesta a mi pregunta es "espere hasta que los autores de su biblioteca logren escribir una interfaz CommonJS o hágalo usted mismo".
AFAIK, así es como deben cargarse los módulos. Sin embargo, en lugar de virar todas las funciones exportadas en el objeto de exports
, también puede virarlas en this
(lo que de otro modo sería el objeto global).
Entonces, si desea mantener las otras bibliotecas compatibles, puede hacer esto:
this.quadTree = function () {
// the function''s code
};
o, cuando la biblioteca externa ya tiene su propio espacio de nombres, p. ej. jQuery
(no se puede usar eso en un entorno de servidor):
this.jQuery = jQuery;
En un entorno que no sea Node, this
se resolvería en el objeto global, convirtiéndolo así en una variable global ... que ya era. Entonces no debería romper nada.
Editar : James Herdman tiene una buena reseña sobre node.js para principiantes, que también menciona esto.
Aquí está lo que creo que es la respuesta más ''correcta'' para esta situación.
Supongamos que tiene un archivo de script llamado quadtree.js
.
Debes construir un node_module
personalizado que tenga este tipo de estructura de directorio ...
./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
Todo en su directorio ./node_modules/quadtree/quadtree-lib/
son archivos de su biblioteca de terceros.
Entonces su archivo ./node_modules/quadtree/index.js
simplemente cargará esa biblioteca desde el sistema de archivos y hará el trabajo de exportar las cosas correctamente.
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
Ahora puedes usar tu módulo quadtree
como cualquier otro módulo de nodo ...
var qt = require(''quadtree'');
qt.QuadTree();
Me gusta este método porque no hay necesidad de cambiar el código fuente de la biblioteca de terceros, por lo que es más fácil de mantener. Todo lo que necesita hacer en la actualización es mirar su código fuente y asegurarse de que todavía está exportando los objetos adecuados.
Hay un método mucho mejor que usar eval
: el módulo vm
.
Por ejemplo, aquí está mi módulo execfile
, que evalúa el script en la path
en el context
o en el contexto 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;
}
Y se puede usar así:
> 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
Donde example.js
contiene:
function getSomeGlobal() {
return someGlobal;
}
La gran ventaja de este método es que tienes un control total sobre las variables globales en el script ejecutado: puedes pasar elementos globales personalizados (a través del context
) y todos los elementos globales creados por el script se agregarán al context
. La depuración también es más fácil porque los errores de sintaxis y similares se informarán con el nombre de archivo correcto.
La forma más simple es: eval(require(''fs'').readFileSync(''./path/to/file.js'', ''utf8''));
Esto funciona muy bien para probar en el shell interactivo.
No estoy seguro de si realmente voy a terminar usando esto porque es una solución bastante hacky, pero una forma de evitar esto es construir un pequeño importador de mini módulos como este ...
En el archivo ./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;
}
Luego, cuando desee utilizar la funcionalidad de su biblioteca, tendrá que elegir manualmente qué nombres exportar.
Entonces para una biblioteca como el archivo ./lib/mylibrary.js
...
function Foo() { //Do something... }
biz = "Blah blah";
var bar = {''baz'':''filler''};
Cuando desee utilizar su funcionalidad en su código 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)
Sin embargo, no sé qué tan bien funcionaría esto en la práctica.
Pude simplemente agregar module.exports =
a la secuencia de comandos que era una función en su archivo.
Por ejemplo, donde su código, en su archivo que puse en ''./libs/apprise.js'', comienza con la function apprise(string, args, callback){
Lo he cambiado a:
module.exports = function(string, args, callback){
Entonces mi código dice:
window.apprise = require(''./libs/apprise.js'');
Y estaba listo para irme. YMMV, esto fue con el paquete web .
Una función simple de include(filename)
con un mejor mensaje de error (pila, nombre de archivo, etc.) para eval
, en caso de errores:
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");
}
}
Pero incluso se vuelve más sucio con nodejs: necesita especificar esto:
export NODE_MODULE_CONTEXTS=1
nodejs tmp.js
De lo contrario, no puede usar variables globales en archivos incluidos con include(...)
.