Подтвердить что ты не робот

Загрузить "Vanilla" Библиотеки Javascript в Node.js

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

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

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

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


РЕДАКТИРОВАТЬ. Изучение вещей больше, и теперь я вижу, что шаблон загрузки модуля, который использует Node.js, фактически является частью недавно разработанного стандарта для загрузки библиотек Javascript под названием CommonJS. Он говорит об этом прямо на странице doc для Node.js, но до этого я пропустил это.

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

4b9b3361

Ответ 1

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

Например, вот мой модуль execfile, который оценивает script в 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;
}

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

Ответ 2

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

Скажем, у вас есть script файл с именем 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 будет просто загружать эту библиотеку из файловой системы и выполнять работу по экспорту.

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, как и любой другой модуль node...

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

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

Ответ 3

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

Ответ 4

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

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

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

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

this.jQuery = jQuery;

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

Edit: Джеймс Хердман имеет хорошую запись о node.js для новичков, в которой также упоминается об этом.

Ответ 5

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

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

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

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

Ответ 6

Я смог заставить его работать, обновив свой скрипт, очень легко, просто добавив module.exports = где это необходимо...

Например, я взял файл, и я скопировал его в. /libs/apprise.js. Затем, где он начинается с

function apprise(string, args, callback){

Я назначил функцию module.exports = таким образом:

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

Таким образом, я могу импортировать библиотеку в свой код следующим образом:

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

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

Ответ 7

Простая функция 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(...).