Загрузите "ванильные" библиотеки Javascript в узел.js


есть некоторые сторонние библиотеки Javascript, которые имеют некоторые функциональные возможности, которые я хотел бы использовать в узле.сервер JS. (В частности, я хочу использовать библиотеку javascript QuadTree, которую я нашел.) Но эти библиотеки просто просты .js файлы, а не " Узел.библиотеки js".

таким образом, эти библиотеки не следуют exports.var_name синтаксис этого узла.js ожидает для своих модулей. Насколько я понимаю, это означает, когда вы делаете module = require('module_name'); или module = require('./path/to/file.js'); вы будете в конечном итоге с модуль без публично доступных функций, etc.

мой вопрос тогда: "как загрузить произвольный файл javascript в узел.js такой, что я могу использовать его функциональность без необходимости переписывать его так, чтобы он делал exports?"

Я очень новичок в узле.js поэтому, пожалуйста, дайте мне знать, если есть какая-то вопиющая дыра в моем понимании того, как это работает.


EDIT: исследуя вещи больше, и теперь я вижу, что загрузка модуля шаблон этого узла.JS uses на самом деле является частью недавно разработанного стандарта для загрузки библиотек Javascript под названием CommonJS. Он говорит это прямо на модуль doc страница для узла.js, но я пропустил это до сих пор.

это может закончиться тем, что ответ на мой вопрос: "подождите, пока авторы вашей библиотеки не решатся написать интерфейс CommonJS или сделать это самостоятельно."

7 101

7 ответов:

есть гораздо лучший способ, чем использование eval: the vm модуль.

например, вот мой execfile модуль, который вычисляет скрипт path либо context или глобальный контекст:

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

и его можно использовать так:

> 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 содержит:

function getSomeGlobal() {
    return someGlobal;
}

большим преимуществом этого метода является то, что у вас есть полный контроль над глобальными переменными в выполненных скрипт: вы можете передать в пользовательские глобалы (через context), и все глобалы, созданные скриптом, будут добавлены в context. Отладка также проще, потому что синтаксические ошибки и тому подобное будут сообщаться с правильным именем файла.

вот что я думаю ,что это "самый правильный" ответ для этой ситуации.

скажем, у вас есть файл сценария под названием quadtree.js.

вы должны построить собственное node_module это имеет такую структуру каталогов...

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

все в вашей ./node_modules/quadtree/quadtree-lib/ каталог-это файлы из вашей сторонней библиотеки.

затем ваш ./node_modules/quadtree/index.js file просто загрузит эту библиотеку из файловой системы и выполнит работу по экспорту вещей правильно.

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

теперь вы можете использовать ваш quadtree модуль, как и любой другой модуль...

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

мне нравится этот метод, потому что нет необходимости менять исходный код 3-й партии библиотеки--так легче поддерживать. Все, что вам нужно сделать при обновлении посмотреть на их исходный код и убедиться, что вы по-прежнему экспортировать объекты.

самый простой способ-это: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); Это отлично подходит для тестирования в интерактивной оболочке.

AFAIK, это действительно то, как модули должны быть загружены. Однако вместо привязки всех экспортированных функций к exports объект, вы также можете прикрепить их на this (что в противном случае было бы глобальным объектом).

Итак, если вы хотите сохранить совместимость других библиотек, вы можете сделать это:

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

или, когда внешняя библиотека уже имеет свое собственное пространство имен, например,jQuery (не то, что вы можете использовать это на стороне сервера окружающая среда):

this.jQuery = jQuery;

в Неузловой среде,this разрешит глобальный объект, тем самым делая его глобальной переменной... что уже было сделано. Так что он не должен ничего ломать.

Edit: Джеймс Хердман имеет хорошая рецензия о узел.JS для начинающих, который также упоминает об этом.

Я не уверен, что я на самом деле буду использовать это, потому что это довольно хаки решение, но один из способов обойти это-построить небольшой импортер мини-модулей, как это...

в файле ./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;
}

затем, когда вы хотите использовать функциональность вашей библиотеки вам нужно будет вручную выбрать, какие имена для экспорта.

так что для библиотеки, как файл ./lib/mylibrary.js...

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

если вы хотите использовать его функциональность в вашем узле.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)

не знаю, насколько хорошо это все работает на практике.

я смог просто добавить module.exports = к скрипту, который был функцией в их файле.

например, где их код, в их файле, который я поместил в './библиотеки/информировать.Яш', начинается с function apprise(string, args, callback){ Я изменил его на:

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

затем мой кодекса гласит:

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

и мне было хорошо идти. YMMV, это было с webpack.

простой include(filename) функция с лучшим сообщением об ошибках (стек, имя файла и т. д.) для eval в случае ошибки:

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

но он даже становится грязнее с nodejs: вам нужно указать это:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

в противном случае вы не можете использовать глобальные переменные в файлах, включенных в include(...).