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