1 /*
2 * Node.js-like module loading framework for Duktape
3 *
4 * https://nodejs.org/api/modules.html
5 */
6
7 #include "be_inl.h"
8
9 static void duk__push_module_object(duk_context *ctx, const char *id);
10
duk__get_cached_module(duk_context * ctx,const char * id)11 static duk_bool_t duk__get_cached_module(duk_context *ctx, const char *id)
12 {
13 duk_push_global_stash(ctx);
14 (void)duk_get_prop_string(ctx, -1,
15 "\xff"
16 "requireCache");
17 if (duk_get_prop_string(ctx, -1, id)) {
18 duk_remove(ctx, -2);
19 duk_remove(ctx, -2);
20 return 1;
21 } else {
22 duk_pop_3(ctx);
23 return 0;
24 }
25 }
26
27 /* Place a `module` object on the top of the value stack into the require cache
28 * based on its `.id` property. As a convenience to the caller, leave the
29 * object on top of the value stack afterwards.
30 */
duk__put_cached_module(duk_context * ctx)31 static void duk__put_cached_module(duk_context *ctx)
32 {
33 /* [ ... module ] */
34
35 duk_push_global_stash(ctx);
36 (void)duk_get_prop_string(ctx, -1,
37 "\xff"
38 "requireCache");
39 duk_dup(ctx, -3);
40
41 /* [ ... module stash req_cache module ] */
42
43 (void)duk_get_prop_string(ctx, -1, "id");
44 duk_dup(ctx, -2);
45 duk_put_prop(ctx, -4);
46
47 duk_pop_3(ctx); /* [ ... module ] */
48 }
49
duk__del_cached_module(duk_context * ctx,const char * id)50 static void duk__del_cached_module(duk_context *ctx, const char *id)
51 {
52 duk_push_global_stash(ctx);
53 (void)duk_get_prop_string(ctx, -1,
54 "\xff"
55 "requireCache");
56 duk_del_prop_string(ctx, -1, id);
57 duk_pop_2(ctx);
58 }
59
duk__eval_module_source(duk_context * ctx,void * udata)60 static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata)
61 {
62 const char *src;
63
64 /*
65 * Stack: [ ... module source ]
66 */
67
68 (void)udata;
69
70 /* Wrap the module code in a function expression. This is the simplest
71 * way to implement CommonJS closure semantics and matches the behavior of
72 * e.g. Node.js.
73 */
74 duk_push_string(ctx,
75 "(function(exports,require,module,__filename,__dirname){");
76 src = duk_require_string(ctx, -2);
77 duk_push_string(ctx, (src[0] == '#' && src[1] == '!')
78 ? "//"
79 : ""); /* Shebang support. */
80 duk_dup(ctx, -3); /* source */
81 duk_push_string(
82 ctx,
83 "\n})"); /* Newline allows module last line to contain a // comment. */
84 duk_concat(ctx, 4);
85
86 /* [ ... module source func_src ] */
87
88 (void)duk_get_prop_string(ctx, -3, "filename");
89 duk_compile(ctx, DUK_COMPILE_EVAL);
90 duk_call(ctx, 0);
91
92 /* [ ... module source func ] */
93
94 /* Set name for the wrapper function. */
95 duk_push_string(ctx, "name");
96 duk_push_string(ctx, "main");
97 duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);
98
99 /* call the function wrapper */
100 (void)duk_get_prop_string(ctx, -3, "exports"); /* exports */
101 (void)duk_get_prop_string(ctx, -4, "require"); /* require */
102 duk_dup(ctx, -5); /* module */
103 (void)duk_get_prop_string(ctx, -6, "filename"); /* __filename */
104 duk_push_undefined(ctx); /* __dirname */
105 duk_call(ctx, 5);
106
107 /* [ ... module source result(ignore) ] */
108
109 /* module.loaded = true */
110 duk_push_true(ctx);
111 duk_put_prop_string(ctx, -4, "loaded");
112
113 /* [ ... module source retval ] */
114
115 duk_pop_2(ctx);
116
117 /* [ ... module ] */
118
119 return 1;
120 }
121
duk__handle_require(duk_context * ctx)122 static duk_ret_t duk__handle_require(duk_context *ctx)
123 {
124 /*
125 * Value stack handling here is a bit sloppy but should be correct.
126 * Call handling will clean up any extra garbage for us.
127 */
128
129 const char *id;
130 const char *parent_id;
131 duk_idx_t module_idx;
132 duk_idx_t stash_idx;
133 duk_int_t ret;
134
135 duk_push_global_stash(ctx);
136 stash_idx = duk_normalize_index(ctx, -1);
137
138 duk_push_current_function(ctx);
139 (void)duk_get_prop_string(ctx, -1,
140 "\xff"
141 "moduleId");
142 parent_id = duk_require_string(ctx, -1);
143 (void)parent_id; /* not used directly; suppress warning */
144
145 /* [ id stash require parent_id ] */
146
147 id = duk_require_string(ctx, 0);
148
149 (void)duk_get_prop_string(ctx, stash_idx,
150 "\xff"
151 "modResolve");
152 duk_dup(ctx, 0); /* module ID */
153 duk_dup(ctx, -3); /* parent ID */
154 duk_call(ctx, 2);
155
156 /* [ ... stash ... resolved_id ] */
157
158 id = duk_require_string(ctx, -1);
159
160 if (duk__get_cached_module(ctx, id)) {
161 goto have_module; /* use the cached module */
162 }
163
164 duk__push_module_object(ctx, id);
165 duk__put_cached_module(ctx); /* module remains on stack */
166
167 /*
168 * From here on out, we have to be careful not to throw. If it can't be
169 * avoided, the error must be caught and the module removed from the
170 * require cache before rethrowing. This allows the application to
171 * reattempt loading the module.
172 */
173
174 module_idx = duk_normalize_index(ctx, -1);
175
176 /* [ ... stash ... resolved_id module ] */
177
178 (void)duk_get_prop_string(ctx, stash_idx,
179 "\xff"
180 "modLoad");
181 duk_dup(ctx, -3); /* resolved ID */
182 (void)duk_get_prop_string(ctx, module_idx, "exports");
183 duk_dup(ctx, module_idx);
184 ret = duk_pcall(ctx, 3);
185 if (ret != DUK_EXEC_SUCCESS) {
186 duk__del_cached_module(ctx, id);
187 (void)duk_throw(ctx); /* rethrow */
188 }
189
190 if (duk_is_string(ctx, -1)) {
191 duk_int_t ret;
192
193 /* [ ... module source ] */
194
195 ret = duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
196 if (ret != DUK_EXEC_SUCCESS) {
197 duk__del_cached_module(ctx, id);
198 (void)duk_throw(ctx); /* rethrow */
199 }
200 } else if (duk_is_undefined(ctx, -1)) {
201 duk_pop(ctx);
202 } else {
203 duk__del_cached_module(ctx, id);
204 (void)duk_type_error(ctx, "invalid module load callback return value");
205 }
206
207 /* fall through */
208
209 have_module:
210 /* [ ... module ] */
211
212 (void)duk_get_prop_string(ctx, -1, "exports");
213 duk_gc(ctx, 0);
214 return 1;
215 }
216
duk__push_require_function(duk_context * ctx,const char * id)217 static void duk__push_require_function(duk_context *ctx, const char *id)
218 {
219 duk_push_c_function(ctx, duk__handle_require, 1);
220 duk_push_string(ctx, "name");
221 duk_push_string(ctx, "require");
222 duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE);
223 duk_push_string(ctx, id);
224 duk_put_prop_string(ctx, -2,
225 "\xff"
226 "moduleId");
227
228 /* require.cache */
229 duk_push_global_stash(ctx);
230 (void)duk_get_prop_string(ctx, -1,
231 "\xff"
232 "requireCache");
233 duk_put_prop_string(ctx, -3, "cache");
234 duk_pop(ctx);
235 }
236
duk__push_module_object(duk_context * ctx,const char * id)237 static void duk__push_module_object(duk_context *ctx, const char *id)
238 {
239 duk_push_object(ctx);
240
241 /* Node.js uses the canonicalized filename of a module for both module.id
242 * and module.filename. We have no concept of a file system here, so just
243 * use the module ID for both values.
244 */
245 duk_push_string(ctx, id);
246 duk_dup(ctx, -1);
247 duk_put_prop_string(ctx, -3, "filename");
248 duk_put_prop_string(ctx, -2, "id");
249
250 /* module.exports = {} */
251 duk_push_object(ctx);
252 duk_put_prop_string(ctx, -2, "exports");
253
254 /* module.loaded = false */
255 duk_push_false(ctx);
256 duk_put_prop_string(ctx, -2, "loaded");
257
258 /* module.require */
259 duk__push_require_function(ctx, id);
260 duk_put_prop_string(ctx, -2, "require");
261 }
262
be_module_node_set_entry(duk_context * ctx,const char * entry)263 void be_module_node_set_entry(duk_context *ctx, const char *entry)
264 {
265 duk_push_global_object(ctx);
266 duk_get_prop_string(ctx, -1, "require");
267 duk_push_string(ctx, entry);
268 duk_put_prop_string(ctx, -2,
269 "\xff"
270 "moduleId");
271 duk_pop_n(ctx, 2);
272 }
273
be_module_node_init(duk_context * ctx)274 void be_module_node_init(duk_context *ctx)
275 {
276 /*
277 * Stack: [ ... options ] => [ ... ]
278 */
279
280 duk_idx_t options_idx;
281
282 duk_require_object_coercible(ctx,
283 -1); /* error before setting up requireCache */
284 options_idx = duk_require_normalize_index(ctx, -1);
285
286 /* Initialize the require cache to a fresh object. */
287 duk_push_global_stash(ctx);
288 duk_push_bare_object(ctx);
289 duk_put_prop_string(ctx, -2,
290 "\xff"
291 "requireCache");
292 duk_pop(ctx);
293
294 /* Stash callbacks for later use. User code can overwrite them later
295 * on directly by accessing the global stash.
296 */
297 duk_push_global_stash(ctx);
298 duk_get_prop_string(ctx, options_idx, "resolve");
299 duk_require_function(ctx, -1);
300 duk_put_prop_string(ctx, -2,
301 "\xff"
302 "modResolve");
303 duk_get_prop_string(ctx, options_idx, "load");
304 duk_require_function(ctx, -1);
305 duk_put_prop_string(ctx, -2,
306 "\xff"
307 "modLoad");
308 duk_pop(ctx);
309
310 /* register `require` as a global function. */
311 duk_push_global_object(ctx);
312 duk_push_string(ctx, "require");
313 duk__push_require_function(ctx, "");
314 duk_def_prop(ctx, -3,
315 DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_SET_WRITABLE |
316 DUK_DEFPROP_SET_CONFIGURABLE);
317 duk_pop(ctx);
318
319 duk_pop(ctx); /* pop argument */
320 }
321