Muat Pustaka Javascript "Vanilla" ke Node.js

108

Ada beberapa pustaka Javascript pihak ketiga yang memiliki beberapa fungsi yang ingin saya gunakan di server Node.js. (Secara khusus saya ingin menggunakan pustaka javascript QuadTree yang saya temukan.) Tetapi pustaka ini hanyalah .jsfile langsung dan bukan "pustaka Node.js".

Dengan demikian, pustaka ini tidak mengikuti exports.var_namesintaks yang diharapkan Node.js untuk modulnya. Sejauh yang saya mengerti itu berarti ketika Anda melakukannya module = require('module_name');atau module = require('./path/to/file.js');Anda akan berakhir dengan modul tanpa fungsi yang dapat diakses publik, dll.

Pertanyaan saya kemudian adalah "Bagaimana cara memuat file javascript arbitrer ke Node.js sehingga saya dapat memanfaatkan fungsinya tanpa harus menulis ulang sehingga berfungsi exports?"

Saya sangat baru di Node.js jadi tolong beri tahu saya jika ada beberapa lubang mencolok dalam pemahaman saya tentang cara kerjanya.


EDIT : Meneliti lebih banyak dan sekarang saya melihat bahwa pola pemuatan modul yang digunakan Node.js sebenarnya adalah bagian dari standar yang dikembangkan baru-baru ini untuk memuat pustaka Javascript yang disebut CommonJS . Ia mengatakan ini tepat di halaman dokumen modul untuk Node.js , tapi saya melewatkannya sampai sekarang.

Mungkin akhirnya jawaban atas pertanyaan saya adalah "tunggu sampai penulis perpustakaan Anda sempat menulis antarmuka CommonJS atau lakukan sendiri."

Chris W.
sumber
pertanyaan terkait: stackoverflow.com/questions/22898080/…
Josmar

Jawaban:

75

Ada metode yang jauh lebih baik daripada menggunakan eval: vmmodul.

Misalnya, berikut adalah execfilemodul saya , yang mengevaluasi skrip pathdi salah satu contextatau konteks 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;
}

Dan itu bisa digunakan seperti ini:

> 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

Dimana example.jsmengandung:

function getSomeGlobal() {
    return someGlobal;
}

Keuntungan besar dari metode ini adalah Anda memiliki kendali penuh atas variabel global dalam skrip yang dieksekusi: Anda dapat mengirimkan global kustom (melalui context), dan semua global yang dibuat oleh skrip akan ditambahkan context. Debugging juga lebih mudah karena kesalahan sintaks dan sejenisnya akan dilaporkan dengan nama file yang benar.

David Wolever
sumber
Apakah runInNewContextmenggunakan konteks global jika context(sebaliknya disebut sebagai sandbox, dalam dokumen) tidak ditentukan? (hal ini tidak dijelaskan oleh dokumen yang saya temukan)
Steven Lu
Tampaknya, untuk tujuan bermain dengan pustaka pihak ketiga yang mengabaikan Node atau pola CommonJS, metode eval Christopher < stackoverflow.com/a/9823294/1450294 > berfungsi dengan baik. Manfaat apa yang dapat vmditawarkan modul dalam kasus ini?
Michael Scheper
2
Lihat pembaruan saya untuk penjelasan mengapa metode ini lebih baik daripada eval.
David Wolever
1
ini benar-benar menakjubkan - ini memungkinkan saya untuk secara instan menggunakan kembali kode non-modul berbasis web saya untuk implementasi sisi server yang mengirimkan output melalui email [sesuai jadwal] alih-alih menampilkannya di halaman web. Semua kode web menggunakan pola modul augmentasi longgar dan injeksi skrip - jadi ini berfungsi dengan baik !!
Al Joslin
Bagaimana kita bisa menggunakan ini di Node.js jika example.js bergantung pada pustaka example1.js?
sytolk
80

Inilah yang menurut saya adalah jawaban yang 'paling benar' untuk situasi ini.

Katakanlah Anda memiliki file skrip bernama quadtree.js.

Anda harus membuat kustom node_moduleyang memiliki struktur direktori semacam ini ...

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

Semua yang ada di ./node_modules/quadtree/quadtree-lib/direktori Anda adalah file dari perpustakaan pihak ketiga Anda.

Kemudian ./node_modules/quadtree/index.jsfile Anda hanya akan memuat pustaka itu dari sistem file dan melakukan pekerjaan mengekspor dengan benar.

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

Sekarang Anda dapat menggunakan quadtreemodul Anda seperti modul node lainnya ...

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

Saya suka metode ini karena tidak perlu mengubah kode sumber apa pun dari perpustakaan pihak ketiga Anda - jadi lebih mudah untuk memeliharanya. Yang perlu Anda lakukan saat pemutakhiran adalah melihat kode sumbernya dan memastikan bahwa Anda masih mengekspor objek yang tepat.

