1'use strict'; 2 3const { 4 RegExpPrototypeExec, 5 ObjectPrototypeHasOwnProperty, 6 PromisePrototypeThen, 7 PromiseResolve, 8 StringPrototypeIncludes, 9 StringPrototypeCharCodeAt, 10 StringPrototypeSlice, 11} = primordials; 12const { getOptionValue } = require('internal/options'); 13const { 14 extensionFormatMap, 15 getFormatOfExtensionlessFile, 16 getLegacyExtensionFormat, 17 mimeToFormat, 18} = require('internal/modules/esm/formats'); 19 20const experimentalNetworkImports = 21 getOptionValue('--experimental-network-imports'); 22const experimentalSpecifierResolution = 23 getOptionValue('--experimental-specifier-resolution'); 24const defaultTypeFlag = getOptionValue('--experimental-default-type'); 25// The next line is where we flip the default to ES modules someday. 26const defaultType = defaultTypeFlag === 'module' ? 'module' : 'commonjs'; 27const { getPackageType } = require('internal/modules/esm/resolve'); 28const { fileURLToPath } = require('internal/url'); 29const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; 30 31const protocolHandlers = { 32 '__proto__': null, 33 'data:': getDataProtocolModuleFormat, 34 'file:': getFileProtocolModuleFormat, 35 'http:': getHttpProtocolModuleFormat, 36 'https:': getHttpProtocolModuleFormat, 37 'node:'() { return 'builtin'; }, 38}; 39 40/** 41 * @param {URL} parsed 42 * @returns {string | null} 43 */ 44function getDataProtocolModuleFormat(parsed) { 45 const { 1: mime } = RegExpPrototypeExec( 46 /^([^/]+\/[^;,]+)(?:[^,]*?)(;base64)?,/, 47 parsed.pathname, 48 ) || [ null, null, null ]; 49 50 return mimeToFormat(mime); 51} 52 53const DOT_CODE = 46; 54const SLASH_CODE = 47; 55 56/** 57 * Returns the file extension from a URL. Should give similar result to 58 * `require('node:path').extname(require('node:url').fileURLToPath(url))` 59 * when used with a `file:` URL. 60 * @param {URL} url 61 * @returns {string} 62 */ 63function extname(url) { 64 const { pathname } = url; 65 for (let i = pathname.length - 1; i > 0; i--) { 66 switch (StringPrototypeCharCodeAt(pathname, i)) { 67 case SLASH_CODE: 68 return ''; 69 70 case DOT_CODE: 71 return StringPrototypeCharCodeAt(pathname, i - 1) === SLASH_CODE ? '' : StringPrototypeSlice(pathname, i); 72 } 73 } 74 return ''; 75} 76 77/** 78 * Determine whether the given file URL is under a `node_modules` folder. 79 * This function assumes that the input has already been verified to be a `file:` URL, 80 * and is a file rather than a folder. 81 * @param {URL} url 82 */ 83function underNodeModules(url) { 84 if (url.protocol !== 'file:') { return false; } // We determine module types for other protocols based on MIME header 85 86 return StringPrototypeIncludes(url.pathname, '/node_modules/'); 87} 88 89/** 90 * @param {URL} url 91 * @param {{parentURL: string}} context 92 * @param {boolean} ignoreErrors 93 * @returns {string} 94 */ 95function getFileProtocolModuleFormat(url, context, ignoreErrors) { 96 const ext = extname(url); 97 98 if (ext === '.js') { 99 const packageType = getPackageType(url); 100 if (packageType !== 'none') { 101 return packageType; 102 } 103 // The controlling `package.json` file has no `type` field. 104 if (defaultType === 'module') { 105 // An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules` 106 // should retain the assumption that a lack of a `type` field means CommonJS. 107 return underNodeModules(url) ? 'commonjs' : 'module'; 108 } 109 return 'commonjs'; 110 } 111 112 if (ext === '') { 113 const packageType = getPackageType(url); 114 if (defaultType === 'commonjs') { // Legacy behavior 115 if (packageType === 'none' || packageType === 'commonjs') { 116 return 'commonjs'; 117 } // Else packageType === 'module' 118 return getFormatOfExtensionlessFile(url); 119 } // Else defaultType === 'module' 120 if (underNodeModules(url)) { // Exception for package scopes under `node_modules` 121 return packageType === 'module' ? getFormatOfExtensionlessFile(url) : 'commonjs'; 122 } 123 if (packageType === 'none' || packageType === 'module') { 124 return getFormatOfExtensionlessFile(url); 125 } // Else packageType === 'commonjs' 126 return 'commonjs'; 127 } 128 129 const format = extensionFormatMap[ext]; 130 if (format) { return format; } 131 132 if (experimentalSpecifierResolution !== 'node') { 133 // Explicit undefined return indicates load hook should rerun format check 134 if (ignoreErrors) { return undefined; } 135 const filepath = fileURLToPath(url); 136 throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath); 137 } 138 139 return getLegacyExtensionFormat(ext) ?? null; 140} 141 142/** 143 * @param {URL} url 144 * @param {{parentURL: string}} context 145 * @returns {Promise<string> | undefined} only works when enabled 146 */ 147function getHttpProtocolModuleFormat(url, context) { 148 if (experimentalNetworkImports) { 149 const { fetchModule } = require('internal/modules/esm/fetch_module'); 150 return PromisePrototypeThen( 151 PromiseResolve(fetchModule(url, context)), 152 (entry) => { 153 return mimeToFormat(entry.headers['content-type']); 154 }, 155 ); 156 } 157} 158 159/** 160 * @param {URL} url 161 * @param {{parentURL: string}} context 162 * @returns {Promise<string> | string | undefined} only works when enabled 163 */ 164function defaultGetFormatWithoutErrors(url, context) { 165 const protocol = url.protocol; 166 if (!ObjectPrototypeHasOwnProperty(protocolHandlers, protocol)) { 167 return null; 168 } 169 return protocolHandlers[protocol](url, context, true); 170} 171 172/** 173 * @param {URL} url 174 * @param {{parentURL: string}} context 175 * @returns {Promise<string> | string | undefined} only works when enabled 176 */ 177function defaultGetFormat(url, context) { 178 const protocol = url.protocol; 179 if (!ObjectPrototypeHasOwnProperty(protocolHandlers, protocol)) { 180 return null; 181 } 182 return protocolHandlers[protocol](url, context, false); 183} 184 185module.exports = { 186 defaultGetFormat, 187 defaultGetFormatWithoutErrors, 188 extensionFormatMap, 189 extname, 190}; 191