svaarala / duktape

Duktape - embeddable Javascript engine with a focus on portability and compact footprint
MIT License
5.95k stars 515 forks source link

Problem with extras/module-node #2147

Open moar82 opened 5 years ago

moar82 commented 5 years ago

Hello, I'm using version duktape 2.3.0 and I have issues to load Node.js modules.

In the following web page: https://duktape.org/guide.html there is a link for a Node.js modules compatible loader. However, the following link is broken "See How to use Node.js-like modules for examples."

I'm running the example in duktape-2.3.0/extras/module-node However, I got undefined in all cases. I'm not sure I understand the test, but I see in test.c file the following instruction:


if (strcmp(module_id, "pig.js") == 0) {
        duk_push_sprintf(ctx, "module.exports = 'you\\'re about to get eaten by %s';",

Which is clearly not working.

make
rm -rf ./prep
python2 ../../tools/configure.py --quiet --output-directory ./prep
genconfig.py          WARNING Recommended config option DUK_USE_FATAL_HANDLER not provided
gcc -std=c99 -Wall -Wextra -o test -I./prep -I. ./prep/duktape.c duk_module_node.c test.c -lm

./test 'assert(typeof require("pig") === "string", "basic require()");'
top after init: 0
Evaling: assert(typeof require("pig") === "string", "basic require()");
resolve_cb: id:'pig', parent-id:'', resolve-to:'pig.js'
load_cb: id:'pig.js', filename:'pig.js'
--> undefined
Done
./test 'assert(require("cow").indexOf("pig") !== -1, "nested require()");'
top after init: 0
Evaling: assert(require("cow").indexOf("pig") !== -1, "nested require()");
resolve_cb: id:'cow', parent-id:'', resolve-to:'cow.js'
load_cb: id:'cow.js', filename:'cow.js'
resolve_cb: id:'pig', parent-id:'cow.js', resolve-to:'pig.js'
load_cb: id:'pig.js', filename:'pig.js'
--> undefined
Done
./test 'var ape1 = require("ape"); var ape2 = require("ape"); assert(ape1 === ape2, "caching");'
top after init: 0
Evaling: var ape1 = require("ape"); var ape2 = require("ape"); assert(ape1 === ape2, "caching");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
--> undefined
Done
./test 'var ape1 = require("ape"); var inCache = "ape.js" in require.cache; delete require.cache["ape.js"]; var ape2 = require("ape"); assert(inCache && ape2 !== ape1, "require.cache");'
top after init: 0
Evaling: var ape1 = require("ape"); var inCache = "ape.js" in require.cache; delete require.cache["ape.js"]; var ape2 = require("ape"); assert(inCache && ape2 !== ape1, "require.cache");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
--> undefined
Done
./test 'var ape = require("ape"); assert(typeof ape.module.require === "function", "module.require()");'
top after init: 0
Evaling: var ape = require("ape"); assert(typeof ape.module.require === "function", "module.require()");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
--> undefined
Done
./test 'var ape = require("ape"); assert(ape.module.exports === ape, "module.exports");'
top after init: 0
Evaling: var ape = require("ape"); assert(ape.module.exports === ape, "module.exports");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
--> undefined
Done
./test 'var ape = require("ape"); assert(ape.module.id === "ape.js" && ape.module.id === ape.module.filename, "module.id");'
top after init: 0
Evaling: var ape = require("ape"); assert(ape.module.id === "ape.js" && ape.module.id === ape.module.filename, "module.id");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
--> undefined
Done
./test 'var ape = require("ape"); assert(ape.module.filename === "ape.js", "module.filename");'
top after init: 0
Evaling: var ape = require("ape"); assert(ape.module.filename === "ape.js", "module.filename");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
--> undefined
Done
./test 'var ape = require("ape"); assert(ape.module.loaded === true && ape.wasLoaded === false, "module.loaded");'
top after init: 0
Evaling: var ape = require("ape"); assert(ape.module.loaded === true && ape.wasLoaded === false, "module.loaded");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
--> undefined
Done
./test 'var ape = require("ape"); assert(ape.__filename === "ape.js", "__filename");'
top after init: 0
Evaling: var ape = require("ape"); assert(ape.__filename === "ape.js", "__filename");
resolve_cb: id:'ape', parent-id:'', resolve-to:'ape.js'
load_cb: id:'ape.js', filename:'ape.js'
--> undefined
Done
./test 'var badger = require("badger"); assert(badger.foo === 123 && badger.bar === 234, "exports.foo assignment");'
top after init: 0
Evaling: var badger = require("badger"); assert(badger.foo === 123 && badger.bar === 234, "exports.foo assignment");
resolve_cb: id:'badger', parent-id:'', resolve-to:'badger.js'
load_cb: id:'badger.js', filename:'badger.js'
--> undefined
Done
./test 'var comment = require("comment"); assert(comment.foo === 123 && comment.bar === 234, "last line with // comment");'
top after init: 0
Evaling: var comment = require("comment"); assert(comment.foo === 123 && comment.bar === 234, "last line with // comment");
resolve_cb: id:'comment', parent-id:'', resolve-to:'comment.js'
load_cb: id:'comment.js', filename:'comment.js'
--> undefined
Done
./test 'var shebang = require("shebang"); assert(shebang.foo === 123 && shebang.bar === 234, "shebang");'
top after init: 0
Evaling: var shebang = require("shebang"); assert(shebang.foo === 123 && shebang.bar === 234, "shebang");
resolve_cb: id:'shebang', parent-id:'', resolve-to:'shebang.js'
load_cb: id:'shebang.js', filename:'shebang.js'
--> undefined
Done

Any help would be appreciated.

Regards

svaarala commented 5 years ago

If you're referring to the --> undefined printouts, they are simply the result of eval()ing the input:

In particular, if the script being eval'd ends with assert(...), assert() will return undefined, and thus the script result will be undefined on success. The makefile tests use this assert() technique, throwing an error if some test fails, and that error would then show up in the output: --> TypeError: xxx.

moar82 commented 5 years ago

Sorry, I was expecting a more straightforward test. I apologize my lack of knowledge, but I'm struggling to fix the blanks on the documentation (https://github.com/svaarala/duktape/tree/master/extras/module-node).

Is it too much to ask for a running example? something like runing this script that requires a node.js library?

var http = require('http'); 
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('Hello World!');
}).listen(8080); 

