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