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