• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypeJoin,
5  ArrayPrototypeMap,
6  ArrayPrototypePush,
7  ArrayPrototypeSome,
8  FunctionPrototype,
9  ObjectSetPrototypeOf,
10  PromiseAll,
11  PromiseResolve,
12  PromisePrototypeCatch,
13  ReflectApply,
14  RegExpPrototypeTest,
15  SafeArrayIterator,
16  SafeSet,
17  StringPrototypeIncludes,
18  StringPrototypeMatch,
19  StringPrototypeReplace,
20  StringPrototypeSplit,
21  StringPrototypeStartsWith,
22} = primordials;
23
24const { ModuleWrap } = internalBinding('module_wrap');
25
26const { decorateErrorStack } = require('internal/util');
27const {
28  getSourceMapsEnabled,
29} = require('internal/source_map/source_map_cache');
30const assert = require('internal/assert');
31const resolvedPromise = PromiseResolve();
32
33const noop = FunctionPrototype;
34
35let hasPausedEntry = false;
36
37const CJSGlobalLike = [
38  'require',
39  'module',
40  'exports',
41  '__filename',
42  '__dirname',
43];
44const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
45  ArrayPrototypeSome(
46    CJSGlobalLike,
47    (globalLike) => errorMessage === `${globalLike} is not defined`
48  );
49
50/* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of
51 * its dependencies, over time. */
52class ModuleJob {
53  // `loader` is the Loader instance used for loading dependencies.
54  // `moduleProvider` is a function
55  constructor(loader, url, moduleProvider, isMain, inspectBrk) {
56    this.loader = loader;
57    this.isMain = isMain;
58    this.inspectBrk = inspectBrk;
59
60    this.module = undefined;
61    // Expose the promise to the ModuleWrap directly for linking below.
62    // `this.module` is also filled in below.
63    this.modulePromise = ReflectApply(moduleProvider, loader, [url, isMain]);
64
65    // Wait for the ModuleWrap instance being linked with all dependencies.
66    const link = async () => {
67      this.module = await this.modulePromise;
68      assert(this.module instanceof ModuleWrap);
69
70      // Explicitly keeping track of dependency jobs is needed in order
71      // to flatten out the dependency graph below in `_instantiate()`,
72      // so that circular dependencies can't cause a deadlock by two of
73      // these `link` callbacks depending on each other.
74      const dependencyJobs = [];
75      const promises = this.module.link(async (specifier) => {
76        const jobPromise = this.loader.getModuleJob(specifier, url);
77        ArrayPrototypePush(dependencyJobs, jobPromise);
78        const job = await jobPromise;
79        return job.modulePromise;
80      });
81
82      if (promises !== undefined)
83        await PromiseAll(new SafeArrayIterator(promises));
84
85      return PromiseAll(new SafeArrayIterator(dependencyJobs));
86    };
87    // Promise for the list of all dependencyJobs.
88    this.linked = link();
89    // This promise is awaited later anyway, so silence
90    // 'unhandled rejection' warnings.
91    PromisePrototypeCatch(this.linked, noop);
92
93    // instantiated == deep dependency jobs wrappers are instantiated,
94    // and module wrapper is instantiated.
95    this.instantiated = undefined;
96  }
97
98  instantiate() {
99    if (this.instantiated === undefined) {
100      this.instantiated = this._instantiate();
101    }
102    return this.instantiated;
103  }
104
105  async _instantiate() {
106    const jobsInGraph = new SafeSet();
107    const addJobsToDependencyGraph = async (moduleJob) => {
108      if (jobsInGraph.has(moduleJob)) {
109        return;
110      }
111      jobsInGraph.add(moduleJob);
112      const dependencyJobs = await moduleJob.linked;
113      return PromiseAll(new SafeArrayIterator(
114        ArrayPrototypeMap(dependencyJobs, addJobsToDependencyGraph)));
115    };
116    await addJobsToDependencyGraph(this);
117
118    try {
119      if (!hasPausedEntry && this.inspectBrk) {
120        hasPausedEntry = true;
121        const initWrapper = internalBinding('inspector').callAndPauseOnStart;
122        initWrapper(this.module.instantiate, this.module);
123      } else {
124        this.module.instantiate();
125      }
126    } catch (e) {
127      decorateErrorStack(e);
128      // TODO(@bcoe): Add source map support to exception that occurs as result
129      // of missing named export. This is currently not possible because
130      // stack trace originates in module_job, not the file itself. A hidden
131      // symbol with filename could be set in node_errors.cc to facilitate this.
132      if (!getSourceMapsEnabled() &&
133          StringPrototypeIncludes(e.message,
134                                  ' does not provide an export named')) {
135        const splitStack = StringPrototypeSplit(e.stack, '\n');
136        const parentFileUrl = StringPrototypeReplace(
137          splitStack[0],
138          /:\d+$/,
139          ''
140        );
141        const { 1: childSpecifier, 2: name } = StringPrototypeMatch(
142          e.message,
143          /module '(.*)' does not provide an export named '(.+)'/);
144        const childFileURL =
145            await this.loader.resolve(childSpecifier, parentFileUrl);
146        const format = await this.loader.getFormat(childFileURL);
147        if (format === 'commonjs') {
148          const importStatement = splitStack[1];
149          // TODO(@ctavan): The original error stack only provides the single
150          // line which causes the error. For multi-line import statements we
151          // cannot generate an equivalent object destructuring assignment by
152          // just parsing the error stack.
153          const oneLineNamedImports = StringPrototypeMatch(importStatement, /{.*}/);
154          const destructuringAssignment = oneLineNamedImports &&
155              StringPrototypeReplace(oneLineNamedImports, /\s+as\s+/g, ': ');
156          e.message = `Named export '${name}' not found. The requested module` +
157            ` '${childSpecifier}' is a CommonJS module, which may not support` +
158            ' all module.exports as named exports.\nCommonJS modules can ' +
159            'always be imported via the default export, for example using:' +
160            `\n\nimport pkg from '${childSpecifier}';\n${
161              destructuringAssignment ?
162                `const ${destructuringAssignment} = pkg;\n` : ''}`;
163          const newStack = StringPrototypeSplit(e.stack, '\n');
164          newStack[3] = `SyntaxError: ${e.message}`;
165          e.stack = ArrayPrototypeJoin(newStack, '\n');
166        }
167      }
168      throw e;
169    }
170
171    for (const dependencyJob of jobsInGraph) {
172      // Calling `this.module.instantiate()` instantiates not only the
173      // ModuleWrap in this module, but all modules in the graph.
174      dependencyJob.instantiated = resolvedPromise;
175    }
176  }
177
178  async run() {
179    await this.instantiate();
180    const timeout = -1;
181    const breakOnSigint = false;
182    try {
183      await this.module.evaluate(timeout, breakOnSigint);
184    } catch (e) {
185      if (e?.name === 'ReferenceError' &&
186          isCommonJSGlobalLikeNotDefinedError(e.message)) {
187        e.message += ' in ES module scope';
188
189        if (StringPrototypeStartsWith(e.message, 'require ')) {
190          e.message += ', you can use import instead';
191        }
192
193        const packageConfig =
194          StringPrototypeStartsWith(this.module.url, 'file://') &&
195            RegExpPrototypeTest(/\.js(\?[^#]*)?(#.*)?$/, this.module.url) &&
196            require('internal/modules/esm/resolve')
197              .getPackageScopeConfig(this.module.url);
198        if (packageConfig.type === 'module') {
199          e.message +=
200            '\nThis file is being treated as an ES module because it has a ' +
201            `'.js' file extension and '${packageConfig.pjsonPath}' contains ` +
202            '"type": "module". To treat it as a CommonJS script, rename it ' +
203            'to use the \'.cjs\' file extension.';
204        }
205      }
206      throw e;
207    }
208    return { module: this.module };
209  }
210}
211ObjectSetPrototypeOf(ModuleJob.prototype, null);
212module.exports = ModuleJob;
213