Tải Thư viện Javascript “Vanilla” vào Node.js


108

Có một số thư viện Javascript của bên thứ ba có một số chức năng mà tôi muốn sử dụng trong máy chủ Node.js. (Cụ thể là tôi muốn sử dụng thư viện javascript QuadTree mà tôi đã tìm thấy.) Nhưng những thư viện này chỉ là .jscác tệp đơn giản và không phải "thư viện Node.js".

Do đó, các thư viện này không tuân theo exports.var_namecú pháp mà Node.js mong đợi cho các mô-đun của nó. Theo như tôi hiểu, điều đó có nghĩa là khi bạn làm module = require('module_name');hoặc module = require('./path/to/file.js');bạn sẽ kết thúc với một mô-đun không có chức năng truy cập công khai, v.v.

Câu hỏi của tôi sau đó là "Làm cách nào để tải một tệp javascript tùy ý vào Node.js để tôi có thể sử dụng chức năng của nó mà không cần phải viết lại để nó hoạt động exports?"

Tôi là người mới sử dụng Node.js, vì vậy hãy cho tôi biết nếu có một số lỗ hổng trong hiểu biết của tôi về cách nó hoạt động.


CHỈNH SỬA : Nghiên cứu kỹ hơn về mọi thứ và bây giờ tôi thấy rằng mẫu tải mô-đun mà Node.js sử dụng thực sự là một phần của tiêu chuẩn được phát triển gần đây để tải các thư viện Javascript được gọi là CommonJS . Nó nói điều này ngay trên trang tài liệu mô-đun cho Node.js , nhưng tôi đã bỏ lỡ điều đó cho đến bây giờ.

Có thể cuối cùng câu trả lời cho câu hỏi của tôi là "hãy đợi cho đến khi các tác giả trong thư viện của bạn bắt đầu viết giao diện CommonJS hoặc tự làm điều đó."


Câu trả lời:


75

Có một phương pháp tốt hơn nhiều so với việc sử dụng eval: vmmô-đun.

Ví dụ: đây là execfilemô-đun của tôi , mô-đun này đánh giá tập lệnh tại pathmột trong hai contexthoặc trong ngữ cảnh chung:

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

Và nó có thể được sử dụng như thế này:

> 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

Nơi example.jschứa:

function getSomeGlobal() {
    return someGlobal;
}

Ưu điểm lớn của phương pháp này là bạn có toàn quyền kiểm soát các biến toàn cục trong tập lệnh được thực thi: bạn có thể chuyển vào các toàn cầu tùy chỉnh (qua context) và tất cả các toàn cầu được tạo bởi tập lệnh sẽ được thêm vào context. Gỡ lỗi cũng dễ dàng hơn vì các lỗi cú pháp và những thứ tương tự sẽ được thông báo với tên tệp chính xác.


Không runInNewContextsử dụng bối cảnh toàn cầu nếu context(nếu không gọi là sandbox, trong tài liệu) là undefined? (thời điểm này đã không được thực hiện rõ ràng bởi bất kỳ tài liệu tôi tìm thấy)
Steven Lu

Có vẻ như, với mục đích chơi với thư viện của bên thứ ba không biết Node hoặc mẫu CommonJS, phương pháp eval của Christopher < stackoverflow.com/a/9823294/1450294 > hoạt động tốt. vmMô-đun có thể cung cấp những lợi ích gì trong trường hợp này?
Michael Scheper

2
Xem các cập nhật của tôi để biết mô tả tại sao phương pháp này tốt hơn eval.
David Wolever

1
điều này hoàn toàn tuyệt vời - nó cho phép tôi sử dụng lại ngay lập tức mã không phải mô-đun dựa trên web của mình để triển khai phía máy chủ gửi email kết quả đầu ra [theo lịch trình] thay vì hiển thị chúng trên trang web. Tất cả mã web đều sử dụng mô-đun tăng cường lỏng lẻo và chèn tập lệnh - vì vậy điều này hoạt động rất tốt !!
Al Joslin

Làm thế nào chúng ta có thể sử dụng điều này trong Node.js nếu example.js phụ thuộc vào thư viện example1.js?
sytolk

80

Đây là những gì tôi nghĩ là câu trả lời 'đúng nhất' cho tình huống này.

Giả sử bạn có một tệp kịch bản được gọi là quadtree.js.

Bạn nên xây dựng một tùy chỉnh node_modulecó cấu trúc thư mục kiểu này ...

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

Mọi thứ trong ./node_modules/quadtree/quadtree-lib/thư mục của bạn là các tệp từ thư viện bên thứ 3 của bạn.

Sau đó, ./node_modules/quadtree/index.jstệp của bạn sẽ chỉ tải thư viện đó từ hệ thống tệp và thực hiện công việc xuất mọi thứ đúng cách.

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

Bây giờ bạn có thể sử dụng quadtreemô-đun của mình giống như bất kỳ mô-đun nút nào khác ...

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

Tôi thích phương pháp này vì không cần phải thay đổi bất kỳ mã nguồn nào của thư viện bên thứ 3 của bạn - vì vậy nó dễ bảo trì hơn. Tất cả những gì bạn cần làm khi nâng cấp là xem mã nguồn của chúng và đảm bảo rằng bạn vẫn đang xuất các đối tượng thích hợp.


3
Chỉ cần tìm thấy câu trả lời của bạn (tạo một trò chơi nhiều người chơi và cần bao gồm JigLibJS, công cụ vật lý của chúng tôi, trên máy chủ cũng như máy khách), bạn đã tiết kiệm cho tôi rất nhiều thời gian và rắc rối. Cảm ơn bạn!
stevendesu

