1'use strict'; 2 3// This is needed to avoid cycles in esm/resolve <-> cjs/loader 4require('internal/modules/cjs/loader'); 5 6const { 7 FunctionPrototypeBind, 8 ObjectSetPrototypeOf, 9 RegExpPrototypeExec, 10 SafeWeakMap, 11 StringPrototypeStartsWith, 12 globalThis, 13} = primordials; 14 15const { 16 ERR_INVALID_ARG_TYPE, 17 ERR_INVALID_ARG_VALUE, 18 ERR_INVALID_MODULE_SPECIFIER, 19 ERR_INVALID_RETURN_PROPERTY, 20 ERR_INVALID_RETURN_PROPERTY_VALUE, 21 ERR_INVALID_RETURN_VALUE, 22 ERR_UNKNOWN_MODULE_FORMAT 23} = require('internal/errors').codes; 24const { URL, pathToFileURL, isURLInstance } = require('internal/url'); 25const ModuleMap = require('internal/modules/esm/module_map'); 26const ModuleJob = require('internal/modules/esm/module_job'); 27 28const { 29 defaultResolve, 30 DEFAULT_CONDITIONS, 31} = require('internal/modules/esm/resolve'); 32const { defaultGetFormat } = require('internal/modules/esm/get_format'); 33const { defaultGetSource } = require( 34 'internal/modules/esm/get_source'); 35const { defaultTransformSource } = require( 36 'internal/modules/esm/transform_source'); 37const { translators } = require( 38 'internal/modules/esm/translators'); 39const { getOptionValue } = require('internal/options'); 40 41/* A Loader instance is used as the main entry point for loading ES modules. 42 * Currently, this is a singleton -- there is only one used for loading 43 * the main module and everything in its dependency graph. */ 44class Loader { 45 constructor() { 46 // Methods which translate input code or other information 47 // into es modules 48 this.translators = translators; 49 50 // Registry of loaded modules, akin to `require.cache` 51 this.moduleMap = new ModuleMap(); 52 53 // Map of already-loaded CJS modules to use 54 this.cjsCache = new SafeWeakMap(); 55 56 // This hook is called before the first root module is imported. It's a 57 // function that returns a piece of code that runs as a sloppy-mode script. 58 // The script may evaluate to a function that can be called with a 59 // `getBuiltin` helper that can be used to retrieve builtins. 60 // If the hook returns `null` instead of a source string, it opts out of 61 // running any preload code. 62 // The preload code runs as soon as the hook module has finished evaluating. 63 this._getGlobalPreloadCode = null; 64 // The resolver has the signature 65 // (specifier : string, parentURL : string, defaultResolve) 66 // -> Promise<{ url : string }> 67 // where defaultResolve is ModuleRequest.resolve (having the same 68 // signature itself). 69 this._resolve = defaultResolve; 70 // This hook is called after the module is resolved but before a translator 71 // is chosen to load it; the format returned by this function is the name 72 // of a translator. 73 this._getFormat = defaultGetFormat; 74 // This hook is called just before the source code of an ES module file 75 // is loaded. 76 this._getSource = defaultGetSource; 77 // This hook is called just after the source code of an ES module file 78 // is loaded, but before anything is done with the string. 79 this._transformSource = defaultTransformSource; 80 // The index for assigning unique URLs to anonymous module evaluation 81 this.evalIndex = 0; 82 } 83 84 async resolve(specifier, parentURL) { 85 const isMain = parentURL === undefined; 86 if (!isMain && typeof parentURL !== 'string' && !isURLInstance(parentURL)) 87 throw new ERR_INVALID_ARG_TYPE('parentURL', ['string', 'URL'], parentURL); 88 89 const resolveResponse = await this._resolve( 90 specifier, { parentURL, conditions: DEFAULT_CONDITIONS }, defaultResolve); 91 if (typeof resolveResponse !== 'object') { 92 throw new ERR_INVALID_RETURN_VALUE( 93 'object', 'loader resolve', resolveResponse); 94 } 95 96 const { url } = resolveResponse; 97 if (typeof url !== 'string') { 98 throw new ERR_INVALID_RETURN_PROPERTY_VALUE( 99 'string', 'loader resolve', 'url', url); 100 } 101 return url; 102 } 103 104 async getFormat(url) { 105 const getFormatResponse = await this._getFormat( 106 url, {}, defaultGetFormat); 107 if (typeof getFormatResponse !== 'object') { 108 throw new ERR_INVALID_RETURN_VALUE( 109 'object', 'loader getFormat', getFormatResponse); 110 } 111 112 const { format } = getFormatResponse; 113 if (format === null) { 114 const dataUrl = RegExpPrototypeExec( 115 /^data:([^/]+\/[^;,]+)(?:[^,]*?)(;base64)?,/, 116 url, 117 ); 118 throw new ERR_INVALID_MODULE_SPECIFIER( 119 url, 120 dataUrl ? `has an unsupported MIME type "${dataUrl[1]}"` : ''); 121 } 122 if (typeof format !== 'string') { 123 throw new ERR_INVALID_RETURN_PROPERTY_VALUE( 124 'string', 'loader getFormat', 'format', format); 125 } 126 127 if (format === 'builtin') { 128 return format; 129 } 130 131 if (this._resolve !== defaultResolve) { 132 try { 133 new URL(url); 134 } catch { 135 throw new ERR_INVALID_RETURN_PROPERTY( 136 'url', 'loader resolve', 'url', url 137 ); 138 } 139 } 140 141 if (this._resolve === defaultResolve && 142 !StringPrototypeStartsWith(url, 'file:') && 143 !StringPrototypeStartsWith(url, 'data:') 144 ) { 145 throw new ERR_INVALID_RETURN_PROPERTY( 146 'file: or data: url', 'loader resolve', 'url', url 147 ); 148 } 149 150 return format; 151 } 152 153 async eval( 154 source, 155 url = pathToFileURL(`${process.cwd()}/[eval${++this.evalIndex}]`).href 156 ) { 157 const evalInstance = (url) => { 158 const { ModuleWrap, callbackMap } = internalBinding('module_wrap'); 159 const module = new ModuleWrap(url, undefined, source, 0, 0); 160 callbackMap.set(module, { 161 importModuleDynamically: (specifier, { url }) => { 162 return this.import(specifier, url); 163 } 164 }); 165 166 return module; 167 }; 168 const job = new ModuleJob(this, url, evalInstance, false, false); 169 this.moduleMap.set(url, job); 170 const { module } = await job.run(); 171 return { 172 namespace: module.getNamespace(), 173 }; 174 } 175 176 async import(specifier, parent) { 177 const job = await this.getModuleJob(specifier, parent); 178 const { module } = await job.run(); 179 return module.getNamespace(); 180 } 181 182 hook(hooks) { 183 const { 184 resolve, 185 dynamicInstantiate, 186 getFormat, 187 getSource, 188 transformSource, 189 getGlobalPreloadCode, 190 } = hooks; 191 192 // Use .bind() to avoid giving access to the Loader instance when called. 193 if (resolve !== undefined) 194 this._resolve = FunctionPrototypeBind(resolve, null); 195 if (dynamicInstantiate !== undefined) { 196 process.emitWarning( 197 'The dynamicInstantiate loader hook has been removed.'); 198 } 199 if (getFormat !== undefined) { 200 this._getFormat = FunctionPrototypeBind(getFormat, null); 201 } 202 if (getSource !== undefined) { 203 this._getSource = FunctionPrototypeBind(getSource, null); 204 } 205 if (transformSource !== undefined) { 206 this._transformSource = FunctionPrototypeBind(transformSource, null); 207 } 208 if (getGlobalPreloadCode !== undefined) { 209 this._getGlobalPreloadCode = 210 FunctionPrototypeBind(getGlobalPreloadCode, null); 211 } 212 } 213 214 runGlobalPreloadCode() { 215 if (!this._getGlobalPreloadCode) { 216 return; 217 } 218 const preloadCode = this._getGlobalPreloadCode(); 219 if (preloadCode === null) { 220 return; 221 } 222 223 if (typeof preloadCode !== 'string') { 224 throw new ERR_INVALID_RETURN_VALUE( 225 'string', 'loader getGlobalPreloadCode', preloadCode); 226 } 227 const { compileFunction } = require('vm'); 228 const preloadInit = compileFunction(preloadCode, ['getBuiltin'], { 229 filename: '<preload>', 230 }); 231 const { NativeModule } = require('internal/bootstrap/loaders'); 232 233 preloadInit.call(globalThis, (builtinName) => { 234 if (NativeModule.canBeRequiredByUsers(builtinName)) { 235 return require(builtinName); 236 } 237 throw new ERR_INVALID_ARG_VALUE('builtinName', builtinName); 238 }); 239 } 240 241 async getModuleJob(specifier, parentURL) { 242 const url = await this.resolve(specifier, parentURL); 243 const format = await this.getFormat(url); 244 let job = this.moduleMap.get(url); 245 // CommonJS will set functions for lazy job evaluation. 246 if (typeof job === 'function') 247 this.moduleMap.set(url, job = job()); 248 if (job !== undefined) 249 return job; 250 251 if (!translators.has(format)) 252 throw new ERR_UNKNOWN_MODULE_FORMAT(format); 253 254 const loaderInstance = translators.get(format); 255 256 const inspectBrk = parentURL === undefined && 257 format === 'module' && getOptionValue('--inspect-brk'); 258 job = new ModuleJob(this, url, loaderInstance, parentURL === undefined, 259 inspectBrk); 260 this.moduleMap.set(url, job); 261 return job; 262 } 263} 264 265ObjectSetPrototypeOf(Loader.prototype, null); 266 267exports.Loader = Loader; 268