doc: improvements to debugger.markdown copy
[platform/upstream/nodejs.git] / lib / module.js
1 'use strict';
2
3 const NativeModule = require('native_module');
4 const util = require('util');
5 const internalModule = require('internal/module');
6 const internalUtil = require('internal/util');
7 const runInThisContext = require('vm').runInThisContext;
8 const assert = require('assert').ok;
9 const fs = require('fs');
10 const path = require('path');
11 const internalModuleReadFile = process.binding('fs').internalModuleReadFile;
12 const internalModuleStat = process.binding('fs').internalModuleStat;
13
14 const splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//;
15 const isIndexRe = /^index\.\w+?$/;
16 const shebangRe = /^\#\!.*/;
17
18 // If obj.hasOwnProperty has been overridden, then calling
19 // obj.hasOwnProperty(prop) will break.
20 // See: https://github.com/joyent/node/issues/1707
21 function hasOwnProperty(obj, prop) {
22   return Object.prototype.hasOwnProperty.call(obj, prop);
23 }
24
25
26 function Module(id, parent) {
27   this.id = id;
28   this.exports = {};
29   this.parent = parent;
30   if (parent && parent.children) {
31     parent.children.push(this);
32   }
33
34   this.filename = null;
35   this.loaded = false;
36   this.children = [];
37 }
38 module.exports = Module;
39
40 Module._cache = {};
41 Module._pathCache = {};
42 Module._extensions = {};
43 var modulePaths = [];
44 Module.globalPaths = [];
45
46 Module.wrapper = NativeModule.wrapper;
47 Module.wrap = NativeModule.wrap;
48 Module._debug = util.debuglog('module');
49
50 // We use this alias for the preprocessor that filters it out
51 const debug = Module._debug;
52
53
54 // given a module name, and a list of paths to test, returns the first
55 // matching file in the following precedence.
56 //
57 // require("a.<ext>")
58 //   -> a.<ext>
59 //
60 // require("a")
61 //   -> a
62 //   -> a.<ext>
63 //   -> a/index.<ext>
64
65 // check if the directory is a package.json dir
66 const packageMainCache = {};
67
68 function readPackage(requestPath) {
69   if (hasOwnProperty(packageMainCache, requestPath)) {
70     return packageMainCache[requestPath];
71   }
72
73   var jsonPath = path.resolve(requestPath, 'package.json');
74   var json = internalModuleReadFile(path._makeLong(jsonPath));
75
76   if (json === undefined) {
77     return false;
78   }
79
80   try {
81     var pkg = packageMainCache[requestPath] = JSON.parse(json).main;
82   } catch (e) {
83     e.path = jsonPath;
84     e.message = 'Error parsing ' + jsonPath + ': ' + e.message;
85     throw e;
86   }
87   return pkg;
88 }
89
90 function tryPackage(requestPath, exts) {
91   var pkg = readPackage(requestPath);
92
93   if (!pkg) return false;
94
95   var filename = path.resolve(requestPath, pkg);
96   return tryFile(filename) || tryExtensions(filename, exts) ||
97          tryExtensions(path.resolve(filename, 'index'), exts);
98 }
99
100 // In order to minimize unnecessary lstat() calls,
101 // this cache is a list of known-real paths.
102 // Set to an empty object to reset.
103 Module._realpathCache = {};
104
105 // check if the file exists and is not a directory
106 function tryFile(requestPath) {
107   const rc = internalModuleStat(path._makeLong(requestPath));
108   return rc === 0 && toRealPath(requestPath);
109 }
110
111 function toRealPath(requestPath) {
112   return fs.realpathSync(requestPath, Module._realpathCache);
113 }
114
115 // given a path check a the file exists with any of the set extensions
116 function tryExtensions(p, exts) {
117   for (var i = 0, EL = exts.length; i < EL; i++) {
118     var filename = tryFile(p + exts[i]);
119
120     if (filename) {
121       return filename;
122     }
123   }
124   return false;
125 }
126
127 var warned = false;
128 Module._findPath = function(request, paths) {
129   var exts = Object.keys(Module._extensions);
130
131   if (path.isAbsolute(request)) {
132     paths = [''];
133   }
134
135   var trailingSlash = (request.slice(-1) === '/');
136
137   var cacheKey = JSON.stringify({request: request, paths: paths});
138   if (Module._pathCache[cacheKey]) {
139     return Module._pathCache[cacheKey];
140   }
141
142   // For each path
143   for (var i = 0, PL = paths.length; i < PL; i++) {
144     // Don't search further if path doesn't exist
145     if (paths[i] && internalModuleStat(path._makeLong(paths[i])) < 1) continue;
146     var basePath = path.resolve(paths[i], request);
147     var filename;
148
149     if (!trailingSlash) {
150       const rc = internalModuleStat(path._makeLong(basePath));
151       if (rc === 0) {  // File.
152         filename = toRealPath(basePath);
153       } else if (rc === 1) {  // Directory.
154         filename = tryPackage(basePath, exts);
155       }
156
157       if (!filename) {
158         // try it with each of the extensions
159         filename = tryExtensions(basePath, exts);
160       }
161     }
162
163     if (!filename) {
164       filename = tryPackage(basePath, exts);
165     }
166
167     if (!filename) {
168       // try it with each of the extensions at "index"
169       filename = tryExtensions(path.resolve(basePath, 'index'), exts);
170     }
171
172     if (filename) {
173       // Warn once if '.' resolved outside the module dir
174       if (request === '.' && i > 0) {
175         warned = internalUtil.printDeprecationMessage(
176           'warning: require(\'.\') resolved outside the package ' +
177           'directory. This functionality is deprecated and will be removed ' +
178           'soon.', warned);
179       }
180
181       Module._pathCache[cacheKey] = filename;
182       return filename;
183     }
184   }
185   return false;
186 };
187
188 // 'from' is the __dirname of the module.
189 Module._nodeModulePaths = function(from) {
190   // guarantee that 'from' is absolute.
191   from = path.resolve(from);
192
193   // note: this approach *only* works when the path is guaranteed
194   // to be absolute.  Doing a fully-edge-case-correct path.split
195   // that works on both Windows and Posix is non-trivial.
196   var paths = [];
197   var parts = from.split(splitRe);
198
199   for (var tip = parts.length - 1; tip >= 0; tip--) {
200     // don't search in .../node_modules/node_modules
201     if (parts[tip] === 'node_modules') continue;
202     var dir = parts.slice(0, tip + 1).concat('node_modules').join(path.sep);
203     paths.push(dir);
204   }
205
206   return paths;
207 };
208
209
210 Module._resolveLookupPaths = function(request, parent) {
211   if (NativeModule.nonInternalExists(request)) {
212     return [request, []];
213   }
214
215   var start = request.substring(0, 2);
216   if (start !== './' && start !== '..') {
217     var paths = modulePaths;
218     if (parent) {
219       if (!parent.paths) parent.paths = [];
220       paths = parent.paths.concat(paths);
221     }
222
223     // Maintain backwards compat with certain broken uses of require('.')
224     // by putting the module's directory in front of the lookup paths.
225     if (request === '.') {
226       if (parent && parent.filename) {
227         paths.splice(0, 0, path.dirname(parent.filename));
228       } else {
229         paths.splice(0, 0, path.resolve(request));
230       }
231     }
232
233     return [request, paths];
234   }
235
236   // with --eval, parent.id is not set and parent.filename is null
237   if (!parent || !parent.id || !parent.filename) {
238     // make require('./path/to/foo') work - normally the path is taken
239     // from realpath(__filename) but with eval there is no filename
240     var mainPaths = ['.'].concat(modulePaths);
241     mainPaths = Module._nodeModulePaths('.').concat(mainPaths);
242     return [request, mainPaths];
243   }
244
245   // Is the parent an index module?
246   // We can assume the parent has a valid extension,
247   // as it already has been accepted as a module.
248   var isIndex = isIndexRe.test(path.basename(parent.filename));
249   var parentIdPath = isIndex ? parent.id : path.dirname(parent.id);
250   var id = path.resolve(parentIdPath, request);
251
252   // make sure require('./path') and require('path') get distinct ids, even
253   // when called from the toplevel js file
254   if (parentIdPath === '.' && id.indexOf('/') === -1) {
255     id = './' + id;
256   }
257
258   debug('RELATIVE: requested: %s set ID to: %s from %s', request, id,
259         parent.id);
260
261   return [id, [path.dirname(parent.filename)]];
262 };
263
264
265 // Check the cache for the requested file.
266 // 1. If a module already exists in the cache: return its exports object.
267 // 2. If the module is native: call `NativeModule.require()` with the
268 //    filename and return the result.
269 // 3. Otherwise, create a new module for the file and save it to the cache.
270 //    Then have it load  the file contents before returning its exports
271 //    object.
272 Module._load = function(request, parent, isMain) {
273   if (parent) {
274     debug('Module._load REQUEST %s parent: %s', request, parent.id);
275   }
276
277   // REPL is a special case, because it needs the real require.
278   if (request === 'internal/repl' || request === 'repl') {
279     if (Module._cache[request]) {
280       return Module._cache[request];
281     }
282     var replModule = new Module(request);
283     replModule._compile(NativeModule.getSource(request), `${request}.js`);
284     NativeModule._cache[request] = replModule;
285     return replModule.exports;
286   }
287
288   var filename = Module._resolveFilename(request, parent);
289
290   var cachedModule = Module._cache[filename];
291   if (cachedModule) {
292     return cachedModule.exports;
293   }
294
295   if (NativeModule.nonInternalExists(filename)) {
296     debug('load native module %s', request);
297     return NativeModule.require(filename);
298   }
299
300   var module = new Module(filename, parent);
301
302   if (isMain) {
303     process.mainModule = module;
304     module.id = '.';
305   }
306
307   Module._cache[filename] = module;
308
309   var hadException = true;
310
311   try {
312     module.load(filename);
313     hadException = false;
314   } finally {
315     if (hadException) {
316       delete Module._cache[filename];
317     }
318   }
319
320   return module.exports;
321 };
322
323 Module._resolveFilename = function(request, parent) {
324   if (NativeModule.nonInternalExists(request)) {
325     return request;
326   }
327
328   var resolvedModule = Module._resolveLookupPaths(request, parent);
329   var id = resolvedModule[0];
330   var paths = resolvedModule[1];
331
332   // look up the filename first, since that's the cache key.
333   debug('looking for %j in %j', id, paths);
334
335   var filename = Module._findPath(request, paths);
336   if (!filename) {
337     var err = new Error("Cannot find module '" + request + "'");
338     err.code = 'MODULE_NOT_FOUND';
339     throw err;
340   }
341   return filename;
342 };
343
344
345 // Given a file name, pass it to the proper extension handler.
346 Module.prototype.load = function(filename) {
347   debug('load %j for module %j', filename, this.id);
348
349   assert(!this.loaded);
350   this.filename = filename;
351   this.paths = Module._nodeModulePaths(path.dirname(filename));
352
353   var extension = path.extname(filename) || '.js';
354   if (!Module._extensions[extension]) extension = '.js';
355   Module._extensions[extension](this, filename);
356   this.loaded = true;
357 };
358
359
360 // Loads a module at the given file path. Returns that module's
361 // `exports` property.
362 Module.prototype.require = function(path) {
363   assert(path, 'missing path');
364   assert(typeof path === 'string', 'path must be a string');
365   return Module._load(path, this);
366 };
367
368
369 // Resolved path to process.argv[1] will be lazily placed here
370 // (needed for setting breakpoint when called with --debug-brk)
371 var resolvedArgv;
372
373
374 // Run the file contents in the correct scope or sandbox. Expose
375 // the correct helper variables (require, module, exports) to
376 // the file.
377 // Returns exception, if any.
378 Module.prototype._compile = function(content, filename) {
379   var self = this;
380   // remove shebang
381   content = content.replace(shebangRe, '');
382
383   function require(path) {
384     return self.require(path);
385   }
386
387   require.resolve = function(request) {
388     return Module._resolveFilename(request, self);
389   };
390
391   Object.defineProperty(require, 'paths', { get: function() {
392     throw new Error('require.paths is removed. Use ' +
393                     'node_modules folders, or the NODE_PATH ' +
394                     'environment variable instead.');
395   }});
396
397   require.main = process.mainModule;
398
399   // Enable support to add extra extension types
400   require.extensions = Module._extensions;
401   require.registerExtension = function() {
402     throw new Error('require.registerExtension() removed. Use ' +
403                     'require.extensions instead.');
404   };
405
406   require.cache = Module._cache;
407
408   var dirname = path.dirname(filename);
409
410   // create wrapper function
411   var wrapper = Module.wrap(content);
412
413   var compiledWrapper = runInThisContext(wrapper,
414                                       { filename: filename, lineOffset: -1 });
415   if (global.v8debug) {
416     if (!resolvedArgv) {
417       // we enter the repl if we're not given a filename argument.
418       if (process.argv[1]) {
419         resolvedArgv = Module._resolveFilename(process.argv[1], null);
420       } else {
421         resolvedArgv = 'repl';
422       }
423     }
424
425     // Set breakpoint on module start
426     if (filename === resolvedArgv) {
427       // Installing this dummy debug event listener tells V8 to start
428       // the debugger.  Without it, the setBreakPoint() fails with an
429       // 'illegal access' error.
430       global.v8debug.Debug.setListener(function() {});
431       global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0);
432     }
433   }
434   var args = [self.exports, require, self, filename, dirname];
435   return compiledWrapper.apply(self.exports, args);
436 };
437
438
439 // Native extension for .js
440 Module._extensions['.js'] = function(module, filename) {
441   var content = fs.readFileSync(filename, 'utf8');
442   module._compile(internalModule.stripBOM(content), filename);
443 };
444
445
446 // Native extension for .json
447 Module._extensions['.json'] = function(module, filename) {
448   var content = fs.readFileSync(filename, 'utf8');
449   try {
450     module.exports = JSON.parse(internalModule.stripBOM(content));
451   } catch (err) {
452     err.message = filename + ': ' + err.message;
453     throw err;
454   }
455 };
456
457
458 //Native extension for .node
459 Module._extensions['.node'] = function(module, filename) {
460   return process.dlopen(module, path._makeLong(filename));
461 };
462
463
464 // bootstrap main module.
465 Module.runMain = function() {
466   // Load the main module--the command line argument.
467   Module._load(process.argv[1], null, true);
468   // Handle any nextTicks added in the first tick of the program
469   process._tickCallback();
470 };
471
472 Module._initPaths = function() {
473   const isWindows = process.platform === 'win32';
474
475   if (isWindows) {
476     var homeDir = process.env.USERPROFILE;
477   } else {
478     var homeDir = process.env.HOME;
479   }
480
481   var paths = [path.resolve(process.execPath, '..', '..', 'lib', 'node')];
482
483   if (homeDir) {
484     paths.unshift(path.resolve(homeDir, '.node_libraries'));
485     paths.unshift(path.resolve(homeDir, '.node_modules'));
486   }
487
488   var nodePath = process.env['NODE_PATH'];
489   if (nodePath) {
490     paths = nodePath.split(path.delimiter).filter(function(path) {
491       return !!path;
492     }).concat(paths);
493   }
494
495   modulePaths = paths;
496
497   // clone as a read-only copy, for introspection.
498   Module.globalPaths = modulePaths.slice(0);
499 };
500
501 // bootstrap repl
502 Module.requireRepl = function() {
503   return Module._load('internal/repl', '.');
504 };
505
506 Module._preloadModules = function(requests) {
507   if (!Array.isArray(requests))
508     return;
509
510   // Preloaded modules have a dummy parent module which is deemed to exist
511   // in the current working directory. This seeds the search path for
512   // preloaded modules.
513   var parent = new Module('internal/preload', null);
514   try {
515     parent.paths = Module._nodeModulePaths(process.cwd());
516   }
517   catch (e) {
518     if (e.code !== 'ENOENT') {
519       throw e;
520     }
521   }
522   requests.forEach(function(request) {
523     parent.require(request);
524   });
525 };
526
527 Module._initPaths();
528
529 // backwards compatibility
530 Module.Module = Module;