This is my C code:

#include <stdio.h>
#include <string.h>
#include "duktape.h"
#include "duk_module_node.h"

static duk_ret_t cb_resolve_module(duk_context *ctx) {
    /*
     *  Entry stack: [ requested_id parent_id ]
     */

    const char *requested_id = duk_get_string(ctx, 0);
    const char *parent_id = duk_get_string(ctx, 1);  /* calling module */
    const char *resolved_id;

    /* Arrive at the canonical module ID somehow. */

    duk_push_string(ctx, resolved_id);
    return 1;  /*nrets*/

}

static duk_ret_t cb_load_module(duk_context *ctx) {
    const char *filename;
    const char *module_id;

    module_id = duk_require_string(ctx, 0);
    duk_get_prop_string(ctx, 2, "filename");
    filename = duk_require_string(ctx, -1);

     duk_push_string(ctx, module_id);

    return 1;
}

static duk_ret_t handle_print(duk_context *ctx) {
    printf("%s\n", duk_safe_to_string(ctx, 0));
    return 0;
}

static duk_ret_t handle_assert(duk_context *ctx) {
    if (duk_to_boolean(ctx, 0)) {
        return 0;
    }
    (void) duk_generic_error(ctx, "assertion failed: %s", duk_safe_to_string(ctx, 1));
    return 0;
}

/* We assume a  JS maximum file size of 17kB. */
static void push_file_as_string(duk_context *ctx, const char *filename) {
    FILE *f;
    size_t len;
    char buf[17384];

    f = fopen(filename, "rb");
    if (f) {
        len = fread((void *) buf, 1, sizeof(buf), f);
        fclose(f);
        duk_push_lstring(ctx, (const char *) buf, (duk_size_t) len);
    } else {
        duk_push_undefined(ctx);
    }
}

int main(int argc, char *argv[]) {
    duk_context *ctx;
    //int i;
    //int exitcode = 0;

    ctx = duk_create_heap_default();
    if (!ctx) {
        return 1;
    }

    duk_push_c_function(ctx, handle_print, 1);
    duk_put_global_string(ctx, "print");
    duk_push_c_function(ctx, handle_assert, 2);
    duk_put_global_string(ctx, "assert");

    duk_push_object(ctx);
    duk_push_c_function(ctx, cb_resolve_module, DUK_VARARGS);
    duk_put_prop_string(ctx, -2, "resolve");
    duk_push_c_function(ctx, cb_load_module, DUK_VARARGS);
    duk_put_prop_string(ctx, -2, "load");
    duk_module_node_init(ctx);

    /* We push to Duktape heap the JS file*/
    push_file_as_string(ctx, argv[1]);
    if (duk_peval(ctx) != 0) {
        printf("Error: %s\n", duk_safe_to_string(ctx, -1));
            goto finished;
        }
    if (duk_pcall(ctx, 0 /*nargs*/) != 0) {
                printf("Error: %s\n", duk_safe_to_string(ctx, -1));
                } 
    duk_pop(ctx);  /* pop result/error */

    finished:
    duk_destroy_heap(ctx);    

    printf("Done\n");

}

Thanks for the support

fatcerberus commented 5 years ago

@moar82 If you’re expecting that require("http") to work automatically with the node module loader, it won’t unless you provide your own implementation of the http module...

This is just a module loader framework providing support for Node.js-style CommonJS modules (i.e. require() itself). You don’t get the Node.js APIs “for free” if that’s what you were expecting.