创建Theia 扩展
例如,我们将添加一个菜单项 Say hello,它会显示通知“Hello world!”。本文将指导您完成所有必要的步骤。
Theia的架构
Theia 应用程序由扩展程序组成。扩展为特定功能提供一组小部件、命令、处理程序等。 Theia 本身提供了许多扩展,例如用于编辑器、终端、项目视图等。每个扩展都在自己的 npm 包中。
Theia 定义了大量的“贡献”接口,允许扩展将它们的行为添加到应用程序的各个方面。只需搜索名称为 *Contribution 的接口即可找到。扩展实现贡献接口,进而对外提供能力。在此示例中,我们将实现 CommandContribution 和 MenuContribution。扩展与 Theia 应用程序交互的其他方式是通过各种服务或管理器。
在 Theia 中,一切都是通过依赖注入连接起来的。一个扩展定义了一个或多个依赖注入模块。从而将“贡献”的实现绑定到“贡献”的相应接口。模块在扩展的 package.json 中列出。扩展可以为前端做出功能支持,例如提供 UI 扩展,也可以支持后端,例如贡献一个语言服务器。当应用程序启动时,所有这些模块全部配置在一个单一的全局依赖注入容器。然后,运行时将通过多重注入收集特定类型的贡献。
先决条件
Theia 存储库中提供了先决条件信息。(https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md#prerequisites)
项目布局
我们将创建一个名为 theia-hello-world-extension 的 monorepo(包含多个 npm 包的存储库),其中包含三个包:hello-world-extension、browser-app 和 electron-app。第一个包含我们的扩展,后两个 Theia 应用程序在浏览器和electron模式下运行我们的扩展。我们将使用 yarn 而不是 npm,因为它允许将这样的 monorepos 构建到工作空间中。在我们的例子中,每个工作区都包含自己的 npm 包。这些包的公共依赖项被yarn“提升”到它们的公共根目录。我们还将使用 lerna 用于跨工作区运行脚本。
为了简化此类存储库的设置,我们创建了一个代码生成器(https://www.npmjs.com/package/generator-theia-extension)来为项目搭建初始架构。它还将生成 hello-world 示例。运行它使用下面的命令:
npm install -g yo generator-theia-extension mkdir theia-hello-world-extension cd theia-hello-world-extension yo theia-extension # select the 'Hello World' option and complete the prompts
现在让我们看看生成的代码。 package.json 定义了工作空间、对 lerna 的依赖以及一些用于重建浏览器或electron本地包的脚本。
{ "private": true, "scripts": { "prepare": "lerna run prepare", "rebuild:browser": "theia rebuild:browser", "rebuild:electron": "theia rebuild:electron" }, "devDependencies": { "lerna": "2.4.0" }, "workspaces": [ "hello-world-extension", "browser-app", "electron-app" ] }
我们还有一个 lerna.json 文件来配置 lerna:
{ "lerna": "2.4.0", "version": "0.1.0", "useWorkspaces": true, "npmClient": "yarn", "command": { "run": { "stream": true } } }
lerna 是一种多包管理工具, 可以让你在主项目下管理多个子项目,从而解决了多个包互相依赖,且发布时需要手动维护多个包的问题(https://www.jianshu.com/p/e18d1bfad05a)
实现扩展
接下来让我们看看 hello-world-extension 文件夹中生成的代码。 让我们从 package.json 开始。 它指定了包的元数据、它对Theia 核心包的依赖项、一些脚本和开发依赖项以及 theia 扩展。
关键字 theia-extension 很重要:它允许 Theia 应用程序从 npm 识别和安装 Theia 扩展。
{ "name": "hello-world-extension", "keywords": [ "theia-extension" ], "version": "0.1.0", "files": [ "lib", "src" ], "dependencies": { "@theia/core": "latest" }, "devDependencies": { "rimraf": "latest", "typescript": "latest" }, "scripts": { "prepare": "yarn run clean && yarn run build", "clean": "rimraf lib", "build": "tsc", "watch": "tsc -w" }, "theiaExtensions": [ { "frontend": "lib/browser/hello-world-frontend-module" } ] }
最后一个属性 theiaExtensions 是我们列出导出 DI 模块的 JavaScript 模块的地方,这些 DI 模块定义了我们的扩展的贡献绑定。 在我们的例子中,我们只提供一个前端功能(一个命令和一个菜单项)。 类似地,您还可以定义对后端的贡献,例如 语言服务器的语言贡献。
在前端模块中,我们导出一个默认对象,它是一个 InversifyJS ContainerModule,其中包含命令贡献和菜单贡献的绑定。
export default new ContainerModule(bind => { // add your contribution bindings here bind(CommandContribution).to(HelloWorldCommandContribution); bind(MenuContribution).to(HelloWorldMenuContribution); });
command是定义 ID 和标签的普通数据结构。 命令的行为是通过在命令贡献中将处理程序注册到其 ID 来实现的。 生成器已经添加了一个命令和一个显示“Hello World!”的处理程序。
export const HelloWorldCommand = { id: 'HelloWorld.command', label: "Shows a message" }; @injectable() export class HelloWorldCommandContribution implements CommandContribution { constructor( @inject(MessageService) private readonly messageService: MessageService, ) { } registerCommands(registry: CommandRegistry): void { registry.registerCommand(HelloWorldCommand, { execute: () => this.messageService.info('Hello World!') }); } } ...
请注意我们如何在构造函数中使用 @inject 来获取 MessageService 作为属性,以及我们稍后在处理程序的实现中如何使用它。 这就是依赖注入的优雅之处:作为客户端,我们既不关心这些依赖来自哪里,也不关心它们的生命周期是什么。
为了让 UI 可以访问它,我们实现了一个 MenuContribution,将一个项目添加到菜单栏中编辑菜单的 Search/Replace 部分。
... @injectable() export class HelloWorldMenuContribution implements MenuContribution { registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(CommonMenus.EDIT_FIND, { commandId: HelloWorldCommand.id, label: 'Say Hello' }); } }
在浏览器中运行扩展
现在我们希望运行我们的扩展。 为此,生成器在文件夹 browser-app 中创建了一个 package.json。 它定义了一个 Theia 浏览器应用程序,其中包含几个静态包含的扩展,包括我们的 hello-world-extension。 此目录中的所有剩余文件都是由 yarn 在构建期间调用 theia-cli 工具自动生成的,也即是下面『scripts』部分中所定义的内容。
{ "name": "browser-app", "version": "0.1.0", "dependencies": { "@theia/core": "latest", "@theia/filesystem": "latest", "@theia/workspace": "latest", "@theia/preferences": "latest", "@theia/navigator": "latest", "@theia/process": "latest", "@theia/terminal": "latest", "@theia/editor": "latest", "@theia/languages": "latest", "@theia/markers": "latest", "@theia/monaco": "latest", "@theia/messages": "latest", "hello-world-extension": "0.1.0" }, "devDependencies": { "@theia/cli": "latest" }, "scripts": { "prepare": "theia build", "start": "theia start", "watch": "theia build --watch" }, "theia": { "target": "browser" } }
现在我们将所有部分组合在一起来构建和运行应用程序。 要运行浏览器应用程序,请输入:
# npm i lerna //是否需要执行这两条指令,未研究,官网文档不需要执行,实测,在linux下不用执行,mac下本实例运行失败,没跑起来
# lerna run prepare
cd browser-app yarn start <path to workspace>
浏览器访问 http://localhost:3000。 然后从菜单中选择“编辑”>“Say hello”:会弹出一条消息“Hello World!” 。效果如下图:
在electron中运行扩展
Electron 应用程序的 package.json 看起来几乎相同,除了 name 和 target 属性。
{ "name": "electron-app", ... "theia": { "target": "electron" } }
在运行Electron应用程序之前,您还必须重新构建一些原生模块:
yarn rebuild:electron cd electron-app yarn start <path to workspace>
部署扩展
如果您想让您的扩展程序公开可用,我们建议您将其发布到 npm。 这可以通过从扩展包的目录调用 yarn publish 来实现。 当然,您需要一个有效的npm帐户。