"Vanilla"자바 스크립트 라이브러리를 Node.js에로드


108

Node.js 서버에서 사용하고 싶은 일부 기능이있는 타사 Javascript 라이브러리가 있습니다. (특히 내가 찾은 QuadTree javascript 라이브러리를 사용하고 싶습니다.) 그러나 이러한 라이브러리는 .js"Node.js 라이브러리"가 아닌 단순한 파일입니다.

따라서 이러한 라이브러리는 exports.var_nameNode.js가 모듈에 대해 기대 하는 구문을 따르지 않습니다 . 지금까지 내가 그 방법을 이해 당신이 할 때, module = require('module_name');또는 module = require('./path/to/file.js');당신은 등 더 공개적으로 액세스 할 수있는 기능을 가진 모듈을하게 될 겁니다

내 질문은 "임의의 자바 스크립트 파일을 Node.js에로드하여 다시 작성하지 않고도 기능을 활용할 수 있도록하는 방법은 무엇 exports입니까?"입니다.

저는 Node.js를 처음 사용하므로 작동 방식을 이해하는 데 눈에 띄는 구멍이 있는지 알려주십시오.


편집 : 더 많은 것을 연구하고 이제 Node.js가 사용하는 모듈 로딩 패턴이 실제로 CommonJS 라는 Javascript 라이브러리를로드하기 위해 최근 개발 된 표준의 일부임을 알았습니다 . Node.js모듈 문서 페이지 에이 내용이 나와 있지만 지금까지 놓쳤습니다.

내 질문에 대한 대답은 "도서관의 작성자가 CommonJS 인터페이스를 작성하거나 직접 수행 할 때까지 기다리십시오"라는 것입니다.


답변:


75

사용하는 것보다 훨씬 더 나은 방법이있다 eval: vm모듈.

예를 들어 다음은 전역 컨텍스트 또는 전역 컨텍스트 에서 execfile스크립트를 평가하는 내 모듈입니다 .pathcontext

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경우 전역 컨텍스트를 사용 합니까 ? (이 점은 어떤 문서에 의해 명확하게하지 않은 내가 발견)contextsandbox
스티븐 루에게

Node 또는 CommonJS 패턴을 모르는 타사 라이브러리를 사용하기 위해 Christopher의 eval 메서드 < stackoverflow.com/a/9823294/1450294 >가 잘 작동하는 것 같습니다. vm이 경우 모듈이 제공 할 수있는 이점은 무엇입니까 ?
Michael Scheper 2015

2
이 방법이 eval보다 나은 이유에 대한 설명은 내 업데이트를 참조하십시오.
David Wolever 2015

1
이것은 완전히 흔들립니다. 웹 페이지에 표시하는 대신 [일정에 따라] 출력을 이메일로 보내는 서버 측 구현을 위해 웹 기반 비 모듈 코드를 즉시 재사용 할 수있었습니다. 모든 웹 코드는 느슨하게 증가하는 모듈 패턴과 스크립트 삽입을 사용했습니다.
Al Joslin 2015

example.js가 example1.js 라이브러리에 의존하는 경우 Node.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
이것을 정확히 따르면, 특히 SCM에 체크인하지 않는 경우 NPM을 사용하여 node_modules 폴더를 실수로 지우는 것이 매우 쉽습니다. QuadTree 라이브러리를 별도의 리포지토리에 넣은 다음 npm link응용 프로그램에 넣는 것이 좋습니다. 그런 다음 네이티브 Node.js 패키지 인 것처럼 처리됩니다.
btown

@btown, 저와 같은 초보자를 위해 SCM 및 npm 링크가 정확히 어떤 작업을 수행하여 언급 한 잠재적 문제를 방지 할 수 있습니까?
Flion

스크립트를 포함하고 싶다면 정말 필요한가요?
quantumpotato 2015 년

1
@flion이 다른 사람의 이전 댓글에 답장하는 중이므로 지금까지 답을 알 수있을 것입니다.
SCM-

30

가장 간단한 방법은 다음과 같습니다 eval(require('fs').readFileSync('./path/to/file.js', 'utf8')); . 대화 형 셸에서 테스트 할 때 유용합니다.


1
힘내 친구 야! 많은 도움이
Schoening

이것은 또한 가장 빠른 방법이며 때로는 빠르고 더러운 것이 필요합니다. 이것과 David의 대답 사이 에서이 SO 페이지는 훌륭한 리소스입니다.
마이클 Scheper

5

AFAIK, 실제로 모듈을로드해야하는 방법입니다. 그러나 내 보낸 모든 함수를 exports개체 에 고정하는 대신 에 this(그렇지 않으면 전역 개체가 될 수있는) 추가 할 수도 있습니다.

따라서 다른 라이브러리를 호환되도록 유지하려면 다음을 수행 할 수 있습니다.

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

또는, 외부 라이브러리가 이미 자신의 네임 스페이스, 예를 들어이있는 경우 jQuery(없는 당신이 사용할 수있는 것을 서버 측 환경에서)

this.jQuery = jQuery;

비 노드 환경 this에서 전역 개체로 확인되어 이미있는 전역 변수가됩니다. 그래서 그것은 아무것도 깨지 않아야합니다.

편집 : James Herdman은 초보자를위한 node.js에 대한 멋진 글을 가지고 있으며 이것도 언급합니다.


'this'트릭은 Node.js 라이브러리를 Node.js 외부에서 사용할 수 있도록 좀 더 이식성있게 만드는 좋은 방법처럼 들리지만 여전히 Node.js를 지원하려면 자바 스크립트 라이브러리를 수동으로 변경해야합니다. .
Chris W.

@ChrisW .: 예, 라이브러리를 수동으로 변경해야합니다. 개인적으로 나는 또한 외부 파일을 포함하는 두 번째 메커니즘이 마음에 들었습니다. 포함 된 파일의 글로벌 네임 스페이스를 가져온 네임 스페이스로 자동 변환하는 메커니즘입니다. 노드 개발자에게 RFE를 제출할 수 있습니까?
Martijn 2011 년

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)

이 모든 것이 실제로 얼마나 잘 작동하는지 모르겠습니다.


Hey, wow : 동일한 질문에 대해 동일한 사용자가 반대 투표 (내가 아님) 및 찬성 응답을했습니다! 그것에 대한 배지가 있어야합니다! ;-)
마이클 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.