• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3// This is needed to avoid cycles in esm/resolve <-> cjs/loader
4require('internal/modules/cjs/loader');
5
6const {
7  FunctionPrototypeBind,
8  ObjectSetPrototypeOf,
9  SafeWeakMap,
10} = primordials;
11
12const {
13  ERR_INVALID_ARG_VALUE,
14  ERR_INVALID_RETURN_PROPERTY,
15  ERR_INVALID_RETURN_PROPERTY_VALUE,
16  ERR_INVALID_RETURN_VALUE,
17  ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK,
18  ERR_UNKNOWN_MODULE_FORMAT
19} = require('internal/errors').codes;
20const { URL, pathToFileURL } = require('internal/url');
21const { validateString } = require('internal/validators');
22const ModuleMap = require('internal/modules/esm/module_map');
23const ModuleJob = require('internal/modules/esm/module_job');
24
25const {
26  defaultResolve,
27  DEFAULT_CONDITIONS,
28} = require('internal/modules/esm/resolve');
29const { defaultGetFormat } = require('internal/modules/esm/get_format');
30const { defaultGetSource } = require(
31  'internal/modules/esm/get_source');
32const { defaultTransformSource } = require(
33  'internal/modules/esm/transform_source');
34const createDynamicModule = require(
35  'internal/modules/esm/create_dynamic_module');
36const { translators } = require(
37  'internal/modules/esm/translators');
38const { getOptionValue } = require('internal/options');
39
40const debug = require('internal/util/debuglog').debuglog('esm');
41
42/* A Loader instance is used as the main entry point for loading ES modules.
43 * Currently, this is a singleton -- there is only one used for loading
44 * the main module and everything in its dependency graph. */
45class Loader {
46  constructor() {
47    // Methods which translate input code or other information
48    // into es modules
49    this.translators = translators;
50
51    // Registry of loaded modules, akin to `require.cache`
52    this.moduleMap = new ModuleMap();
53
54    // Map of already-loaded CJS modules to use
55    this.cjsCache = new SafeWeakMap();
56
57    // This hook is called before the first root module is imported. It's a
58    // function that returns a piece of code that runs as a sloppy-mode script.
59    // The script may evaluate to a function that can be called with a
60    // `getBuiltin` helper that can be used to retrieve builtins.
61    // If the hook returns `null` instead of a source string, it opts out of
62    // running any preload code.
63    // The preload code runs as soon as the hook module has finished evaluating.
64    this._getGlobalPreloadCode = null;
65    // The resolver has the signature
66    //   (specifier : string, parentURL : string, defaultResolve)
67    //       -> Promise<{ url : string }>
68    // where defaultResolve is ModuleRequest.resolve (having the same
69    // signature itself).
70    this._resolve = defaultResolve;
71    // This hook is called after the module is resolved but before a translator
72    // is chosen to load it; the format returned by this function is the name
73    // of a translator.
74    // If `.format` on the returned value is 'dynamic', .dynamicInstantiate
75    // will be used as described below.
76    this._getFormat = defaultGetFormat;
77    // This hook is called just before the source code of an ES module file
78    // is loaded.
79    this._getSource = defaultGetSource;
80    // This hook is called just after the source code of an ES module file
81    // is loaded, but before anything is done with the string.
82    this._transformSource = defaultTransformSource;
83    // This hook is only called when getFormat is 'dynamic' and
84    // has the signature
85    //   (url : string) -> Promise<{ exports: { ... }, execute: function }>
86    // Where `exports` is an object whose property names define the exported
87    // names of the generated module. `execute` is a function that receives
88    // an object with the same keys as `exports`, whose values are get/set
89    // functions for the actual exported values.
90    this._dynamicInstantiate = undefined;
91    // The index for assigning unique URLs to anonymous module evaluation
92    this.evalIndex = 0;
93  }
94
95  async resolve(specifier, parentURL) {
96    const isMain = parentURL === undefined;
97    if (!isMain)
98      validateString(parentURL, 'parentURL');
99
100    const resolveResponse = await this._resolve(
101      specifier, { parentURL, conditions: DEFAULT_CONDITIONS }, defaultResolve);
102    if (typeof resolveResponse !== 'object') {
103      throw new ERR_INVALID_RETURN_VALUE(
104        'object', 'loader resolve', resolveResponse);
105    }
106
107    const { url } = resolveResponse;
108    if (typeof url !== 'string') {
109      throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
110        'string', 'loader resolve', 'url', url);
111    }
112    return url;
113  }
114
115  async getFormat(url) {
116    const getFormatResponse = await this._getFormat(
117      url, {}, defaultGetFormat);
118    if (typeof getFormatResponse !== 'object') {
119      throw new ERR_INVALID_RETURN_VALUE(
120        'object', 'loader getFormat', getFormatResponse);
121    }
122
123    const { format } = getFormatResponse;
124    if (typeof format !== 'string') {
125      throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
126        'string', 'loader getFormat', 'format', format);
127    }
128
129    if (format === 'builtin') {
130      return format;
131    }
132
133    if (this._resolve !== defaultResolve) {
134      try {
135        new URL(url);
136      } catch {
137        throw new ERR_INVALID_RETURN_PROPERTY(
138          'url', 'loader resolve', 'url', url
139        );
140      }
141    }
142
143    if (this._resolve === defaultResolve &&
144      format !== 'dynamic' &&
145      !url.startsWith('file:') &&
146      !url.startsWith('data:')
147    ) {
148      throw new ERR_INVALID_RETURN_PROPERTY(
149        'file: or data: url', 'loader resolve', 'url', url
150      );
151    }
152
153    return format;
154  }
155
156  async eval(
157    source,
158    url = pathToFileURL(`${process.cwd()}/[eval${++this.evalIndex}]`).href
159  ) {
160    const evalInstance = (url) => {
161      const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
162      const module = new ModuleWrap(url, undefined, source, 0, 0);
163      callbackMap.set(module, {
164        importModuleDynamically: (specifier, { url }) => {
165          return this.import(specifier, url);
166        }
167      });
168
169      return module;
170    };
171    const job = new ModuleJob(this, url, evalInstance, false, false);
172    this.moduleMap.set(url, job);
173    const { module, result } = await job.run();
174    return {
175      namespace: module.getNamespace(),
176      result
177    };
178  }
179
180  async import(specifier, parent) {
181    const job = await this.getModuleJob(specifier, parent);
182    const { module } = await job.run();
183    return module.getNamespace();
184  }
185
186  hook(hooks) {
187    const {
188      resolve,
189      dynamicInstantiate,
190      getFormat,
191      getSource,
192      transformSource,
193      getGlobalPreloadCode,
194    } = hooks;
195
196    // Use .bind() to avoid giving access to the Loader instance when called.
197    if (resolve !== undefined)
198      this._resolve = FunctionPrototypeBind(resolve, null);
199    if (dynamicInstantiate !== undefined) {
200      this._dynamicInstantiate =
201        FunctionPrototypeBind(dynamicInstantiate, null);
202    }
203    if (getFormat !== undefined) {
204      this._getFormat = FunctionPrototypeBind(getFormat, null);
205    }
206    if (getSource !== undefined) {
207      this._getSource = FunctionPrototypeBind(getSource, null);
208    }
209    if (transformSource !== undefined) {
210      this._transformSource = FunctionPrototypeBind(transformSource, null);
211    }
212    if (getGlobalPreloadCode !== undefined) {
213      this._getGlobalPreloadCode =
214        FunctionPrototypeBind(getGlobalPreloadCode, null);
215    }
216  }
217
218  runGlobalPreloadCode() {
219    if (!this._getGlobalPreloadCode) {
220      return;
221    }
222    const preloadCode = this._getGlobalPreloadCode();
223    if (preloadCode === null) {
224      return;
225    }
226
227    if (typeof preloadCode !== 'string') {
228      throw new ERR_INVALID_RETURN_VALUE(
229        'string', 'loader getGlobalPreloadCode', preloadCode);
230    }
231    const { compileFunction } = require('vm');
232    const preloadInit = compileFunction(preloadCode, ['getBuiltin'], {
233      filename: '<preload>',
234    });
235    const { NativeModule } = require('internal/bootstrap/loaders');
236
237    preloadInit.call(globalThis, (builtinName) => {
238      if (NativeModule.canBeRequiredByUsers(builtinName)) {
239        return require(builtinName);
240      }
241      throw new ERR_INVALID_ARG_VALUE('builtinName', builtinName);
242    });
243  }
244
245  async getModuleJob(specifier, parentURL) {
246    const url = await this.resolve(specifier, parentURL);
247    const format = await this.getFormat(url);
248    let job = this.moduleMap.get(url);
249    // CommonJS will set functions for lazy job evaluation.
250    if (typeof job === 'function')
251      this.moduleMap.set(url, job = job());
252    if (job !== undefined)
253      return job;
254
255    let loaderInstance;
256    if (format === 'dynamic') {
257      if (typeof this._dynamicInstantiate !== 'function')
258        throw new ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK();
259
260      loaderInstance = async (url) => {
261        debug(`Translating dynamic ${url}`);
262        const { exports, execute } = await this._dynamicInstantiate(url);
263        return createDynamicModule([], exports, url, (reflect) => {
264          debug(`Loading dynamic ${url}`);
265          execute(reflect.exports);
266        }).module;
267      };
268    } else {
269      if (!translators.has(format))
270        throw new ERR_UNKNOWN_MODULE_FORMAT(format);
271
272      loaderInstance = translators.get(format);
273    }
274
275    const inspectBrk = parentURL === undefined &&
276        format === 'module' && getOptionValue('--inspect-brk');
277    job = new ModuleJob(this, url, loaderInstance, parentURL === undefined,
278                        inspectBrk);
279    this.moduleMap.set(url, job);
280    return job;
281  }
282}
283
284ObjectSetPrototypeOf(Loader.prototype, null);
285
286exports.Loader = Loader;
287