123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- /*
- * 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 */
- }
|