- IDE
- 代码编辑器
- 开发工具
- 前端
- 桌面应用
- 插件系统
- 后端
- AI 应用
【免费下载链接】theia
Eclipse Theia is a cloud & desktop IDE framework implemented in TypeScript.
导读
在 Eclipse Theia 的插件系统中,插件既可以按传统 CommonJS(require/exports)方式加载,也可以按现代 ECMAScript Module(import/export)方式加载。本篇文章以仓库中的 plugin-esm-mjs 示例插件为入口,深入讲解不修改package.json的"type"字段、仅凭.mjs文件扩展名即可让 Theia 将插件入口识别为 ESM 并改用import()加载的原理与完整实操。读完本文,你将掌握 Theia 判定 ESM 插件的三条规则、.mjs与"type": "module"两种 ESM 交付方式的差异,以及如何编写、配置和运行一个.mjs格式的 VS Code 风格插件。
一、示例插件是什么:plugin-esm-mjs
plugin-esm-mjs是 Theia 仓库中sample-plugins/sample-namespace命名空间下的一个样例插件。它本质上是一个以.mjs文件作为入口的 VS Code 扩展,用于演示 Theia 插件宿主(plugin host)对 ESM 模块格式的支持。
该插件目录只包含四个文件:
- extension.mjs —— 插件入口,使用 ESM 语法
- package.json —— 插件清单,
main字段指向extension.mjs - README.md —— 样例说明
- LICENSE 与
icon128.png—— 许可证与图标
样例的package.json声明了插件元信息、激活事件与命令贡献点:
{ "private": true, "name": "plugin-esm-mjs", "version": "1.75.0", "main": "extension.mjs", "license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0", "publisher": "sample-namespace", "engines": { "vscode": "^1.125.0" }, "activationEvents": [ "onCommand:plugin-esm-mjs.hello" ], "devDependencies": { "@types/vscode": "^1.125.0" }, "scripts": { "build": "vsce package --no-dependencies" }, "contributes": { "commands": [ { "command": "plugin-esm-mjs.hello", "title": "Hello from plugin-esm-mjs" } ] } }关键点在于"main": "extension.mjs"——Theia 正是通过读取这个入口文件的扩展名来判断加载方式的。
对应的入口实现(extension.mjs)用标准 ESM 语法导入vscodeAPI 并导出activate函数:
import { commands, window } from 'vscode'; export function activate(context) { context.subscriptions.push(commands.registerCommand('plugin-esm-mjs.hello', () => { window.showInformationMessage('Hello from plugin-esm-mjs (.mjs)!'); })); }当用户在 Theia 中执行plugin-esm-mjs.hello命令时,会弹出一条信息提示,证明该插件已成功以 ESM 方式加载并运行。
二、核心原理:Theia 如何判定一个插件是 ESM
样例 README 指出,与plugin-esm不同,plugin-esm-mjs并不在package.json中设置"type": "module",仅凭extension.mjs的.mjs扩展名就足以让 Node 把该文件当作 ESM 加载。Theia 则通过检查入口文件的扩展名,决定调用import()而非require()。
这一判定逻辑在源码中有完整实现,位于 packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts 的isESMPlugin方法:
/** * Determine whether a plugin should be loaded via ESM `import()` instead of * CommonJS `require()`. Mirrors Node's own rules: * - `.mjs` is always ESM * - `.cjs` is always CJS * - any other extension falls back to the `package.json` `type` field */ protected isESMPlugin(plugin: Plugin): boolean { const ext = path.extname(plugin.pluginPath || '').toLowerCase(); if (ext === '.mjs') { return true; } if (ext === '.cjs') { return false; } return plugin.rawModel.type === 'module'; }由此可以总结出 Theia 判定 ESM 插件的三条确定规则:
.mjs扩展名 → 永远按 ESM 加载(return true),与package.json无关;.cjs扩展名 → 永远按 CommonJS 加载(return false);- 其他扩展名(如
.js)→ 回退到package.json的"type"字段,"type": "module"视为 ESM,否则视为 CommonJS。
这套判定规则与 Node.js 自身的模块解析规则保持一致:.mjs强制 ESM、.cjs强制 CommonJS、其余扩展名看type字段。
三、加载链路:从判定到import()动态导入
判定为 ESM 后,Theia 走的是import()动态导入路径。同样在 plugin-host-rpc.ts 的createPluginHost()中,loadPlugin会按判定结果分流:
loadPlugin(plugin: Plugin): any { ... removeFromCache(mod => mod.id.startsWith(plugin.pluginFolder)); if (!plugin.pluginPath) { return undefined; } if (self.isESMPlugin(plugin)) { return importESMPlugin(pathToFileURL(plugin.pluginPath).href); } return dynamicRequire(plugin.pluginPath); }值得注意的细节是,源码在 plugin-host-rpc.ts 中用一个new Function包装了动态import():
// Hide the dynamic `import()` inside `new Function` so that bundlers and // transpilers targeting CommonJS (tsc, esbuild, webpack) cannot statically // rewrite it into `Promise.resolve(require(...))`. const importESMPlugin = new Function('url', 'return import(url)') as (url: string) => Promise<any>;这是有意为之的实现细节:如果不加这层包装,tsc、esbuild、webpack等面向 CommonJS 的打包器/转译器可能会把import()静态重写为require(),从而破坏 ESM 加载。此外,加载前还通过removeFromCache清理插件文件夹相关模块的缓存,避免插件宿主重启时产生内存泄漏(注释中引用了 theia PR #4931 与 nodejs/node#8443 两个问题背景)。
四、配套机制:入口文件解析时的扩展名回退
除了loadPlugin时的格式判定,Theia 在解析插件资源时也内置了对.mjs的支持。在 packages/plugin-ext/src/hosted/node/plugin-reader.ts 的resolveFile方法中,当请求的模块路径不带扩展名时,会依次尝试追加.js、.cjs、.mjs:
const candidates = [absolutePath]; const pathExtension = path.extname(absolutePath).toLowerCase(); if (!pathExtension) { candidates.push(absolutePath + '.js'); candidates.push(absolutePath + '.cjs'); candidates.push(absolutePath + '.mjs'); }这意味着,即使插件内部代码以不带扩展名的相对路径引用模块,只要磁盘上实际存在.mjs文件,Theia 也能正确解析并返回该文件(resolveFile同时做了路径越界防护:若解析结果脱离插件本地目录则直接返回undefined)。从源码结构可以推断,这套扩展名回退机制让.mjs插件中的内部模块引用与 Node.js/CommonJS 时代的习惯写法保持兼容。
五、对比参照:plugin-esm与plugin-esm-mjs的两种 ESM 交付方式
仓库中还提供了另一个示例插件 plugin-esm,它与plugin-esm-mjs形成了一组完整的对照实验:
| 对比维度 | plugin-esm | plugin-esm-mjs |
|---|---|---|
| 入口文件 | extension.js | extension.mjs |
package.json的"type" | 声明为"module" | 不声明(或非module) |
| ESM 判定依据 | package.json的type字段 | .mjs扩展名(硬性规则) |
| 入口写法 | import * as vscode from 'vscode'+export function activate(...) | import { commands, window } from 'vscode'+export function activate(...) |
plugin-esm的 README 还提到,这种通过"type": "module"打包 ESM 扩展的方式,与近期 VS Code 内置扩展(如vscode.github)的打包方式一致。而plugin-esm-mjs展示的则是"零配置"路径:不改package.json,只改入口文件扩展名。
两种方式在实际使用中的选择建议(结合 Node 规则推断):
- 如果插件包含大量
.js文件且希望整体按 ESM 解释,使用"type": "module"更省事; - 如果只想让单个入口文件按 ESM 加载,或需要与其他 CommonJS 文件混用,
.mjs扩展名是更精细、侵入性更小的选择(Node 同样支持用.cjs在"type": "module"包内强制单个文件走 CommonJS,Theia 的isESMPlugin也覆盖了这一规则)。
六、如何构建与运行.mjs插件
该样例的package.json提供了基于 VS Code 官方打包工具vsce的构建脚本:
"scripts": { "build": "vsce package --no-dependencies" }vsce package --no-dependencies会在不打包依赖的情况下生成.vsix插件包。生成后的插件可以像普通 Theia 插件一样,通过 Theia 的插件安装流程(例如将.vsix放入 Theia 应用的插件目录,或通过 download-plugins 等 CLI 工具 在构建阶段拉取)加载到 Theia 运行时中。
在 Theia 中运行后,可通过以下方式验证 ESM 加载生效:
- 启动 Theia 应用并加载该插件;
- 执行命令面板中的Hello from plugin-esm-mjs命令;
- 观察弹出
Hello from plugin-esm-mjs (.mjs)!信息,同时在插件宿主日志中可以看到loadPlugin阶段对 ESM 路径的处理记录。
七、小结
plugin-esm-mjs虽然只是 Theia 仓库中一个极简样例,但它精准覆盖了"以.mjs扩展名交付 ESM 插件"这一完整链路,并与仓库源码形成了清晰对应:
- 判定规则:
isESMPlugin中.mjs→ ESM、.cjs→ CommonJS、其余看type字段; - 加载执行:
loadPlugin对 ESM 插件调用被new Function保护的动态import(); - 资源解析:
resolveFile支持.js/.cjs/.mjs扩展名回退。
对于插件开发者而言,.mjs扩展名提供了一条最低成本的 ESM 接入路径:无需改动package.json的模块类型声明,即可让 Theia 以现代 ESM 语义加载插件,从而与 VS Code 生态中日益增多的 ESM 内置扩展保持一致。若需继续深入,可进一步阅读 Theia 插件运行时的宿主实现 plugin-ext 及插件 API 说明 Plugin-API.md。
- IDE
- 代码编辑器
- 开发工具
- 前端
- 桌面应用
- 插件系统
- 后端
- AI 应用
【免费下载链接】theia
Eclipse Theia is a cloud & desktop IDE framework implemented in TypeScript.
相关推荐
Eclipse Theia 插件宿主如何加载 ESM 插件:以 plugin-esm 示例插件为剖析样本
Eclipse Theia 插件宿主如何加载 ESM 插件:以 plugin esm 示例插件为剖析样本 导读 随着 VS Code 官方内置扩展逐步以 ECM
IDE代码编辑器开发工具前端桌面应用插件系统后端AI 应用Eclipse Theia Headless 插件扩展(plugin-ext-headless)深度解析:后端无头插件宿主的架构与实践
Eclipse Theia Headless 插件扩展(plugin ext headless)深度解析:后端无头插件宿主的架构与实践 导读 @theia/pl
IDE代码编辑器开发工具前端桌面应用插件系统后端AI 应用zkp-hmac-communication-js示例代码详解:example1.mjs到example3.mjs全解析
zkp hmac communication js示例代码详解:example1.mjs到example3.mjs全解析 zkp hmac communicat
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考