Chris W.
sumber
3
Baru saja menemukan jawaban Anda (membuat game multipemain dan perlu menyertakan JigLibJS, mesin fisika kami, di server serta klien), Anda menghemat BANYAK waktu dan kerumitan. Terima kasih!
stevendesu
8
Jika Anda mengikuti ini dengan tepat, perlu diingat bahwa sangat mudah untuk secara tidak sengaja menghapus folder node_modules Anda menggunakan NPM, terutama jika Anda tidak memeriksanya ke SCM. Pertimbangkan untuk meletakkan pustaka QuadTree Anda di repositori terpisah, lalu npm linkmemasukkannya ke dalam aplikasi Anda. Kemudian ditangani seolah-olah itu adalah paket Node.js asli.
Btown
@btown, dapatkah Anda memperluas sedikit untuk pemula seperti saya apa yang dilakukan oleh tautan SCM dan npm yang mencegah potensi masalah yang Anda sebutkan?
Flion
Apakah ini benar-benar perlu jika saya hanya ingin memasukkan skrip?
Quantumpotato
1
@flion membalas komentar lama untuk orang lain ref karena saya yakin Anda akan tahu Anda menjawab sekarang. SCM - Manajemen Kontrol Sumber (misalnya GIT) dan tautan ke demo cepat tapi bagus dari tautan
npm
30

Cara paling sederhana adalah: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); Ini berfungsi dengan baik untuk pengujian di shell interaktif.

Christopher Weiss
sumber
1
Salut kawan! Banyak membantu
Schoening
Ini juga merupakan cara tercepat, dan terkadang yang Anda butuhkan adalah yang cepat dan kotor. Antara ini dan jawaban David, halaman SO ini adalah sumber yang bagus.
Michael Scheper
5

AFAIK, begitulah modul harus dimuat. Namun, alih-alih menggunakan semua fungsi yang diekspor ke exportsobjek, Anda juga dapat memakainya ke this(apa yang akan menjadi objek global).

Jadi, jika Anda ingin menjaga pustaka lain tetap kompatibel, Anda dapat melakukan ini:

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

atau, ketika perpustakaan eksternal sudah memiliki namespace sendiri, misalnya jQuery(bukan bahwa Anda dapat menggunakan yang dalam lingkungan server-side):

this.jQuery = jQuery;

Dalam lingkungan non-Node, thisakan menyelesaikan objek global, sehingga menjadikannya variabel global ... yang sudah ada. Jadi seharusnya tidak merusak apapun.

Sunting : James Herdman memiliki artikel bagus tentang node.js untuk pemula, yang juga menyebutkan ini.

Martijn
sumber
Trik 'ini' terdengar seperti cara yang baik untuk membuat segalanya lebih portabel sehingga perpustakaan Node.js dapat digunakan di luar Node.js, tetapi itu tetap berarti saya perlu mengubah secara manual perpustakaan javascript saya untuk mendukung Node.js memerlukan sintaksis .
Chris W.
@ ChrisW .: ya, Anda harus mengubah perpustakaan Anda secara manual. Secara pribadi, saya juga akan menyukai mekanisme kedua untuk menyertakan file eksternal, yang secara otomatis mengubah namespace global file yang disertakan ke namespace yang diimpor. Mungkin Anda dapat mengajukan RFE ke pengembang Node?
Martijn
3

Saya tidak yakin apakah saya akan benar-benar menggunakan ini karena ini adalah solusi yang agak hacky, tetapi salah satu cara mengatasinya adalah dengan membangun importir modul mini kecil seperti ini ...

Di dalam file ./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;
}

Kemudian ketika Anda ingin menggunakan fungsionalitas perpustakaan Anda, Anda harus memilih nama mana yang akan diekspor secara manual.

Jadi untuk perpustakaan seperti file ./lib/mylibrary.js...

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

Saat Anda ingin menggunakan fungsinya di kode 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)

Tidak tahu seberapa baik ini semua akan berhasil dalam praktiknya.

Chris W.
sumber
Hei, wow: jawaban yang tidak disukai (bukan oleh saya) dan jawaban yang disukai oleh pengguna yang sama untuk pertanyaan yang sama! Harus ada lencana untuk itu! ;-)
Michael Scheper
2

Saya dapat membuatnya berfungsi dengan memperbarui skrip mereka, dengan sangat mudah, hanya menambahkan module.exports =jika sesuai ...

Misalnya, saya mengambil file mereka dan saya menyalin ke './libs/apprise.js'. Lalu di mana itu dimulai

function apprise(string, args, callback){

Saya menetapkan fungsi sebagai module.exports =berikut:

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

Jadi saya dapat mengimpor pustaka ke dalam kode saya seperti ini:

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

Dan saya baik untuk pergi. YMMV, ini dengan webpack .

John Mee
sumber
0

include(filename)Fungsi sederhana dengan pesan kesalahan yang lebih baik (tumpukan, nama file, dll.) Untuk eval, jika terjadi kesalahan:

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

Tetapi bahkan menjadi lebih kotor dengan nodejs: Anda perlu menentukan ini:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

Jika tidak, Anda tidak dapat menggunakan variabel global dalam file yang disertakan dengan include(...).

lama12345
sumber