Załaduj biblioteki JavaScript „Vanilla” do Node.js.

108

Istnieją biblioteki JavaScript innych firm, które mają pewne funkcje, których chciałbym używać na serwerze Node.js. (W szczególności chcę użyć biblioteki JavaScript QuadTree, którą znalazłem). Ale te biblioteki to tylko proste .jspliki, a nie „biblioteki Node.js”.

W związku z tym te biblioteki nie są zgodne ze exports.var_nameskładnią, której Node.js oczekuje dla swoich modułów. O ile rozumiem, oznacza to, że kiedy to zrobisz, module = require('module_name');lub module = require('./path/to/file.js');skończysz z modułem bez publicznie dostępnych funkcji itp.

Moje pytanie brzmi zatem: „Jak załadować dowolny plik javascript do Node.js, aby móc wykorzystać jego funkcjonalność bez konieczności przepisywania go tak, aby działał exports?”

Jestem bardzo nowy w Node.js, więc daj mi znać, jeśli jest jakaś rażąca dziura w moim zrozumieniu, jak to działa.


EDYCJA : Badam więcej rzeczy i teraz widzę, że wzorzec ładowania modułów, którego używa Node.js, jest w rzeczywistości częścią niedawno opracowanego standardu ładowania bibliotek Javascript o nazwie CommonJS . Mówi to bezpośrednio na stronie dokumentacji modułu dla Node.js , ale do tej pory to przegapiłem.

Może się okazać, że odpowiedź na moje pytanie brzmi: „Poczekaj, aż autorzy z Twojej biblioteki zajmą się pisaniem interfejsu CommonJS, albo zrób to sam”.

Chris W.
źródło
powiązane pytanie: stackoverflow.com/questions/22898080/…
Josmar

Odpowiedzi:

75

Jest znacznie lepsza metoda niż użycie eval: vmmoduł.

Na przykład, oto mój execfilemoduł, który ocenia skrypt pathw dowolnym contextlub globalnym kontekście:

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

Można go używać w ten sposób:

> 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

Gdzie example.jszawiera:

function getSomeGlobal() {
    return someGlobal;
}

Dużą zaletą tej metody jest to, że masz pełną kontrolę nad zmiennymi globalnymi w wykonywanym skrypcie: możesz przekazać niestandardowe wartości globalne (via context), do których zostaną dodane wszystkie wartości globalne utworzone przez skrypt context. Debugowanie jest również łatwiejsze, ponieważ błędy składniowe i tym podobne będą zgłaszane z poprawną nazwą pliku.

David Wolever
źródło
Czy runInNewContextużywa kontekstu globalnego, jeśli context(w innych przypadkach określany jako sandbox, w dokumentach) jest niezdefiniowany? (ten punkt nie został wyjaśniony przez żaden dokument, który znalazłem)
Steven Lu
Wydaje się, że w celu zabawy z biblioteką innej firmy, która nie zna Node lub wzorca CommonJS, metoda eval Christophera < stackoverflow.com/a/9823294/1450294 > działa dobrze. Jakie korzyści może vmzaoferować moduł w tym przypadku?
Michael Scheper
2
Zobacz moje aktualizacje, aby dowiedzieć się, dlaczego ta metoda jest lepsza niż eval.
David Wolever
1
to totalnie rządzi - pozwoliło mi natychmiastowo ponownie wykorzystać mój internetowy kod niebędący modułem do implementacji po stronie serwera, który wysyła dane wyjściowe [zgodnie z harmonogramem] zamiast wyświetlać je na stronie internetowej. Cały kod sieciowy wykorzystywał luźno rozszerzający się wzorzec modułu i wstrzyknięcie skryptu - więc to działa tak dobrze !!
Al Joslin
Jak możemy tego użyć w Node.js, jeśli przykład.js zależy od biblioteki example1.js?
sytolk
80

Oto, co uważam za „najlepszą” odpowiedź na tę sytuację.

Powiedzmy, że masz plik skryptu o nazwie quadtree.js.

Powinieneś zbudować niestandardowy, node_modulektóry ma taką strukturę katalogów ...

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

Wszystko w Twoim ./node_modules/quadtree/quadtree-lib/katalogu to pliki z biblioteki innej firmy.

Wtedy twój ./node_modules/quadtree/index.jsplik po prostu załaduje tę bibliotekę z systemu plików i wykona pracę eksportu rzeczy poprawnie.

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

Teraz możesz używać swojego quadtreemodułu jak każdego innego modułu węzłowego ...

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

Podoba mi się ta metoda, ponieważ nie ma potrzeby zmieniać żadnego kodu źródłowego biblioteki innej firmy - więc jest łatwiejsza w utrzymaniu. Wszystko, co musisz zrobić po aktualizacji, to spojrzeć na ich kod źródłowy i upewnić się, że nadal eksportujesz odpowiednie obiekty.