8
Nếu bạn làm đúng theo điều này, hãy nhớ rằng rất dễ vô tình xóa thư mục node_modules của bạn bằng cách sử dụng NPM, đặc biệt nếu bạn không kiểm tra nó trong SCM. Chắc chắn hãy cân nhắc việc đưa thư viện QuadTree của bạn vào một kho lưu trữ riêng biệt, sau đó nhập npm linknó vào ứng dụng của bạn. Sau đó, nó được xử lý như thể nó là một gói Node.js gốc.
btown

@btown, bạn có thể mở rộng một chút cho những người mới như tôi, liên kết SCM và npm làm gì chính xác để ngăn chặn vấn đề tiềm ẩn mà bạn đề cập không?
Flion

Điều này có thực sự cần thiết nếu tôi chỉ muốn bao gồm một tập lệnh?
quantumpotato

1
@flion trả lời nhận xét cũ cho những người khác tham khảo vì tôi chắc rằng bạn sẽ biết bạn trả lời ngay bây giờ. SCM - Quản lý kiểm soát nguồn (ví dụ: GIT) và liên kết đến bản giới thiệu nhanh nhưng tốt về liên kết
npm

30

Cách đơn giản nhất là: eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); Điều này hoạt động tốt để thử nghiệm trong trình bao tương tác.


1
Cổ vũ người bạn đời! Được giúp đỡ rất nhiều
Schoening vào

Đây cũng là cách nhanh nhất, và đôi khi nhanh và bẩn là những gì bạn cần. Giữa câu trả lời này và câu trả lời của David, trang SO này là một nguồn tài liệu tuyệt vời.
Michael Scheper

5

AFAIK, đó thực sự là cách các mô-đun phải được tải. Tuy nhiên, thay vì xếp tất cả các hàm đã xuất vào exportsđối tượng, bạn cũng có thể gắn chúng vào this(nếu không sẽ là đối tượng toàn cục).

Vì vậy, nếu bạn muốn giữ cho các thư viện khác tương thích, bạn có thể làm như sau:

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

hoặc, khi thư viện bên ngoài đã có không gian tên riêng, ví dụ: jQuery(không phải bạn có thể sử dụng không gian đó trong môi trường phía máy chủ):

this.jQuery = jQuery;

Trong môi trường không phải Node, thissẽ phân giải thành đối tượng toàn cục, do đó biến nó thành một biến toàn cục ... mà nó đã có. Vì vậy, nó không nên phá vỡ bất cứ điều gì.

Chỉnh sửa : James Herdman có một bài viết hay về node.js cho người mới bắt đầu, cũng đề cập đến vấn đề này.


Thủ thuật 'này' nghe có vẻ là một cách hay để làm cho mọi thứ trở nên linh hoạt hơn để các thư viện Node.js có thể được sử dụng bên ngoài Node.js, nhưng nó vẫn có nghĩa là tôi cần phải thay đổi thủ công các thư viện javascript của mình để hỗ trợ cú pháp yêu cầu Node.js .
Chris W. Ngày

@ChrisW .: vâng, bạn sẽ phải thay đổi các thư viện của mình theo cách thủ công. Cá nhân tôi cũng muốn có một cơ chế thứ hai để bao gồm các tệp bên ngoài, một cơ chế tự động chuyển đổi không gian tên chung của tệp được bao gồm thành không gian tên đã nhập. Có lẽ bạn có thể gửi một RFE cho các nhà phát triển Node?
Martijn

3

Tôi không chắc liệu mình có thực sự sử dụng nó hay không vì nó là một giải pháp khá hack, nhưng có một cách để giải quyết vấn đề này là xây dựng một trình nhập mô-đun nhỏ như thế này ...

Trong tệp ./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;
}

Sau đó, khi bạn muốn sử dụng chức năng của thư viện, bạn sẽ cần chọn thủ công những tên cần xuất.

Vì vậy, đối với một thư viện như tệp ./lib/mylibrary.js...

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

Khi bạn muốn sử dụng chức năng của nó trong mã Node.js của mình ...

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)

Tuy nhiên, không biết tất cả điều này sẽ hoạt động tốt như thế nào trong thực tế.


Này, wow: một câu trả lời được bình chọn thấp (không phải bởi tôi) và câu trả lời được bình chọn bởi cùng một người dùng cho cùng một câu hỏi! Nên có một huy hiệu cho điều đó! ;-)
Michael Scheper

2

Tôi đã có thể làm cho nó hoạt động bằng cách cập nhật tập lệnh của họ, rất dễ dàng, chỉ cần thêm vào module.exports =nơi thích hợp ...

Ví dụ: tôi lấy tệp của họ và tôi sao chép vào './libs/apprise.js'. Sau đó, nơi nó bắt đầu với

function apprise(string, args, callback){

Tôi đã gán chức năng cho module.exports =như vậy:

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

Vì vậy, tôi có thể nhập thư viện vào mã của mình như sau:

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

Và tôi đã rất tốt để đi. YMMV, đây là với webpack .


0

Một include(filename)chức năng đơn giản với thông báo lỗi tốt hơn (ngăn xếp, tên tệp, v.v.) eval, trong trường hợp có lỗi:

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

Nhưng nó thậm chí còn bẩn hơn với nodejs: bạn cần xác định điều này:

export NODE_MODULE_CONTEXTS=1
nodejs tmp.js

Nếu không, bạn không thể sử dụng các biến toàn cục trong các tệp được bao gồm include(...).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.