• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypeForEach,
5  ArrayPrototypeJoin,
6  ArrayPrototypeSome,
7  ObjectDefineProperty,
8  ObjectPrototypeHasOwnProperty,
9  SafeMap,
10  SafeSet,
11  StringPrototypeCharCodeAt,
12  StringPrototypeIncludes,
13  StringPrototypeSlice,
14  StringPrototypeStartsWith,
15} = primordials;
16const {
17  ERR_INVALID_ARG_TYPE,
18  ERR_MANIFEST_DEPENDENCY_MISSING,
19  ERR_UNKNOWN_BUILTIN_MODULE,
20} = require('internal/errors').codes;
21const { BuiltinModule } = require('internal/bootstrap/realm');
22
23const { validateString } = require('internal/validators');
24const fs = require('fs'); // Import all of `fs` so that it can be monkey-patched.
25const internalFS = require('internal/fs/utils');
26const path = require('path');
27const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
28
29const { getOptionValue } = require('internal/options');
30const { setOwnProperty } = require('internal/util');
31
32const {
33  privateSymbols: {
34    require_private_symbol,
35  },
36} = internalBinding('util');
37
38let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
39  debug = fn;
40});
41
42/** @typedef {import('internal/modules/cjs/loader.js').Module} Module */
43
44/**
45 * Cache for storing resolved real paths of modules.
46 * In order to minimize unnecessary lstat() calls, this cache is a list of known-real paths.
47 * Set to an empty Map to reset.
48 * @type {Map<string, string>}
49 */
50const realpathCache = new SafeMap();
51/**
52 * Resolves the path of a given `require` specifier, following symlinks.
53 * @param {string} requestPath The `require` specifier
54 */
55function toRealPath(requestPath) {
56  return fs.realpathSync(requestPath, {
57    [internalFS.realpathCacheKey]: realpathCache,
58  });
59}
60
61/** @type {Set<string>} */
62let cjsConditions;
63/**
64 * Define the conditions that apply to the CommonJS loader.
65 */
66function initializeCjsConditions() {
67  const userConditions = getOptionValue('--conditions');
68  const noAddons = getOptionValue('--no-addons');
69  const addonConditions = noAddons ? [] : ['node-addons'];
70  // TODO: Use this set when resolving pkg#exports conditions in loader.js.
71  cjsConditions = new SafeSet([
72    'require',
73    'node',
74    ...addonConditions,
75    ...userConditions,
76  ]);
77}
78
79/**
80 * Get the conditions that apply to the CommonJS loader.
81 */
82function getCjsConditions() {
83  if (cjsConditions === undefined) {
84    initializeCjsConditions();
85  }
86  return cjsConditions;
87}
88
89/**
90 * Provide one of Node.js' public modules to user code.
91 * @param {string} id - The identifier/specifier of the builtin module to load
92 * @param {string} request - The module requiring or importing the builtin module
93 */
94function loadBuiltinModule(id, request) {
95  if (!BuiltinModule.canBeRequiredByUsers(id)) {
96    return;
97  }
98  /** @type {import('internal/bootstrap/realm.js').BuiltinModule} */
99  const mod = BuiltinModule.map.get(id);
100  debug('load built-in module %s', request);
101  // compileForPublicLoader() throws if canBeRequiredByUsers is false:
102  mod.compileForPublicLoader();
103  return mod;
104}
105
106/** @type {Module} */
107let $Module = null;
108/**
109 * Import the Module class on first use.
110 */
111function lazyModule() {
112  $Module = $Module || require('internal/modules/cjs/loader').Module;
113  return $Module;
114}
115
116/**
117 * Invoke with `makeRequireFunction(module)` where `module` is the `Module` object to use as the context for the
118 * `require()` function.
119 * Use redirects to set up a mapping from a policy and restrict dependencies.
120 */
121const urlToFileCache = new SafeMap();
122/**
123 * Create the module-scoped `require` function to pass into CommonJS modules.
124 * @param {Module} mod - The module to create the `require` function for.
125 * @param {ReturnType<import('internal/policy/manifest.js').Manifest['getDependencyMapper']>} redirects
126 * @typedef {(specifier: string) => unknown} RequireFunction
127 */
128function makeRequireFunction(mod, redirects) {
129  // lazy due to cycle
130  const Module = lazyModule();
131  if (mod instanceof Module !== true) {
132    throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod);
133  }
134
135  /** @type {RequireFunction} */
136  let require;
137  if (redirects) {
138    const id = mod.filename || mod.id;
139    const conditions = getCjsConditions();
140    const { resolve, reaction } = redirects;
141    require = function require(specifier) {
142      let missing = true;
143      const destination = resolve(specifier, conditions);
144      if (destination === true) {
145        missing = false;
146      } else if (destination) {
147        const { href, protocol } = destination;
148        if (protocol === 'node:') {
149          const specifier = destination.pathname;
150
151          if (BuiltinModule.canBeRequiredByUsers(specifier)) {
152            const mod = loadBuiltinModule(specifier, href);
153            return mod.exports;
154          }
155          throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier);
156        } else if (protocol === 'file:') {
157          let filepath = urlToFileCache.get(href);
158          if (!filepath) {
159            filepath = fileURLToPath(destination);
160            urlToFileCache.set(href, filepath);
161          }
162          return mod[require_private_symbol](mod, filepath);
163        }
164      }
165      if (missing) {
166        reaction(new ERR_MANIFEST_DEPENDENCY_MISSING(
167          id,
168          specifier,
169          ArrayPrototypeJoin([...conditions], ', '),
170        ));
171      }
172      return mod[require_private_symbol](mod, specifier);
173    };
174  } else {
175    require = function require(path) {
176      // When no policy manifest, the original prototype.require is sustained
177      return mod.require(path);
178    };
179  }
180
181  /**
182   * The `resolve` method that gets attached to module-scope `require`.
183   * @param {string} request
184   * @param {Parameters<Module['_resolveFilename']>[3]} options
185   */
186  function resolve(request, options) {
187    validateString(request, 'request');
188    return Module._resolveFilename(request, mod, false, options);
189  }
190
191  require.resolve = resolve;
192
193  /**
194   * The `paths` method that gets attached to module-scope `require`.
195   * @param {string} request
196   */
197  function paths(request) {
198    validateString(request, 'request');
199    return Module._resolveLookupPaths(request, mod);
200  }
201
202  resolve.paths = paths;
203
204  setOwnProperty(require, 'main', process.mainModule);
205
206  // Enable support to add extra extension types.
207  require.extensions = Module._extensions;
208
209  require.cache = Module._cache;
210
211  return require;
212}
213
214/**
215 * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
216 * because the buffer-to-string conversion in `fs.readFileSync()`
217 * translates it to FEFF, the UTF-16 BOM.
218 * @param {string} content
219 */
220function stripBOM(content) {
221  if (StringPrototypeCharCodeAt(content) === 0xFEFF) {
222    content = StringPrototypeSlice(content, 1);
223  }
224  return content;
225}
226
227/**
228 * Add built-in modules to a global or REPL scope object.
229 * @param {Record<string, unknown>} object - The object such as `globalThis` to add the built-in modules to.
230 * @param {string} dummyModuleName - The label representing the set of built-in modules to add.
231 */
232function addBuiltinLibsToObject(object, dummyModuleName) {
233  // Make built-in modules available directly (loaded lazily).
234  const Module = require('internal/modules/cjs/loader').Module;
235  const { builtinModules } = Module;
236
237  // To require built-in modules in user-land and ignore modules whose
238  // `canBeRequiredByUsers` is false. So we create a dummy module object and not
239  // use `require()` directly.
240  const dummyModule = new Module(dummyModuleName);
241
242  ArrayPrototypeForEach(builtinModules, (name) => {
243    // Neither add underscored modules, nor ones that contain slashes (e.g.,
244    // 'fs/promises') or ones that are already defined.
245    if (StringPrototypeStartsWith(name, '_') ||
246        StringPrototypeIncludes(name, '/') ||
247        ObjectPrototypeHasOwnProperty(object, name)) {
248      return;
249    }
250    // Goals of this mechanism are:
251    // - Lazy loading of built-in modules
252    // - Having all built-in modules available as non-enumerable properties
253    // - Allowing the user to re-assign these variables as if there were no
254    //   pre-existing globals with the same name.
255
256    const setReal = (val) => {
257      // Deleting the property before re-assigning it disables the
258      // getter/setter mechanism.
259      delete object[name];
260      object[name] = val;
261    };
262
263    ObjectDefineProperty(object, name, {
264      __proto__: null,
265      get: () => {
266        const lib = dummyModule.require(name);
267
268        try {
269          // Override the current getter/setter and set up a new
270          // non-enumerable property.
271          ObjectDefineProperty(object, name, {
272            __proto__: null,
273            get: () => lib,
274            set: setReal,
275            configurable: true,
276            enumerable: false,
277          });
278        } catch {
279          // If the property is no longer configurable, ignore the error.
280        }
281
282        return lib;
283      },
284      set: setReal,
285      configurable: true,
286      enumerable: false,
287    });
288  });
289}
290
291/**
292 * If a referrer is an URL instance or absolute path, convert it into an URL string.
293 * @param {string | URL} referrer
294 */
295function normalizeReferrerURL(referrer) {
296  if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
297    return pathToFileURL(referrer).href;
298  }
299  return new URL(referrer).href;
300}
301
302/**
303 * For error messages only, check if ESM syntax is in use.
304 * @param {string} code
305 */
306function hasEsmSyntax(code) {
307  debug('Checking for ESM syntax');
308  const parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
309  let root;
310  try {
311    root = parser.parse(code, { sourceType: 'module', ecmaVersion: 'latest' });
312  } catch {
313    return false;
314  }
315
316  return ArrayPrototypeSome(root.body, (stmt) =>
317    stmt.type === 'ExportDefaultDeclaration' ||
318    stmt.type === 'ExportNamedDeclaration' ||
319    stmt.type === 'ImportDeclaration' ||
320    stmt.type === 'ExportAllDeclaration');
321}
322
323module.exports = {
324  addBuiltinLibsToObject,
325  getCjsConditions,
326  initializeCjsConditions,
327  hasEsmSyntax,
328  loadBuiltinModule,
329  makeRequireFunction,
330  normalizeReferrerURL,
331  stripBOM,
332  toRealPath,
333};
334