โหลด Javascript Libraries“ Vanilla” ลงใน Node.js


108

มีไลบรารี Javascript ของบุคคลที่สามที่มีฟังก์ชันบางอย่างที่ฉันต้องการใช้ในเซิร์ฟเวอร์ Node.js (โดยเฉพาะฉันต้องการใช้ไลบรารี QuadTree javascript ที่ฉันพบ) แต่ไลบรารีเหล่านี้เป็นเพียง.jsไฟล์ที่ตรงไปตรงมาไม่ใช่ "ไลบรารี Node.js"

ด้วยเหตุนี้ไลบรารีเหล่านี้จึงไม่เป็นไปตาม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 หรือทำด้วยตัวคุณเอง"


คำถามที่เกี่ยวข้อง: 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;
}

ประโยชน์ใหญ่ของวิธีนี้คือการที่คุณได้มีการควบคุมอย่างสมบูรณ์ตัวแปรระดับโลกในการดำเนินการสคริปต์นี้คุณสามารถส่งผ่านใน Globals ที่กำหนดเอง (ผ่านcontext) และ Globals contextทั้งหมดที่สร้างขึ้นโดยสคริปต์ที่จะถูกเพิ่มเข้าไป การดีบักยังทำได้ง่ายกว่าเนื่องจากข้อผิดพลาดทางไวยากรณ์และสิ่งที่คล้ายกันจะถูกรายงานด้วยชื่อไฟล์ที่ถูกต้อง


ไม่runInNewContextใช้บริบทของโลกถ้าcontext(มิฉะนั้นจะเรียกเป็นsandboxในเอกสาร) จะไม่ได้กำหนด? (เอกสารใด ๆ ที่ฉันพบไม่ชัดเจน)
Steven Lu

ดูเหมือนว่าเพื่อจุดประสงค์ในการเล่นกับไลบรารีของบุคคลที่สามที่ไม่รู้ Node หรือรูปแบบ CommonJS วิธีการ eval < stackoverflow.com/a/9823294/1450294 > ของ Christopher จะทำงานได้ดี vmโมดูลสามารถให้ประโยชน์อะไรได้บ้างในกรณีนี้?
Michael Scheper

2
ดูการอัปเดตของฉันสำหรับคำอธิบายว่าเหตุใดวิธีนี้จึงดีกว่า eval
David Wolever

1
นี้โดยสิ้นเชิงหิน - มันอนุญาตให้ฉันไปทันทีกลับมาใช้รหัสที่ไม่ใช่โมดูล web-based ของฉันสำหรับการดำเนินงานด้านเซิร์ฟเวอร์ที่อีเมลที่เอาท์พุท [ในตาราง] แทนการแสดงพวกเขาบนหน้าเว็บ โค้ดเว็บทั้งหมดใช้รูปแบบโมดูลการขยายและการแทรกสคริปต์แบบหลวม ๆ - ดังนั้นจึงใช้ได้ดี !!
Al Joslin

เราจะใช้สิ่งนี้ใน 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 ทำอะไรได้บ้างเพื่อป้องกันปัญหาที่คุณพูดถึง
Flion

นี่เป็นสิ่งจำเป็นจริงๆหรือไม่ถ้าฉันแค่ต้องการรวมสคริปต์
quantumpotato

1
@flion ตอบกลับความคิดเห็นเก่าสำหรับคนอื่น ๆ เพราะฉันแน่ใจว่าคุณจะรู้ว่าคุณตอบในตอนนี้ SCM - การจัดการการควบคุมแหล่งที่มา (เช่น GIT) และลิงก์ไปยังการสาธิตลิงก์ npm ที่
delp

30

วิธีที่ง่ายที่สุดคือวิธีeval(require('fs').readFileSync('./path/to/file.js', 'utf8')); นี้เหมาะสำหรับการทดสอบในเชลล์แบบโต้ตอบ


1
ไชโยเมท! ช่วยได้มาก
Schoening

วิธีนี้เป็นวิธีที่เร็วที่สุดและบางครั้งก็รวดเร็วและสกปรกเป็นสิ่งที่คุณต้องการ ระหว่างสิ่งนี้กับคำตอบของเดวิดหน้า SO นี้เป็นแหล่งข้อมูลที่ยอดเยี่ยม
Michael Scheper

5

AFAIK นั่นคือวิธีที่ต้องโหลดโมดูล อย่างไรก็ตามแทนที่จะยึดฟังก์ชั่นที่ส่งออกทั้งหมดลงบนexportsวัตถุคุณยังสามารถตรึงฟังก์ชั่นเหล่านี้ไว้บนวัตถุthis(สิ่งที่จะเป็นวัตถุส่วนกลาง)

ดังนั้นหากคุณต้องการให้ไลบรารีอื่นเข้ากันได้คุณสามารถทำได้:

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

หรือเมื่อห้องสมุดภายนอกแล้วมี namespace ของตัวเองเช่นjQuery(ไม่ได้หมายความว่าคุณสามารถใช้ที่อยู่ในสภาพแวดล้อมด้านเซิร์ฟเวอร์):

this.jQuery = jQuery;

ในสภาพแวดล้อมที่ไม่ใช่โหนดthisจะแก้ไขให้กับวัตถุส่วนกลางดังนั้นจึงทำให้เป็นตัวแปรส่วนกลาง ... ซึ่งเป็นไปแล้ว จึงไม่น่าจะผิดอะไร

แก้ไข : James Herdman มีบทความที่ดีเกี่ยวกับ node.js สำหรับผู้เริ่มต้นซึ่งกล่าวถึงสิ่งนี้ด้วย


เคล็ดลับ 'this' ดูเหมือนจะเป็นวิธีที่ดีในการทำให้สิ่งต่าง ๆ พกพาได้มากขึ้นเพื่อให้ไลบรารี Node.js สามารถใช้นอก Node.js ได้ แต่ก็ยังหมายความว่าฉันต้องเปลี่ยนไลบรารี javascript ด้วยตนเองเพื่อรองรับ Node.js จำเป็นต้องมีไวยากรณ์ .
Chris W.

@ChrisW: ใช่คุณจะต้องเปลี่ยนไลบรารีของคุณด้วยตนเอง โดยส่วนตัวฉันยังชอบกลไกที่สองในการรวมไฟล์ภายนอกซึ่งจะแปลงเนมสเปซส่วนกลางของไฟล์ที่รวมเป็นเนมสเปซที่นำเข้าโดยอัตโนมัติ บางทีคุณอาจยื่น RFE ไปยังนักพัฒนาโหนดได้
Martijn

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)

ไม่รู้ว่าทั้งหมดนี้จะใช้ได้ผลดีแค่ไหนในทางปฏิบัติ


เฮ้ว้าว: โหวตไม่ลง (ไม่ใช่โดยฉัน) และคำตอบที่โหวตโดยผู้ใช้คนเดียวกันสำหรับคำถามเดียวกัน! ควรมีตราสำหรับสิ่งนั้น! ;-)
Michael Scheper

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(...).

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.