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