diff --git a/shell/preload/esm-loader.mjs b/shell/preload/esm-loader.mjs new file mode 100644 index 00000000..c59bf3b1 --- /dev/null +++ b/shell/preload/esm-loader.mjs @@ -0,0 +1,41 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +const builtinModules = [ + 'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns', + 'events', 'fs', 'http', 'https', 'net', 'os', 'path', 'process', 'querystring', + 'readline', 'stream', 'string_decoder', 'timers', 'tls', 'tty', 'url', 'util', + 'v8', 'zlib', +]; + +function isBareSpecifier(specifier) { + return !specifier.startsWith('.') && + !specifier.startsWith('/') && + !specifier.startsWith('file:') && + !specifier.startsWith('node:') && + !builtinModules.includes(specifier) && + !builtinModules.includes(specifier.split('/')[0]); +} + +export function resolve(specifier, context, nextResolve) { + if (!isBareSpecifier(specifier)) { + return nextResolve(specifier, context); + } + + // 解析优先级:全局 pnpm > 系统全局 + const bases = [ + process.env.QL_NODE_GLOBAL_PATH, + '/usr/local/lib/node_modules', + ].filter(Boolean); + + for (const base of bases) { + if (existsSync(join(base, specifier))) { + return nextResolve(specifier, { + ...context, + parentURL: new URL(`${join(base, specifier)}/`, 'file://').href, + }); + } + } + + return nextResolve(specifier, context); +} diff --git a/shell/preload/sitecustomize.js b/shell/preload/sitecustomize.js index 44dc1bea..720dc206 100644 --- a/shell/preload/sitecustomize.js +++ b/shell/preload/sitecustomize.js @@ -4,6 +4,11 @@ const path = require('path'); const client = require('./client.js'); require(`./env.js`); +// 注册 ESM loader,使全局安装的包也可通过 import 导入 +try { + Module.register(new URL('esm-loader.mjs', `file://${__dirname}/`).href); +} catch (_) {} + function preferGlobalNodeModules() { const { QL_NODE_GLOBAL_PATH } = process.env; if (!QL_NODE_GLOBAL_PATH || Module._qlGlobalPathPatched) {