返回 CodeWhale
internal.ts
根目录 / crates / tui / extension-host / src / dsh / upstream / loader / src / internal.ts
1 import { createRequire, type LoadHookContext } from 'node:module'
2 import type { Dict } from '@deepseek-ai/cosmokit'
3
4 /** Node internal module format names handled by loader hooks. */
5 export type ModuleFormat = 'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'
6 /** Source payload accepted by Node internal module load hooks. */
7 export type ModuleSource = string | ArrayBuffer
8
9 /** Result returned by a Node internal resolve hook. */
10 export interface ResolveResult {
11 format: ModuleFormat
12 url: string
13 }
14
15 /** Result returned by a Node internal load hook. */
16 export interface LoadResult {
17 format: ModuleFormat
18 source?: ModuleSource
19 }
20
21 type LoadCacheData = ModuleJob // | Function
22
23 /** @see https://github.com/nodejs/node/blob/main/lib/internal/modules/esm/module_map.js */
24 interface LoadCache extends Omit<Map<string, Dict<LoadCacheData>>, 'get' | 'set' | 'has'> {
25 get(url: string, type?: string): LoadCacheData | undefined
26 set(url: string, type?: string, job?: LoadCacheData): this
27 has(url: string, type?: string): boolean
28 }
29
30 /** Minimal Node internal ModuleWrap surface used by HMR helpers. */
31 export interface ModuleWrap {
32 url: string
33 getNamespace(): any
34 }
35
36 /** @see https://github.com/nodejs/node/blob/main/lib/internal/modules/esm/module_job.js */
37 export interface ModuleJob {
38 url: string
39 loader: ModuleLoader
40 module?: ModuleWrap
41 importAttributes: ImportAttributes
42 linked: Promise<ModuleJob[]>
43 instantiate(): Promise<void>
44 run(): Promise<{ module: ModuleWrap }>
45 }
46
47 /**
48 * Node 22/23 ModuleLoader interface.
49 *
50 * Key methods:
51 * - getModuleJobForImport(specifier, parentURL, importAttributes)
52 * - resolve(specifier, parentURL, importAttributes) → Promise<ResolveResult>
53 * - resolveSync(specifier, parentURL, importAttributes) → ResolveResult
54 */
55 export interface ModuleLoaderV1 {
56 version: 'v1'
57 loadCache: LoadCache
58 import(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<any>
59 register(specifier: string | URL, parentURL?: string | URL, data?: any, transferList?: any[]): void
60 getModuleJobForImport(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<ModuleJob>
61 resolve(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<ResolveResult>
62 resolveSync(specifier: string, parentURL: string, importAttributes: ImportAttributes): ResolveResult
63 load(specifier: string, context: Pick<LoadHookContext, 'format' | 'importAttributes'>): Promise<LoadResult>
64 }
65
66 /** Node 24+ module request object. */
67 export interface ModuleRequest {
68 specifier: string
69 attributes?: ImportAttributes
70 phase?: ModulePhase
71 }
72
73 /** @see https://github.com/nodejs/node/blob/main/src/module_wrap.h */
74 export const enum ModulePhase {
75 Source = 1,
76 Evaluation = 2,
77 }
78
79 /** Opaque Node internal module request type marker. */
80 export type ModuleRequestType = unknown // internal symbols
81
82 /**
83 * Node 24+ ModuleLoader interface.
84 *
85 * Breaking changes from v1:
86 * - getModuleJobForImport removed → getOrCreateModuleJob(parentURL, request, requestType)
87 * - resolve removed (became private #resolve) → resolveSync(parentURL, request)
88 * - Parameter order reversed for resolveSync, request object { specifier, attributes }
89 * - LoadCache became typed Map<url, { [type]: ModuleJob }> with delete only setting undefined
90 */
91 export interface ModuleLoaderV2 {
92 version: 'v2'
93 loadCache: LoadCache
94 import(specifier: string, parentURL: string, importAttributes: ImportAttributes, phase?: ModulePhase, isEntryPoint?: boolean): Promise<any>
95 register(specifier: string | URL, parentURL?: string | URL, data?: any, transferList?: any[], isInternal?: boolean): void
96 getOrCreateModuleJob(parentURL: string, request: ModuleRequest, requestType?: ModuleRequestType): Promise<ModuleJob>
97 resolveSync(parentURL: string, request: ModuleRequest): ResolveResult
98 load(url: string, context: Pick<LoadHookContext, 'format' | 'importAttributes'>): Promise<LoadResult>
99 }
100
101 /** Supported Node internal ESM loader shapes. */
102 export type ModuleLoader = ModuleLoaderV1 | ModuleLoaderV2
103
104 /** Helpers for locating the current Node internal module loader. */
105 export namespace ModuleLoader {
106 let _cachedLoader: ModuleLoader | undefined
107
108 function requireInternal(id: string): any {
109 const require = createRequire(import.meta.url)
110 if (process.execArgv.includes('--expose-internals')) {
111 try {
112 return require(id)
113 } catch {}
114 }
115 try {
116 return require('node-addon-require-builtin').requireBuiltin(id)
117 } catch {}
118 }
119
120 /**
121 * Locate and classify the running Node internal module loader.
122 *
123 * The shape is decided by which module-job API the loader owns, never by the
124 * Node version: v2 landed in 24.12.0, so a major-version test mistags every
125 * 24.0–24.11.1 loader as v2 and makes consumers call `resolveSync` with
126 * reversed parameters. Arity is not usable either — `resolveSync` reports 2
127 * under both shapes. A loader owning neither API is left unclassified rather
128 * than guessed, so consumers take their documented no-internals path.
129 * @returns the classified loader, or `undefined` when none is reachable or its shape is unknown.
130 */
131 export function fromInternal(): ModuleLoader | undefined {
132 if (_cachedLoader) return _cachedLoader
133 const [major] = process.versions.node.split('.').map(Number)
134 if (major < 22) return
135
136 const raw = requireInternal('internal/modules/esm/loader')?.getOrInitializeCascadedLoader()
137 if (!raw) return
138 const version = typeof raw.getOrCreateModuleJob === 'function'
139 ? 'v2'
140 : typeof raw.getModuleJobForImport === 'function' ? 'v1' : undefined
141 if (!version) return
142 return _cachedLoader = Object.assign(raw, { version })
143 }
144 }
145
145 lines TYPESCRIPT