Завантажте бібліотеки JavaScript "Vanilla" у Node.js


108

Є деякі сторонні бібліотеки Javascript, які мають певну функціональність, яку я хотів би використовувати на сервері Node.js. (Зокрема, я хочу використовувати бібліотеку JavaScript QuadTree, яку я знайшов.) Але ці бібліотеки - це просто прості .jsфайли, а не "бібліотеки Node.js".

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

Моє запитання тоді: "Як я завантажую довільний файл JavaScript у Node.js таким чином, щоб я міг використовувати його функціональність, не потребуючи переписання, щоб це зробити exports?"

Я дуже новачок у Node.js, тому, будь ласка, повідомте мене, чи є якась кричуща дірка в моєму розумінні того, як це працює.


EDIT : Досліджую все більше, і тепер я бачу, що модель завантаження модулів, яку використовує Node.js, насправді є частиною нещодавно розробленого стандарту для завантаження бібліотек Javascript під назвою CommonJS . Це написано прямо на сторінці doc модуля для Node.js , але я пропустив це досі.

Зрештою, відповідь на моє запитання - "зачекайте, поки автори вашої бібліотеки обійдуться, щоб написати інтерфейс CommonJS або зроби це твій проклятий".


Пов'язаний з цим питання: stackoverflow.com/questions/22898080 / ...
Josmar

Відповіді:


75

Існує набагато кращий метод, ніж використання eval: 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. Налагодження також простіше, оскільки помилки в синтаксисі тощо можуть надходити з правильним ім'ям файлу.


Чи runInNewContextвикористовує глобальний контекст, якщо context(інакше це називається sandboxв документах) не визначено? (цей пункт не з'ясувався жодними знайденими нами документами)
Стівен Лу

Здається, що для гри з третьою бібліотекою, незнаючою Node або шаблоном CommonJS, метод eval Крістофера < stackoverflow.com/a/9823294/1450294 > працює добре. Які переваги може запропонувати vmмодуль у цьому випадку?
Майкл Шепер

2
Дивіться мої оновлення для опису того, чому цей метод кращий за eval.
Девід Уолвер

1
це повністю змінюється - це дозволило мені миттєво повторно використовувати свій веб-немодульний код для реалізації на стороні сервера, який надсилає електронні листи [за графіком], а не відображати їх на веб-сторінці. Весь веб-код використовував шаблон модуля вільного збільшення та введення сценарію - так це працює так чудово !!
Аль Джослін

Як ми можемо використовувати це в Node.js, якщо example.js залежить від бібліотеки example1.js?
sytolk

80

Ось що, на мою думку, є найправеднішою відповіддю в цій ситуації.

Скажімо, у вас є файл сценарію під назвою 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модуль, як і будь-який інший модуль вузла ...

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

Мені подобається цей метод, тому що не потрібно змінювати жоден вихідний код вашої сторонньої бібліотеки - так це легше підтримувати. Все, що вам потрібно зробити при оновленні - це переглянути їх вихідний код і переконатися, що ви все ще експортуєте належні об’єкти.


3
Щойно знайшов свою відповідь (створивши багатокористувацьку гру і потрібно було включити JigLibJS, наш фізичний двигун, на сервері, а також клієнта), ви заощадили мені багато часу та клопоту. Дякую!
stevendesu

8
Якщо ви точно дотримуєтесь цього, майте на увазі, що випадково видалити папку node_modules за допомогою NPM досить просто, особливо якщо ви не перевірите її в SCM. Однозначно подумайте про те, щоб помістити свою QuadTree бібліотеку в окремий сховище, а потім передати npm linkїї у вашу програму. Тоді обробляється так, ніби це був рідний пакет Node.js.
btown

@btown, не могли б ви трохи розширити для новачків, таких як я, що саме SCM та npm посилання роблять саме так, що запобігає потенційній проблемі, яку ви згадуєте?
Фліон

Це справді не потрібно, якщо я просто хочу включити сценарій?
квантова тота

1
@flion, відповідаючи на старий коментар для інших ref, тому що я впевнений, що ви дізнаєтесь, що відповісти до цього часу. SCM - управління управлінням
джерелами

30

Найпростіший спосіб: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); це чудово підходить для тестування в інтерактивній оболонці.


1
Будьмо, друже! Допомагав багато
Шонінг

Це також найшвидший спосіб, а іноді швидко і брудно - це те, що потрібно. Між цим і відповіддю Девіда ця сторінка ТА є чудовим ресурсом.
Майкл Шепер

5

AFAIK, саме так потрібно завантажувати модулі. Однак замість прив’язки всіх експортованих функцій до exportsоб'єкта, ви також можете застосувати їх this(що інакше було б глобальним об'єктом).

Отже, якщо ви хочете зберегти сумісність інших бібліотек, ви можете зробити це:

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

або, коли зовнішня бібліотека вже має свій власний простір імен, наприклад , jQuery(НЕ то, що ви можете використовувати , що в серверному середовищі):

this.jQuery = jQuery;

У середовищі, thisщо не є Вузлом, вирішив би отримати глобальний об'єкт, таким чином зробивши його глобальною змінною ... якою вона вже була. Тож це нічого не повинно ламати.

Редагувати : Джеймс Гердман має хороший запис про node.js для початківців, який також згадує про це.


Трюк "цей" звучить як хороший спосіб зробити речі більш портативними, щоб бібліотеки Node.js можна було використовувати поза межами Node.js, але це все ще означає, що мені потрібно вручну змінити свої бібліотеки javascript, щоб підтримати синтаксис Node.js .
Кріс В.

@ChrisW .: так, вам доведеться вручну змінювати свої бібліотеки. Особисто мені також сподобався б другий механізм включення зовнішніх файлів, який автоматично перетворив глобальний простір імен включеного файлу в імпортовану область імен. Можливо, ви могли б подати RFE розробникам Node?
Мартийн

3

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

У файлі ./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)

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


Гей, ух: недооцінений (не мною) та відповідь, що повернувся тим самим користувачем того ж користувача на те саме питання! Для цього має бути знак! ;-)
Майкл Шепер,

2

Мені вдалося змусити його працювати, оновивши їхній сценарій, дуже легко, просто додавши 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 .


0

Проста 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(...).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.