资讯详情

Eclipse Theia 插件开发:利用 `.mjs` 扩展名交付 ESM 插件(plugin-esm-mjs 示例深度解析)

📅 2026/9/20 3:48:10 | 华诺云谱 👁 阅读
Eclipse Theia 插件开发:利用 `.mjs` 扩展名交付 ESM 插件(plugin-esm-mjs 示例深度解析)
IDE代码编辑器开发工具前端桌面应用插件系统后端AI 应用【免费下载链接】theiaEclipse Theia is a cloud desktop IDE framework implemented in TypeScript.项目地址https://gitcode.com/gh_mirrors/th/theia点击查看免费下载导读在 Eclipse Theia 的插件系统中插件既可以按传统 CommonJSrequire/exports方式加载也可以按现代 ECMAScript Moduleimport/export方式加载。本篇文章以仓库中的 plugin-esm-mjs 示例插件为入口深入讲解不修改package.json的type字段、仅凭.mjs文件扩展名即可让 Theia 将插件入口识别为 ESM 并改用import()加载的原理与完整实操。读完本文你将掌握 Theia 判定 ESM 插件的三条规则、.mjs与type: module两种 ESM 交付方式的差异以及如何编写、配置和运行一个.mjs格式的 VS Code 风格插件。一、示例插件是什么plugin-esm-mjsplugin-esm-mjs是 Theia 仓库中sample-plugins/sample-namespace命名空间下的一个样例插件。它本质上是一个以.mjs文件作为入口的 VS Code 扩展用于演示 Theia 插件宿主plugin host对 ESM 模块格式的支持。该插件目录只包含四个文件extension.mjs —— 插件入口使用 ESM 语法package.json —— 插件清单main字段指向extension.mjsREADME.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 Nodes 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) Promiseany;这是有意为之的实现细节如果不加这层包装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、.mjsconst 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-esmplugin-esm-mjs入口文件extension.jsextension.mjspackage.json的type声明为module不声明或非moduleESM 判定依据package.json的type字段.mjs扩展名硬性规则入口写法import * as vscode from vscodeexport function activate(...)import { commands, window } from vscodeexport 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包内强制单个文件走 CommonJSTheia 的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 应用【免费下载链接】theiaEclipse Theia is a cloud desktop IDE framework implemented in TypeScript.项目地址https://gitcode.com/gh_mirrors/th/theia点击查看免费下载相关推荐Eclipse Theia 插件宿主如何加载 ESM 插件以 plugin-esm 示例插件为剖析样本Eclipse Theia 插件宿主如何加载 ESM 插件以 plugin esm 示例插件为剖析样本 导读 随着 VS Code 官方内置扩展逐步以 ECMIDE代码编辑器开发工具前端桌面应用插件系统后端AI 应用Eclipse Theia Headless 插件扩展plugin-ext-headless深度解析后端无头插件宿主的架构与实践Eclipse Theia Headless 插件扩展plugin ext headless深度解析后端无头插件宿主的架构与实践 导读 theia/plIDE代码编辑器开发工具前端桌面应用插件系统后端AI 应用zkp-hmac-communication-js示例代码详解example1.mjs到example3.mjs全解析zkp hmac communication js示例代码详解example1.mjs到example3.mjs全解析 zkp hmac communicat上一篇10个实用rp-hal示例解析GPIO、I2C、SPI与UART通信全掌握下一篇从文件添加到上传完成Uppy事件系统全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。