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