1'use strict'; 2 3const { 4 ArrayPrototypeJoin, 5 ArrayPrototypePush, 6 ArrayPrototypeSome, 7 FunctionPrototype, 8 ObjectSetPrototypeOf, 9 PromiseResolve, 10 PromisePrototypeThen, 11 ReflectApply, 12 RegExpPrototypeExec, 13 RegExpPrototypeSymbolReplace, 14 SafePromiseAllReturnArrayLike, 15 SafePromiseAllReturnVoid, 16 SafeSet, 17 StringPrototypeIncludes, 18 StringPrototypeSplit, 19 StringPrototypeStartsWith, 20} = primordials; 21 22const { ModuleWrap } = internalBinding('module_wrap'); 23 24const { decorateErrorStack, kEmptyObject } = require('internal/util'); 25const { 26 getSourceMapsEnabled, 27} = require('internal/source_map/source_map_cache'); 28const assert = require('internal/assert'); 29const resolvedPromise = PromiseResolve(); 30 31const noop = FunctionPrototype; 32 33let hasPausedEntry = false; 34 35const CJSGlobalLike = [ 36 'require', 37 'module', 38 'exports', 39 '__filename', 40 '__dirname', 41]; 42const isCommonJSGlobalLikeNotDefinedError = (errorMessage) => 43 ArrayPrototypeSome( 44 CJSGlobalLike, 45 (globalLike) => errorMessage === `${globalLike} is not defined`, 46 ); 47 48/* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of 49 * its dependencies, over time. */ 50class ModuleJob { 51 // `loader` is the Loader instance used for loading dependencies. 52 // `moduleProvider` is a function 53 constructor(loader, url, importAttributes = { __proto__: null }, 54 moduleProvider, isMain, inspectBrk) { 55 this.loader = loader; 56 this.importAttributes = importAttributes; 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, attributes) => { 76 const job = await this.loader.getModuleJob(specifier, url, attributes); 77 ArrayPrototypePush(dependencyJobs, job); 78 return job.modulePromise; 79 }); 80 81 if (promises !== undefined) { 82 await SafePromiseAllReturnVoid(promises); 83 } 84 85 return SafePromiseAllReturnArrayLike(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 PromisePrototypeThen(this.linked, undefined, 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 SafePromiseAllReturnVoid(dependencyJobs, addJobsToDependencyGraph); 114 }; 115 await addJobsToDependencyGraph(this); 116 117 try { 118 if (!hasPausedEntry && this.inspectBrk) { 119 hasPausedEntry = true; 120 const initWrapper = internalBinding('inspector').callAndPauseOnStart; 121 initWrapper(this.module.instantiate, this.module); 122 } else { 123 this.module.instantiate(); 124 } 125 } catch (e) { 126 decorateErrorStack(e); 127 // TODO(@bcoe): Add source map support to exception that occurs as result 128 // of missing named export. This is currently not possible because 129 // stack trace originates in module_job, not the file itself. A hidden 130 // symbol with filename could be set in node_errors.cc to facilitate this. 131 if (!getSourceMapsEnabled() && 132 StringPrototypeIncludes(e.message, 133 ' does not provide an export named')) { 134 const splitStack = StringPrototypeSplit(e.stack, '\n'); 135 const parentFileUrl = RegExpPrototypeSymbolReplace( 136 /:\d+$/, 137 splitStack[0], 138 '', 139 ); 140 const { 1: childSpecifier, 2: name } = RegExpPrototypeExec( 141 /module '(.*)' does not provide an export named '(.+)'/, 142 e.message); 143 const { url: childFileURL } = await this.loader.resolve( 144 childSpecifier, 145 parentFileUrl, 146 kEmptyObject, 147 ); 148 let format; 149 try { 150 // This might throw for non-CommonJS modules because we aren't passing 151 // in the import attributes and some formats require them; but we only 152 // care about CommonJS for the purposes of this error message. 153 ({ format } = 154 await this.loader.load(childFileURL)); 155 } catch { 156 // Continue regardless of error. 157 } 158 159 if (format === 'commonjs') { 160 const importStatement = splitStack[1]; 161 // TODO(@ctavan): The original error stack only provides the single 162 // line which causes the error. For multi-line import statements we 163 // cannot generate an equivalent object destructuring assignment by 164 // just parsing the error stack. 165 const oneLineNamedImports = RegExpPrototypeExec(/{.*}/, importStatement); 166 const destructuringAssignment = oneLineNamedImports && 167 RegExpPrototypeSymbolReplace(/\s+as\s+/g, oneLineNamedImports, ': '); 168 e.message = `Named export '${name}' not found. The requested module` + 169 ` '${childSpecifier}' is a CommonJS module, which may not support` + 170 ' all module.exports as named exports.\nCommonJS modules can ' + 171 'always be imported via the default export, for example using:' + 172 `\n\nimport pkg from '${childSpecifier}';\n${ 173 destructuringAssignment ? 174 `const ${destructuringAssignment} = pkg;\n` : ''}`; 175 const newStack = StringPrototypeSplit(e.stack, '\n'); 176 newStack[3] = `SyntaxError: ${e.message}`; 177 e.stack = ArrayPrototypeJoin(newStack, '\n'); 178 } 179 } 180 throw e; 181 } 182 183 for (const dependencyJob of jobsInGraph) { 184 // Calling `this.module.instantiate()` instantiates not only the 185 // ModuleWrap in this module, but all modules in the graph. 186 dependencyJob.instantiated = resolvedPromise; 187 } 188 } 189 190 async run() { 191 await this.instantiate(); 192 const timeout = -1; 193 const breakOnSigint = false; 194 try { 195 await this.module.evaluate(timeout, breakOnSigint); 196 } catch (e) { 197 if (e?.name === 'ReferenceError' && 198 isCommonJSGlobalLikeNotDefinedError(e.message)) { 199 e.message += ' in ES module scope'; 200 201 if (StringPrototypeStartsWith(e.message, 'require ')) { 202 e.message += ', you can use import instead'; 203 } 204 205 const packageConfig = 206 StringPrototypeStartsWith(this.module.url, 'file://') && 207 RegExpPrototypeExec(/\.js(\?[^#]*)?(#.*)?$/, this.module.url) !== null && 208 require('internal/modules/esm/resolve') 209 .getPackageScopeConfig(this.module.url); 210 if (packageConfig.type === 'module') { 211 e.message += 212 '\nThis file is being treated as an ES module because it has a ' + 213 `'.js' file extension and '${packageConfig.pjsonPath}' contains ` + 214 '"type": "module". To treat it as a CommonJS script, rename it ' + 215 'to use the \'.cjs\' file extension.'; 216 } 217 } 218 throw e; 219 } 220 return { __proto__: null, module: this.module }; 221 } 222} 223ObjectSetPrototypeOf(ModuleJob.prototype, null); 224module.exports = ModuleJob; 225