/* * Node.js-like module loading framework for Duktape * * https://nodejs.org/api/modules.html */ #include "duktape.h" #include "duk_module_node.h" #if DUK_VERSION >= 19999 static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata); #else static duk_int_t duk__eval_module_source(duk_context *ctx); #endif static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main); static duk_bool_t duk__get_cached_module(duk_context *ctx, const char *id) { duk_push_global_stash(ctx); (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache"); if (duk_get_prop_string(ctx, -1, id)) { duk_remove(ctx, -2); duk_remove(ctx, -2); return 1; } else { duk_pop_3(ctx); return 0; } } /* Place a `module` object on the top of the value stack into the require cache * based on its `.id` property. As a convenience to the caller, leave the * object on top of the value stack afterwards. */ static void duk__put_cached_module(duk_context *ctx) { /* [ ... module ] */ duk_push_global_stash(ctx); (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache"); duk_dup(ctx, -3); /* [ ... module stash req_cache module ] */ (void) duk_get_prop_string(ctx, -1, "id"); duk_dup(ctx, -2); duk_put_prop(ctx, -4); duk_pop_3(ctx); /* [ ... module ] */ } static void duk__del_cached_module(duk_context *ctx, const char *id) { duk_push_global_stash(ctx); (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache"); duk_del_prop_string(ctx, -1, id); duk_pop_2(ctx); } static duk_ret_t duk__handle_require(duk_context *ctx) { /* * Value stack handling here is a bit sloppy but should be correct. * Call handling will clean up any extra garbage for us. */ const char *id; const char *parent_id; duk_idx_t module_idx; duk_idx_t stash_idx; duk_int_t ret; duk_push_global_stash(ctx); stash_idx = duk_normalize_index(ctx, -1); duk_push_current_function(ctx); (void) duk_get_prop_string(ctx, -1, "\xff" "moduleId"); parent_id = duk_require_string(ctx, -1); (void) parent_id; /* not used directly; suppress warning */ /* [ id stash require parent_id ] */ id = duk_require_string(ctx, 0); (void) duk_get_prop_string(ctx, stash_idx, "\xff" "modResolve"); duk_dup(ctx, 0); /* module ID */ duk_dup(ctx, -3); /* parent ID */ duk_call(ctx, 2); /* [ ... stash ... resolved_id ] */ id = duk_require_string(ctx, -1); if (duk__get_cached_module(ctx, id)) { goto have_module; /* use the cached module */ } duk__push_module_object(ctx, id, 0 /*main*/); duk__put_cached_module(ctx); /* module remains on stack */ /* * From here on out, we have to be careful not to throw. If it can't be * avoided, the error must be caught and the module removed from the * require cache before rethrowing. This allows the application to * reattempt loading the module. */ module_idx = duk_normalize_index(ctx, -1); /* [ ... stash ... resolved_id module ] */ (void) duk_get_prop_string(ctx, stash_idx, "\xff" "modLoad"); duk_dup(ctx, -3); /* resolved ID */ (void) duk_get_prop_string(ctx, module_idx, "exports"); duk_dup(ctx, module_idx); ret = duk_pcall(ctx, 3); if (ret != DUK_EXEC_SUCCESS) { duk__del_cached_module(ctx, id); (void) duk_throw(ctx); /* rethrow */ } if (duk_is_string(ctx, -1)) { duk_int_t ret; /* [ ... module source ] */ #if DUK_VERSION >= 19999 ret = duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1); #else ret = duk_safe_call(ctx, duk__eval_module_source, 2, 1); #endif if (ret != DUK_EXEC_SUCCESS) { duk__del_cached_module(ctx, id); (void) duk_throw(ctx); /* rethrow */ } } else if (duk_is_undefined(ctx, -1)) { duk_pop(ctx); } else { duk__del_cached_module(ctx, id); (void) duk_type_error(ctx, "invalid module load callback return value"); } /* fall through */ have_module: /* [ ... module ] */ (void) duk_get_prop_string(ctx, -1, "exports"); return 1; } static void duk__push_require_function(duk_context *ctx, const char *id) { duk_push_c_function(ctx, duk__handle_require, 1); duk_push_string(ctx, "name"); duk_push_string(ctx, "require"); duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE); duk_push_string(ctx, id); duk_put_prop_string(ctx, -2, "\xff" "moduleId"); /* require.cache */ duk_push_global_stash(ctx); (void) duk_get_prop_string(ctx, -1, "\xff" "requireCache"); duk_put_prop_string(ctx, -3, "cache"); duk_pop(ctx); /* require.main */ duk_push_global_stash(ctx); (void) duk_get_prop_string(ctx, -1, "\xff" "mainModule"); duk_put_prop_string(ctx, -3, "main"); duk_pop(ctx); } static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main) { duk_push_object(ctx); /* Set this as the main module, if requested */ if (main) { duk_push_global_stash(ctx); duk_dup(ctx, -2); duk_put_prop_string(ctx, -2, "\xff" "mainModule"); duk_pop(ctx); } /* Node.js uses the canonicalized filename of a module for both module.id * and module.filename. We have no concept of a file system here, so just * use the module ID for both values. */ duk_push_string(ctx, id); duk_dup(ctx, -1); duk_put_prop_string(ctx, -3, "filename"); duk_put_prop_string(ctx, -2, "id"); /* module.exports = {} */ duk_push_object(ctx); duk_put_prop_string(ctx, -2, "exports"); /* module.loaded = false */ duk_push_false(ctx); duk_put_prop_string(ctx, -2, "loaded"); /* module.require */ duk__push_require_function(ctx, id); duk_put_prop_string(ctx, -2, "require"); } #if DUK_VERSION >= 19999 static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata) { #else static duk_int_t duk__eval_module_source(duk_context *ctx) { #endif const char *src; /* * Stack: [ ... module source ] */ #if DUK_VERSION >= 19999 (void) udata; #endif /* Wrap the module code in a function expression. This is the simplest * way to implement CommonJS closure semantics and matches the behavior of * e.g. Node.js. */ duk_push_string(ctx, "(function(exports,require,module,__filename,__dirname){"); src = duk_require_string(ctx, -2); duk_push_string(ctx, (src[0] == '#' && src[1] == '!') ? "//" : ""); /* Shebang support. */ duk_dup(ctx, -3); /* source */ duk_push_string(ctx, "\n})"); /* Newline allows module last line to contain a // comment. */ duk_concat(ctx, 4); /* [ ... module source func_src ] */ (void) duk_get_prop_string(ctx, -3, "filename"); duk_compile(ctx, DUK_COMPILE_EVAL); duk_call(ctx, 0); /* [ ... module source func ] */ /* Set name for the wrapper function. */ duk_push_string(ctx, "name"); duk_push_string(ctx, "main"); duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); /* call the function wrapper */ (void) duk_get_prop_string(ctx, -3, "exports"); /* exports */ (void) duk_get_prop_string(ctx, -4, "require"); /* require */ duk_dup(ctx, -5); /* module */ (void) duk_get_prop_string(ctx, -6, "filename"); /* __filename */ duk_push_undefined(ctx); /* __dirname */ duk_call(ctx, 5); /* [ ... module source result(ignore) ] */ /* module.loaded = true */ duk_push_true(ctx); duk_put_prop_string(ctx, -4, "loaded"); /* [ ... module source retval ] */ duk_pop_2(ctx); /* [ ... module ] */ return 1; } /* Load a module as the 'main' module. */ duk_ret_t duk_module_node_peval_main(duk_context *ctx, const char *path) { /* * Stack: [ ... source ] */ duk__push_module_object(ctx, path, 1 /*main*/); /* [ ... source module ] */ duk_dup(ctx, 0); /* [ ... source module source ] */ #if DUK_VERSION >= 19999 return duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1); #else return duk_safe_call(ctx, duk__eval_module_source, 2, 1); #endif } void duk_module_node_init(duk_context *ctx) { /* * Stack: [ ... options ] => [ ... ] */ duk_idx_t options_idx; duk_require_object_coercible(ctx, -1); /* error before setting up requireCache */ options_idx = duk_require_normalize_index(ctx, -1); /* Initialize the require cache to a fresh object. */ duk_push_global_stash(ctx); #if DUK_VERSION >= 19999 duk_push_bare_object(ctx); #else duk_push_object(ctx); duk_push_undefined(ctx); duk_set_prototype(ctx, -2); #endif duk_put_prop_string(ctx, -2, "\xff" "requireCache"); duk_pop(ctx); /* Stash callbacks for later use. User code can overwrite them later * on directly by accessing the global stash. */ duk_push_global_stash(ctx); duk_get_prop_string(ctx, options_idx, "resolve"); duk_require_function(ctx, -1); duk_put_prop_string(ctx, -2, "\xff" "modResolve"); duk_get_prop_string(ctx, options_idx, "load"); duk_require_function(ctx, -1); duk_put_prop_string(ctx, -2, "\xff" "modLoad"); duk_pop(ctx); /* Stash main module. */ duk_push_global_stash(ctx); duk_push_undefined(ctx); duk_put_prop_string(ctx, -2, "\xff" "mainModule"); duk_pop(ctx); /* register `require` as a global function. */ duk_push_global_object(ctx); duk_push_string(ctx, "require"); duk__push_require_function(ctx, ""); duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_SET_WRITABLE | DUK_DEFPROP_SET_CONFIGURABLE); duk_pop(ctx); duk_pop(ctx); /* pop argument */ }