Chris W.
źródło
3
Właśnie znalazłem odpowiedź (tworzenie gry wieloosobowej i wymaganie włączenia JigLibJS, naszego silnika fizycznego, na serwerze, a także w kliencie), zaoszczędziłeś mi dużo czasu i kłopotów. Dziękuję Ci!
stevendesu
8
Jeśli postępujesz dokładnie według tego, pamiętaj, że dość łatwo jest przypadkowo zniszczyć folder node_modules za pomocą NPM, zwłaszcza jeśli nie sprawdzisz go w SCM. Zdecydowanie rozważ umieszczenie swojej biblioteki QuadTree w oddzielnym repozytorium, a następnie umieszczenie npm linkjej w aplikacji. Następnie jest obsługiwany tak, jakby był natywnym pakietem Node.js.
btown
@btown, czy mógłbyś trochę rozwinąć dla początkujących, takich jak ja, co dokładnie robi łącze SCM i npm, co zapobiega potencjalnemu problemowi, o którym wspomniałeś?
Flion
Czy to naprawdę konieczne, jeśli chcę tylko dołączyć skrypt?
quantumpotato
1
@flion odpowiada na stary komentarz dla innych, ponieważ jestem pewien, że już wiesz, że odpowiadasz. SCM - Source Control Management (np. GIT) i link do szybkiego, ale dobrego demo linku npm
delp
30

Najprostszy sposób: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); To świetnie sprawdza się podczas testowania w powłoce interaktywnej.

Christophera Weissa
źródło
1
Zdrowie przyjacielu! Bardzo pomogło
Schoening
Jest to także najszybszy sposób, a czasami jest to, czego potrzebujesz. Pomiędzy tym a odpowiedzią Davida, ta strona SO jest doskonałym źródłem informacji.
Michael Scheper
5

AFAIK, tak właśnie należy ładować moduły. Jednak zamiast dołączać wszystkie wyeksportowane funkcje do exportsobiektu, możesz również przypisać je this(co w przeciwnym razie byłoby obiektem globalnym).

Jeśli więc chcesz zachować zgodność innych bibliotek, możesz to zrobić:

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

lub, gdy biblioteka zewnętrzny ma już swoją własną przestrzeń nazw, na przykład jQuery(nie, że można użyć , że w środowisku po stronie serwera):

this.jQuery = jQuery;

W środowisku innym niż węzeł, thisrozwiązywałoby się do obiektu globalnego, czyniąc go w ten sposób zmienną globalną ... którą już był. Więc to nie powinno niczego zepsuć.

Edycja : James Herdman ma fajny napis o node.js dla początkujących, który również o tym wspomina.

Martijn
źródło
Sztuczka `` ta '' brzmi jak dobry sposób na uczynienie rzeczy bardziej przenośnymi, tak aby biblioteki Node.js mogły być używane poza Node.js, ale nadal oznacza, że ​​muszę ręcznie zmienić moje biblioteki javascript, aby obsługiwały Node.js wymagały składni .
Chris W.
@ChrisW .: tak, będziesz musiał ręcznie zmienić swoje biblioteki. Osobiście wolałbym również mieć drugi mechanizm do dołączania plików zewnętrznych, który automatycznie konwertuje globalną przestrzeń nazw dołączonego pliku na importowaną przestrzeń nazw. Może mógłbyś przesłać RFE do programistów Node?
Martijn
3

Nie jestem pewien, czy faktycznie skorzystam z tego, ponieważ jest to raczej hakerskie rozwiązanie, ale jednym ze sposobów jest zbudowanie małego importera mini-modułów, takiego jak ten ...

W pliku ./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;
}

Jeśli chcesz skorzystać z funkcji swojej biblioteki, musisz ręcznie wybrać nazwy do wyeksportowania.

Więc dla biblioteki takiej jak plik ./lib/mylibrary.js...

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

Jeśli chcesz wykorzystać jego funkcjonalność w swoim kodzie 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)

Nie wiem jednak, jak dobrze to wszystko zadziała w praktyce.

Chris W.
źródło
Hej, wow: odpowiedź głosowana w dół (nie przeze mnie) i w górę przez tego samego użytkownika na to samo pytanie! Powinna być za to odznaka! ;-)
Michael Scheper
2

Udało mi się to zrobić, bardzo łatwo aktualizując ich skrypt, po prostu dodając module.exports =tam, gdzie było to konieczne ...

Na przykład wziąłem ich plik i skopiowałem do „./libs/apprise.js”. Wtedy od czego się zaczyna

function apprise(string, args, callback){

W ten sposób przypisałem funkcję module.exports =:

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

W ten sposób mogę zaimportować bibliotekę do mojego kodu w następujący sposób:

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

I byłem gotowy. YMMV, to było z pakietem internetowym .

John Mee
źródło
0

Prosta include(filename)funkcja z lepszym komunikowaniem się o błędach (stos, nazwa pliku itp.) evalW przypadku błędów:

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

Ale z nodejs jest nawet brudniej: musisz to określić:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

W przeciwnym razie nie można używać zmiennych globalnych w plikach dołączonych do include(...).

lama12345
źródło