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