internal.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { createRequire, type LoadHookContext } from 'node:module'
  2. import type { Dict } from 'cosmokit'
  3. /** Node internal module format names handled by loader hooks. */
  4. export type ModuleFormat = 'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
  5. /** Source payload accepted by Node internal module load hooks. */
  6. export type ModuleSource = string | ArrayBuffer
  7. /** Result returned by a Node internal resolve hook. */
  8. export interface ResolveResult {
  9. format: ModuleFormat
  10. url: string
  11. }
  12. /** Result returned by a Node internal load hook. */
  13. export interface LoadResult {
  14. format: ModuleFormat
  15. source?: ModuleSource
  16. }
  17. type LoadCacheData = ModuleJob // | Function
  18. /** @see https://github.com/nodejs/node/blob/main/lib/internal/modules/esm/module_map.js */
  19. interface LoadCache extends Omit<Map<string, Dict<LoadCacheData>>, 'get' | 'set' | 'has'> {
  20. get(url: string, type?: string): LoadCacheData | undefined
  21. set(url: string, type?: string, job?: LoadCacheData): this
  22. has(url: string, type?: string): boolean
  23. }
  24. /** Minimal Node internal ModuleWrap surface used by HMR helpers. */
  25. export interface ModuleWrap {
  26. url: string
  27. getNamespace(): any
  28. }
  29. /** @see https://github.com/nodejs/node/blob/main/lib/internal/modules/esm/module_job.js */
  30. export interface ModuleJob {
  31. url: string
  32. loader: ModuleLoader
  33. module?: ModuleWrap
  34. importAttributes: ImportAttributes
  35. linked: Promise<ModuleJob[]>
  36. instantiate(): Promise<void>
  37. run(): Promise<{ module: ModuleWrap }>
  38. }
  39. /**
  40. * Node 22/23 ModuleLoader interface.
  41. *
  42. * Key methods:
  43. * - getModuleJobForImport(specifier, parentURL, importAttributes)
  44. * - resolve(specifier, parentURL, importAttributes) → Promise<ResolveResult>
  45. * - resolveSync(specifier, parentURL, importAttributes) → ResolveResult
  46. */
  47. export interface ModuleLoaderV1 {
  48. version: 'v1'
  49. loadCache: LoadCache
  50. import(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<any>
  51. register(specifier: string | URL, parentURL?: string | URL, data?: any, transferList?: any[]): void
  52. getModuleJobForImport(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<ModuleJob>
  53. resolve(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<ResolveResult>
  54. resolveSync(specifier: string, parentURL: string, importAttributes: ImportAttributes): ResolveResult
  55. load(specifier: string, context: Pick<LoadHookContext, 'format' | 'importAttributes'>): Promise<LoadResult>
  56. }
  57. /** Node 24+ module request object. */
  58. export interface ModuleRequest {
  59. specifier: string
  60. attributes?: ImportAttributes
  61. phase?: ModulePhase
  62. }
  63. /** @see https://github.com/nodejs/node/blob/main/src/module_wrap.h */
  64. export const enum ModulePhase {
  65. Source = 1,
  66. Evaluation = 2,
  67. }
  68. /** Opaque Node internal module request type marker. */
  69. export type ModuleRequestType = unknown // internal symbols
  70. /**
  71. * Node 24+ ModuleLoader interface.
  72. *
  73. * Breaking changes from v1:
  74. * - getModuleJobForImport removed → getOrCreateModuleJob(parentURL, request, requestType)
  75. * - resolve removed (became private #resolve) → resolveSync(parentURL, request)
  76. * - Parameter order reversed for resolveSync, request object { specifier, attributes }
  77. * - LoadCache became typed Map<url, { [type]: ModuleJob }> with delete only setting undefined
  78. */
  79. export interface ModuleLoaderV2 {
  80. version: 'v2'
  81. loadCache: LoadCache
  82. import(specifier: string, parentURL: string, importAttributes: ImportAttributes, phase?: ModulePhase, isEntryPoint?: boolean): Promise<any>
  83. register(specifier: string | URL, parentURL?: string | URL, data?: any, transferList?: any[], isInternal?: boolean): void
  84. getOrCreateModuleJob(parentURL: string, request: ModuleRequest, requestType?: ModuleRequestType): Promise<ModuleJob>
  85. resolveSync(parentURL: string, request: ModuleRequest): ResolveResult
  86. load(url: string, context: Pick<LoadHookContext, 'format' | 'importAttributes'>): Promise<LoadResult>
  87. }
  88. /** Supported Node internal ESM loader shapes. */
  89. export type ModuleLoader = ModuleLoaderV1 | ModuleLoaderV2
  90. /** Helpers for locating the current Node internal module loader. */
  91. export namespace ModuleLoader {
  92. let _cachedLoader: ModuleLoader | undefined
  93. function requireInternal(id: string): any {
  94. const require = createRequire(import.meta.url)
  95. if (process.execArgv.includes('--expose-internals')) {
  96. try {
  97. return require(id)
  98. } catch {}
  99. }
  100. try {
  101. return require('node-addon-require-builtin').requireBuiltin(id)
  102. } catch {}
  103. }
  104. export function fromInternal(): ModuleLoader | undefined {
  105. if (_cachedLoader) return _cachedLoader
  106. const [major] = process.versions.node.split('.').map(Number)
  107. if (major >= 24) {
  108. const raw = requireInternal('internal/modules/esm/loader')?.getOrInitializeCascadedLoader()
  109. if (raw) return _cachedLoader = Object.assign(raw, { version: 'v2' })
  110. } else if (major >= 22) {
  111. const raw = requireInternal('internal/modules/esm/loader')?.getOrInitializeCascadedLoader()
  112. if (raw) return _cachedLoader = Object.assign(raw, { version: 'v1' })
  113. }
  114. }
  115. }