1'use strict'; 2 3const { 4 ArrayPrototypeMap, 5 Boolean, 6 JSONParse, 7 ObjectGetPrototypeOf, 8 ObjectPrototypeHasOwnProperty, 9 ObjectKeys, 10 PromisePrototypeCatch, 11 PromiseReject, 12 RegExpPrototypeTest, 13 SafeMap, 14 SafeSet, 15 StringPrototypeReplace, 16 StringPrototypeSlice, 17 StringPrototypeSplit, 18 StringPrototypeStartsWith, 19 SyntaxErrorPrototype, 20 globalThis: { WebAssembly }, 21} = primordials; 22 23let _TYPES = null; 24function lazyTypes() { 25 if (_TYPES !== null) return _TYPES; 26 return _TYPES = require('internal/util/types'); 27} 28 29const { readFileSync } = require('fs'); 30const { extname, isAbsolute } = require('path'); 31const { 32 stripBOM, 33 loadNativeModule 34} = require('internal/modules/cjs/helpers'); 35const { 36 Module: CJSModule, 37 cjsParseCache 38} = require('internal/modules/cjs/loader'); 39const internalURLModule = require('internal/url'); 40const { defaultGetSource } = require( 41 'internal/modules/esm/get_source'); 42const { defaultTransformSource } = require( 43 'internal/modules/esm/transform_source'); 44const createDynamicModule = require( 45 'internal/modules/esm/create_dynamic_module'); 46const { fileURLToPath, URL } = require('url'); 47let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { 48 debug = fn; 49}); 50const { emitExperimentalWarning } = require('internal/util'); 51const { 52 ERR_UNKNOWN_BUILTIN_MODULE, 53 ERR_INVALID_RETURN_PROPERTY_VALUE 54} = require('internal/errors').codes; 55const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache'); 56const moduleWrap = internalBinding('module_wrap'); 57const { ModuleWrap } = moduleWrap; 58const { getOptionValue } = require('internal/options'); 59const experimentalImportMetaResolve = 60 getOptionValue('--experimental-import-meta-resolve'); 61const asyncESM = require('internal/process/esm_loader'); 62const { emitWarningSync } = require('internal/process/warning'); 63const { TextDecoder } = require('internal/encoding'); 64 65let cjsParse; 66async function initCJSParse() { 67 if (typeof WebAssembly === 'undefined') { 68 cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse; 69 } else { 70 const { parse, init } = 71 require('internal/deps/cjs-module-lexer/dist/lexer'); 72 try { 73 await init(); 74 cjsParse = parse; 75 } catch { 76 cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse; 77 } 78 } 79} 80 81const translators = new SafeMap(); 82exports.translators = translators; 83exports.enrichCJSError = enrichCJSError; 84 85let DECODER = null; 86function assertBufferSource(body, allowString, hookName) { 87 if (allowString && typeof body === 'string') { 88 return; 89 } 90 const { isArrayBufferView, isAnyArrayBuffer } = lazyTypes(); 91 if (isArrayBufferView(body) || isAnyArrayBuffer(body)) { 92 return; 93 } 94 throw new ERR_INVALID_RETURN_PROPERTY_VALUE( 95 `${allowString ? 'string, ' : ''}array buffer, or typed array`, 96 hookName, 97 'source', 98 body 99 ); 100} 101 102function stringify(body) { 103 if (typeof body === 'string') return body; 104 assertBufferSource(body, false, 'transformSource'); 105 DECODER = DECODER === null ? new TextDecoder() : DECODER; 106 return DECODER.decode(body); 107} 108 109function errPath(url) { 110 const parsed = new URL(url); 111 if (parsed.protocol === 'file:') { 112 return fileURLToPath(parsed); 113 } 114 return url; 115} 116 117async function importModuleDynamically(specifier, { url }) { 118 return asyncESM.ESMLoader.import(specifier, url); 119} 120 121function createImportMetaResolve(defaultParentUrl) { 122 return async function resolve(specifier, parentUrl = defaultParentUrl) { 123 return PromisePrototypeCatch( 124 asyncESM.ESMLoader.resolve(specifier, parentUrl), 125 (error) => ( 126 error.code === 'ERR_UNSUPPORTED_DIR_IMPORT' ? 127 error.url : PromiseReject(error)) 128 ); 129 }; 130} 131 132function initializeImportMeta(meta, { url }) { 133 // Alphabetical 134 if (experimentalImportMetaResolve) 135 meta.resolve = createImportMetaResolve(url); 136 meta.url = url; 137} 138 139// Strategy for loading a standard JavaScript module. 140translators.set('module', async function moduleStrategy(url) { 141 let { source } = await this._getSource( 142 url, { format: 'module' }, defaultGetSource); 143 assertBufferSource(source, true, 'getSource'); 144 ({ source } = await this._transformSource( 145 source, { url, format: 'module' }, defaultTransformSource)); 146 source = stringify(source); 147 maybeCacheSourceMap(url, source); 148 debug(`Translating StandardModule ${url}`); 149 const module = new ModuleWrap(url, undefined, source, 0, 0); 150 moduleWrap.callbackMap.set(module, { 151 initializeImportMeta, 152 importModuleDynamically, 153 }); 154 return module; 155}); 156 157function enrichCJSError(err) { 158 if (err == null || ObjectGetPrototypeOf(err) !== SyntaxErrorPrototype) { 159 return; 160 } 161 const stack = StringPrototypeSplit(err.stack, '\n'); 162 /* 163 * The regular expression below targets the most common import statement 164 * usage. However, some cases are not matching, cases like import statement 165 * after a comment block and/or after a variable definition. 166 */ 167 if (StringPrototypeStartsWith(err.message, 'Unexpected token \'export\'') || 168 RegExpPrototypeTest(/^\s*import(?=[ {'"*])\s*(?![ (])/, stack[1])) { 169 // Emit the warning synchronously because we are in the middle of handling 170 // a SyntaxError that will throw and likely terminate the process before an 171 // asynchronous warning would be emitted. 172 emitWarningSync( 173 'To load an ES module, set "type": "module" in the package.json or use ' + 174 'the .mjs extension.' 175 ); 176 } 177} 178 179// Strategy for loading a node-style CommonJS module 180const isWindows = process.platform === 'win32'; 181const winSepRegEx = /\//g; 182translators.set('commonjs', async function commonjsStrategy(url, isMain) { 183 debug(`Translating CJSModule ${url}`); 184 185 let filename = internalURLModule.fileURLToPath(new URL(url)); 186 if (isWindows) 187 filename = StringPrototypeReplace(filename, winSepRegEx, '\\'); 188 189 if (!cjsParse) await initCJSParse(); 190 const { module, exportNames } = cjsPreparseModuleExports(filename); 191 const namesWithDefault = exportNames.has('default') ? 192 [...exportNames] : ['default', ...exportNames]; 193 194 return new ModuleWrap(url, undefined, namesWithDefault, function() { 195 debug(`Loading CJSModule ${url}`); 196 197 let exports; 198 if (asyncESM.ESMLoader.cjsCache.has(module)) { 199 exports = asyncESM.ESMLoader.cjsCache.get(module); 200 asyncESM.ESMLoader.cjsCache.delete(module); 201 } else { 202 try { 203 exports = CJSModule._load(filename, undefined, isMain); 204 } catch (err) { 205 enrichCJSError(err); 206 throw err; 207 } 208 } 209 210 for (const exportName of exportNames) { 211 if (!ObjectPrototypeHasOwnProperty(exports, exportName) || 212 exportName === 'default') 213 continue; 214 // We might trigger a getter -> dont fail. 215 let value; 216 try { 217 value = exports[exportName]; 218 } catch {} 219 this.setExport(exportName, value); 220 } 221 this.setExport('default', exports); 222 }); 223}); 224 225function cjsPreparseModuleExports(filename) { 226 let module = CJSModule._cache[filename]; 227 if (module) { 228 const cached = cjsParseCache.get(module); 229 if (cached) 230 return { module, exportNames: cached.exportNames }; 231 } 232 const loaded = Boolean(module); 233 if (!loaded) { 234 module = new CJSModule(filename); 235 module.filename = filename; 236 module.paths = CJSModule._nodeModulePaths(module.path); 237 CJSModule._cache[filename] = module; 238 } 239 240 let source; 241 try { 242 source = readFileSync(filename, 'utf8'); 243 } catch {} 244 245 let exports, reexports; 246 try { 247 ({ exports, reexports } = cjsParse(source || '')); 248 } catch { 249 exports = []; 250 reexports = []; 251 } 252 253 const exportNames = new SafeSet(exports); 254 255 // Set first for cycles. 256 cjsParseCache.set(module, { source, exportNames, loaded }); 257 258 if (reexports.length) { 259 module.filename = filename; 260 module.paths = CJSModule._nodeModulePaths(module.path); 261 } 262 for (const reexport of reexports) { 263 let resolved; 264 try { 265 resolved = CJSModule._resolveFilename(reexport, module); 266 } catch { 267 continue; 268 } 269 const ext = extname(resolved); 270 if ((ext === '.js' || ext === '.cjs' || !CJSModule._extensions[ext]) && 271 isAbsolute(resolved)) { 272 const { exportNames: reexportNames } = cjsPreparseModuleExports(resolved); 273 for (const name of reexportNames) 274 exportNames.add(name); 275 } 276 } 277 278 return { module, exportNames }; 279} 280 281// Strategy for loading a node builtin CommonJS module that isn't 282// through normal resolution 283translators.set('builtin', async function builtinStrategy(url) { 284 debug(`Translating BuiltinModule ${url}`); 285 // Slice 'node:' scheme 286 const id = StringPrototypeSlice(url, 5); 287 const module = loadNativeModule(id, url); 288 if (!StringPrototypeStartsWith(url, 'node:') || !module) { 289 throw new ERR_UNKNOWN_BUILTIN_MODULE(url); 290 } 291 debug(`Loading BuiltinModule ${url}`); 292 return module.getESMFacade(); 293}); 294 295// Strategy for loading a JSON file 296translators.set('json', async function jsonStrategy(url) { 297 emitExperimentalWarning('Importing JSON modules'); 298 debug(`Translating JSONModule ${url}`); 299 debug(`Loading JSONModule ${url}`); 300 const pathname = StringPrototypeStartsWith(url, 'file:') ? 301 fileURLToPath(url) : null; 302 let modulePath; 303 let module; 304 if (pathname) { 305 modulePath = isWindows ? 306 StringPrototypeReplace(pathname, winSepRegEx, '\\') : pathname; 307 module = CJSModule._cache[modulePath]; 308 if (module && module.loaded) { 309 const exports = module.exports; 310 return new ModuleWrap(url, undefined, ['default'], function() { 311 this.setExport('default', exports); 312 }); 313 } 314 } 315 let { source } = await this._getSource( 316 url, { format: 'json' }, defaultGetSource); 317 assertBufferSource(source, true, 'getSource'); 318 ({ source } = await this._transformSource( 319 source, { url, format: 'json' }, defaultTransformSource)); 320 source = stringify(source); 321 if (pathname) { 322 // A require call could have been called on the same file during loading and 323 // that resolves synchronously. To make sure we always return the identical 324 // export, we have to check again if the module already exists or not. 325 module = CJSModule._cache[modulePath]; 326 if (module && module.loaded) { 327 const exports = module.exports; 328 return new ModuleWrap(url, undefined, ['default'], function() { 329 this.setExport('default', exports); 330 }); 331 } 332 } 333 try { 334 const exports = JSONParse(stripBOM(source)); 335 module = { 336 exports, 337 loaded: true 338 }; 339 } catch (err) { 340 // TODO (BridgeAR): We could add a NodeCore error that wraps the JSON 341 // parse error instead of just manipulating the original error message. 342 // That would allow to add further properties and maybe additional 343 // debugging information. 344 err.message = errPath(url) + ': ' + err.message; 345 throw err; 346 } 347 if (pathname) { 348 CJSModule._cache[modulePath] = module; 349 } 350 return new ModuleWrap(url, undefined, ['default'], function() { 351 debug(`Parsing JSONModule ${url}`); 352 this.setExport('default', module.exports); 353 }); 354}); 355 356// Strategy for loading a wasm module 357translators.set('wasm', async function(url) { 358 emitExperimentalWarning('Importing Web Assembly modules'); 359 let { source } = await this._getSource( 360 url, { format: 'wasm' }, defaultGetSource); 361 assertBufferSource(source, false, 'getSource'); 362 ({ source } = await this._transformSource( 363 source, { url, format: 'wasm' }, defaultTransformSource)); 364 assertBufferSource(source, false, 'transformSource'); 365 debug(`Translating WASMModule ${url}`); 366 let compiled; 367 try { 368 compiled = await WebAssembly.compile(source); 369 } catch (err) { 370 err.message = errPath(url) + ': ' + err.message; 371 throw err; 372 } 373 374 const imports = 375 ArrayPrototypeMap(WebAssembly.Module.imports(compiled), 376 ({ module }) => module); 377 const exports = 378 ArrayPrototypeMap(WebAssembly.Module.exports(compiled), 379 ({ name }) => name); 380 381 return createDynamicModule(imports, exports, url, (reflect) => { 382 const { exports } = new WebAssembly.Instance(compiled, reflect.imports); 383 for (const expt of ObjectKeys(exports)) 384 reflect.exports[expt].set(exports[expt]); 385 }).module; 386}); 387