创建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帐户。

 

posted @ 2022-07-19 22:12  theiaide  阅读(498)  评论(1编辑  收藏  举报