• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypeJoin,
5  ObjectSetPrototypeOf,
6  PromiseAll,
7  SafeSet,
8  SafePromise,
9  StringPrototypeIncludes,
10  StringPrototypeMatch,
11  StringPrototypeReplace,
12  StringPrototypeSplit,
13} = primordials;
14
15const { ModuleWrap } = internalBinding('module_wrap');
16
17const { decorateErrorStack } = require('internal/util');
18const assert = require('internal/assert');
19const resolvedPromise = SafePromise.resolve();
20
21function noop() {}
22
23let hasPausedEntry = false;
24
25/* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of
26 * its dependencies, over time. */
27class ModuleJob {
28  // `loader` is the Loader instance used for loading dependencies.
29  // `moduleProvider` is a function
30  constructor(loader, url, moduleProvider, isMain, inspectBrk) {
31    this.loader = loader;
32    this.isMain = isMain;
33    this.inspectBrk = inspectBrk;
34
35    // This is a Promise<{ module, reflect }>, whose fields will be copied
36    // onto `this` by `link()` below once it has been resolved.
37    this.modulePromise = moduleProvider.call(loader, url, isMain);
38    this.module = undefined;
39
40    // Wait for the ModuleWrap instance being linked with all dependencies.
41    const link = async () => {
42      this.module = await this.modulePromise;
43      assert(this.module instanceof ModuleWrap);
44
45      const dependencyJobs = [];
46      const promises = this.module.link(async (specifier) => {
47        const jobPromise = this.loader.getModuleJob(specifier, url);
48        dependencyJobs.push(jobPromise);
49        return (await jobPromise).modulePromise;
50      });
51
52      if (promises !== undefined)
53        await SafePromise.all(promises);
54
55      return SafePromise.all(dependencyJobs);
56    };
57    // Promise for the list of all dependencyJobs.
58    this.linked = link();
59    // This promise is awaited later anyway, so silence
60    // 'unhandled rejection' warnings.
61    this.linked.catch(noop);
62
63    // instantiated == deep dependency jobs wrappers instantiated,
64    // module wrapper instantiated
65    this.instantiated = undefined;
66  }
67
68  async instantiate() {
69    if (!this.instantiated) {
70      return this.instantiated = this._instantiate();
71    }
72    await this.instantiated;
73    return this.module;
74  }
75
76  // This method instantiates the module associated with this job and its
77  // entire dependency graph, i.e. creates all the module namespaces and the
78  // exported/imported variables.
79  async _instantiate() {
80    const jobsInGraph = new SafeSet();
81
82    const addJobsToDependencyGraph = async (moduleJob) => {
83      if (jobsInGraph.has(moduleJob)) {
84        return;
85      }
86      jobsInGraph.add(moduleJob);
87      const dependencyJobs = await moduleJob.linked;
88      return PromiseAll(dependencyJobs.map(addJobsToDependencyGraph));
89    };
90    await addJobsToDependencyGraph(this);
91    try {
92      if (!hasPausedEntry && this.inspectBrk) {
93        hasPausedEntry = true;
94        const initWrapper = internalBinding('inspector').callAndPauseOnStart;
95        initWrapper(this.module.instantiate, this.module);
96      } else {
97        this.module.instantiate();
98      }
99    } catch (e) {
100      decorateErrorStack(e);
101      if (StringPrototypeIncludes(e.message,
102                                  ' does not provide an export named')) {
103        const splitStack = StringPrototypeSplit(e.stack, '\n');
104        const parentFileUrl = splitStack[0];
105        const [, childSpecifier, name] = StringPrototypeMatch(e.message,
106                                                              /module '(.*)' does not provide an export named '(.+)'/);
107        const childFileURL =
108            await this.loader.resolve(childSpecifier, parentFileUrl);
109        const format = await this.loader.getFormat(childFileURL);
110        if (format === 'commonjs') {
111          const importStatement = splitStack[1];
112          // TODO(@ctavan): The original error stack only provides the single
113          // line which causes the error. For multi-line import statements we
114          // cannot generate an equivalent object descructuring assignment by
115          // just parsing the error stack.
116          const oneLineNamedImports = StringPrototypeMatch(importStatement, /{.*}/);
117          const destructuringAssignment = oneLineNamedImports &&
118              StringPrototypeReplace(oneLineNamedImports, /\s+as\s+/g, ': ');
119          e.message = `Named export '${name}' not found. The requested module` +
120            ` '${childSpecifier}' is a CommonJS module, which may not support` +
121            ' all module.exports as named exports.\nCommonJS modules can ' +
122            'always be imported via the default export, for example using:' +
123            `\n\nimport pkg from '${childSpecifier}';\n${
124              destructuringAssignment ?
125                `const ${destructuringAssignment} = pkg;\n` : ''}`;
126          const newStack = StringPrototypeSplit(e.stack, '\n');
127          newStack[3] = `SyntaxError: ${e.message}`;
128          e.stack = ArrayPrototypeJoin(newStack, '\n');
129        }
130      }
131      throw e;
132    }
133    for (const dependencyJob of jobsInGraph) {
134      // Calling `this.module.instantiate()` instantiates not only the
135      // ModuleWrap in this module, but all modules in the graph.
136      dependencyJob.instantiated = resolvedPromise;
137    }
138    return this.module;
139  }
140
141  async run() {
142    const module = await this.instantiate();
143    const timeout = -1;
144    const breakOnSigint = false;
145    return { module, result: module.evaluate(timeout, breakOnSigint) };
146  }
147}
148ObjectSetPrototypeOf(ModuleJob.prototype, null);
149module.exports = ModuleJob;
150