Eclipse Theia 插件开发:利用 `.mjs` 扩展名交付 ESM 插件(plugin-esm-mjs 示例深度解析)
2026/9/20 3:48:10 网站建设 项目流程
  • IDE
  • 代码编辑器
  • 开发工具
  • 前端
  • 桌面应用
  • 插件系统
  • 后端
  • AI 应用

【免费下载链接】theia

Eclipse Theia is a cloud & desktop IDE framework implemented in TypeScript.

项目地址:https://gitcode.com/gh_mirrors/th/theia
点击查看免费下载

导读

在 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 插件的三条确定规则

  1. .mjs扩展名 → 永远按 ESM 加载return true),与package.json无关;
  2. .cjs扩展名 → 永远按 CommonJS 加载return false);
  3. 其他扩展名(如.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>;

这是有意为之的实现细节:如果不加这层包装,tscesbuildwebpack等面向 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-esmplugin-esm-mjs的两种 ESM 交付方式

仓库中还提供了另一个示例插件 plugin-esm,它与plugin-esm-mjs形成了一组完整的对照实验:

对比维度plugin-esmplugin-esm-mjs
入口文件extension.jsextension.mjs
package.json"type"声明为"module"不声明(或非module
ESM 判定依据package.jsontype字段.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 加载生效:

  1. 启动 Theia 应用并加载该插件;
  2. 执行命令面板中的Hello from plugin-esm-mjs命令;
  3. 观察弹出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.

项目地址:https://gitcode.com/gh_mirrors/th/theia
点击查看免费下载

相关推荐

上一篇:10个实用rp-hal示例解析:GPIO、I2C、SPI与UART通信全掌握
下一篇:从文件添加到上传完成:Uppy事件系统全解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询