get-format.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. // Manually “tree shaken” from:
  2. // <https://github.com/nodejs/node/blob/7c3dce0/lib/internal/modules/esm/get_format.js>
  3. // Last checked on: Apr 29, 2024.
  4. import {fileURLToPath} from 'node:url'
  5. import {getPackageType} from './package-json-reader.js'
  6. import {codes} from './errors.js'
  7. const {ERR_UNKNOWN_FILE_EXTENSION} = codes
  8. const hasOwnProperty = {}.hasOwnProperty
  9. /** @type {Record<string, string>} */
  10. const extensionFormatMap = {
  11. // @ts-expect-error: hush.
  12. __proto__: null,
  13. '.cjs': 'commonjs',
  14. '.js': 'module',
  15. '.json': 'json',
  16. '.mjs': 'module'
  17. }
  18. /**
  19. * @param {string | null} mime
  20. * @returns {string | null}
  21. */
  22. function mimeToFormat(mime) {
  23. if (
  24. mime &&
  25. /\s*(text|application)\/javascript\s*(;\s*charset=utf-?8\s*)?/i.test(mime)
  26. )
  27. return 'module'
  28. if (mime === 'application/json') return 'json'
  29. return null
  30. }
  31. /**
  32. * @callback ProtocolHandler
  33. * @param {URL} parsed
  34. * @param {{parentURL: string, source?: Buffer}} context
  35. * @param {boolean} ignoreErrors
  36. * @returns {string | null | void}
  37. */
  38. /**
  39. * @type {Record<string, ProtocolHandler>}
  40. */
  41. const protocolHandlers = {
  42. // @ts-expect-error: hush.
  43. __proto__: null,
  44. 'data:': getDataProtocolModuleFormat,
  45. 'file:': getFileProtocolModuleFormat,
  46. 'http:': getHttpProtocolModuleFormat,
  47. 'https:': getHttpProtocolModuleFormat,
  48. 'node:'() {
  49. return 'builtin'
  50. }
  51. }
  52. /**
  53. * @param {URL} parsed
  54. */
  55. function getDataProtocolModuleFormat(parsed) {
  56. const {1: mime} = /^([^/]+\/[^;,]+)[^,]*?(;base64)?,/.exec(
  57. parsed.pathname
  58. ) || [null, null, null]
  59. return mimeToFormat(mime)
  60. }
  61. /**
  62. * Returns the file extension from a URL.
  63. *
  64. * Should give similar result to
  65. * `require('node:path').extname(require('node:url').fileURLToPath(url))`
  66. * when used with a `file:` URL.
  67. *
  68. * @param {URL} url
  69. * @returns {string}
  70. */
  71. function extname(url) {
  72. const pathname = url.pathname
  73. let index = pathname.length
  74. while (index--) {
  75. const code = pathname.codePointAt(index)
  76. if (code === 47 /* `/` */) {
  77. return ''
  78. }
  79. if (code === 46 /* `.` */) {
  80. return pathname.codePointAt(index - 1) === 47 /* `/` */
  81. ? ''
  82. : pathname.slice(index)
  83. }
  84. }
  85. return ''
  86. }
  87. /**
  88. * @type {ProtocolHandler}
  89. */
  90. function getFileProtocolModuleFormat(url, _context, ignoreErrors) {
  91. const value = extname(url)
  92. if (value === '.js') {
  93. const packageType = getPackageType(url)
  94. if (packageType !== 'none') {
  95. return packageType
  96. }
  97. return 'commonjs'
  98. }
  99. if (value === '') {
  100. const packageType = getPackageType(url)
  101. // Legacy behavior
  102. if (packageType === 'none' || packageType === 'commonjs') {
  103. return 'commonjs'
  104. }
  105. // Note: we don’t implement WASM, so we don’t need
  106. // `getFormatOfExtensionlessFile` from `formats`.
  107. return 'module'
  108. }
  109. const format = extensionFormatMap[value]
  110. if (format) return format
  111. // Explicit undefined return indicates load hook should rerun format check
  112. if (ignoreErrors) {
  113. return undefined
  114. }
  115. const filepath = fileURLToPath(url)
  116. throw new ERR_UNKNOWN_FILE_EXTENSION(value, filepath)
  117. }
  118. function getHttpProtocolModuleFormat() {
  119. // To do: HTTPS imports.
  120. }
  121. /**
  122. * @param {URL} url
  123. * @param {{parentURL: string}} context
  124. * @returns {string | null}
  125. */
  126. export function defaultGetFormatWithoutErrors(url, context) {
  127. const protocol = url.protocol
  128. if (!hasOwnProperty.call(protocolHandlers, protocol)) {
  129. return null
  130. }
  131. return protocolHandlers[protocol](url, context, true) || null
  132. }