Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove manual approach from composing application docu #621

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 21 additions & 110 deletions src/docs/authoring_extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,83 +4,31 @@ title: Authoring an Extension

# Authoring Theia Extensions

This guide will walk you through the process of creating Theia extensions and deploying them in your Theia-based application. Please make sure to be aware of the [different available extension mechanisms](/docs/extensions/) of Theia (Plugins vs. Extensions) before you continue reading.
This guide will walk you through the process of creating Theia extensions and deploying them in your Theia-based application. Please make sure to be aware of [how to create a Theia based application](/docs/composing_applications/) and the [different available extension mechanisms](/docs/extensions/) of Theia (Plugins vs. Extensions) before you continue reading.

As an example, we are going to add a menu item _Say hello_ that displays a notification "Hello world!". This article is guiding you through all the necessary steps.

## Theia’s Architecture

A Theia app is composed of so-called _extensions_. An extension provides a set of widgets, commands, handlers, etc. for a specific functionality. Theia itself ships a number of extensions, e.g. for editors, terminals, the project view etc. Each extension resides in its own npm package.

Theia defines a plethora of contribution interfaces that allow extensions to add their behaviour to various aspects of the application. Just search for interfaces with the name `*Contribution` to get an idea. An extension implements the contribution interfaces belonging to the functionality it wants to deliver. In this example, we are going to implement a `CommandContribution` and a `MenuContribution`. Other ways for extensions to interact with a Theia application are via one of the various _services_ or _managers_.

In Theia, everything is wired up via [dependency injection](/docs/services_and_contributions#dependency-injection-di). An extension defines one or more dependency injection modules. This is where it binds its contribution implementations to the respective contribution interface. The modules are listed in the `package.json` of the extension package. An extension can contribute to the frontend, e.g. providing a UI extension, as well as to the backend, e.g. contributing a language server. When the application starts, the union of all these modules is used to configure a single, global dependency injection container on each, the frontend and the backend. The runtime will then collect all contributions of a specific kind by means of a multi-inject.

## Prerequisites

Prerequisites information are available from the [Theia repository](https://github.com/eclipse-theia/theia/blob/master/doc/Developing.md#prerequisites).

## Project Layout

We are going to create a monorepo (a repository containing multiple npm packages) named `theia-hello-world-extension` containing three packages: `hello-world-extension`, `browser-app` and `electron-app`. The first contains our extension, the latter two the Theia applications to run our extension in browser and electron mode. We are going to use `yarn` instead of `npm`, because it allows to structure such monorepos into workspaces. In our case, each workspace contains its own `npm` package. Common dependencies of these packages are 'hoisted' by `yarn` to their common root directory. We are also going to use `lerna` to run scripts across workspaces.

To ease the setup of such a repository we have created a [code generator](https://www.npmjs.com/package/generator-theia-extension) to scaffold the project. It will also generate the `hello-world` example. Run it using
Please refer to the [guide to create Theia applications](/docs/composing_applications/) to get familiar with the default project layout and generate a Theia project with the example 'hello world' extension using our [Yeoman Generator](https://github.com/eclipse-theia/generator-theia-extension)

```bash
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
```
## A Custom Theia Extension

Let's have look at the generated code now. The root `package.json` defines the workspaces, the dependency to `lerna` and some scripts to rebuild the native packages for browser or electron.

```json
{
"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"
]
}
```

We also got a `lerna.json` file to configure `lerna`:

```json
{
"lerna": "2.4.0",
"version": "0.1.0",
"useWorkspaces": true,
"npmClient": "yarn",
"command": {
"run": {
"stream": true
}
}
}
```

## Implementing the Extension

Next let's look at the generated code for our extension in the `hello-world-extension` folder. Let’s start with the `package.json`. It specifies the package’s metadata, its dependencies to the (bleeding edge) Theia core package, a few scripts and dev dependencies, and the theia-extensions.
Let's look at the generated code for our extension in the `hello-world` folder. Let’s start with the `package.json`. It specifies the package’s metadata, its dependencies to the Theia core package, a few scripts and dev dependencies, and the theia-extension itself. Please note that the following listing might be outdated, please always refer to the generated examples from our [Yeoman Generator](https://github.com/eclipse-theia/generator-theia-extension)

The keyword `theia-extension` is important: It allows a Theia app to identify and install Theia extensions from `npm`.

```json
{
"name": "hello-world-extension",
"name": "hello-world",
"keywords": [
"theia-extension"
],
"version": "0.1.0",
"version": "0.0.0",
"files": [
"lib",
"src"
Expand All @@ -90,7 +38,7 @@ The keyword `theia-extension` is important: It allows a Theia app to identify an
},
"devDependencies": {
"rimraf": "latest",
"typescript": "latest"
"typescript": "~5.4.5"
},
"scripts": {
"prepare": "yarn run clean && yarn run build",
Expand All @@ -105,10 +53,13 @@ The keyword `theia-extension` is important: It allows a Theia app to identify an
]
}
```
As you can see, the extension is a dedciated package that just depends on Theia. However, as the extension contributes features to our application, it needs to be wired a runtime. To achive this in a modular way, in Theia, everything is wired up via [dependency injection](/docs/services_and_contributions#dependency-injection-di). An extension defines one or more dependency injection modules. This is where it binds its contribution implementations to the respective contribution interface. The modules are listed in the `package.json` of the extension package. An extension can contribute to the frontend, e.g. providing a UI extension, as well as to the backend, e.g. contributing a language server. When the application starts, the union of all these modules is used to configure a single, global dependency injection container on each, the frontend and the backend. The runtime will then collect all contributions of a specific kind by means of a multi-inject.

The last property `theiaExtensions` in the packahe.json above is where we list the JavaScript modules that export the DI modules defining the contribution bindings of our extension. In our case, we only provide a frontend capability (a command and a menu entry). Analogously, you could also define contributions to the backend, e.g. a language contribution with a language server.

The last property `theiaExtensions` is where we list the JavaScript modules that export the DI modules defining the contribution bindings of our extension. In our case, we only provide a frontend capability (a command and a menu entry). Analogously, you could also define contributions to the backend, e.g. a language contribution with a language server.
Theia defines a plethora of contribution interfaces that allow extensions to add their behaviour to various aspects of the application. Browse the documentation section 'Platform Concepts & APIs' or search for interfaces with the name `*Contribution` to get an idea. An extension implements the contribution interfaces belonging to the functionality it wants to deliver. In this example, we are going to implement a `CommandContribution` and a `MenuContribution`. Other ways for extensions to interact with a Theia application are via one of the various _services_ or _managers_.

In the frontend module we export a default object that is a [InversifyJS `ContainerModule`](https://github.com/inversify/InversifyJS/blob/master/wiki/container_modules.md) with bindings for a command contribution and a menu contribution.
In the frontend module we export a default object that is a [InversifyJS `ContainerModule`](https://github.com/inversify/InversifyJS/blob/master/wiki/container_modules.md) with bindings for a command contribution and a menu contribution. Please see our [dependency injection guide](/docs/services_and_contributions/) for more details.

```typescript
export default new ContainerModule(bind => {
Expand Down Expand Up @@ -160,75 +111,35 @@ export class HelloWorldMenuContribution implements MenuContribution {
}
```

## Executing the Extension In the Browser
## Adding Extensions to a Theia application

Now we want to see our extension in action. For this purpose, the generator has created a `package.json` in the folder `browser-app`. It defines a Theia browser application with a couple of statically included extensions, including our `hello-world-extension`. All remaining files in this directory have been auto-generated by `yarn` calling the `theia-cli` tool during the build, as defined in the scripts section.
To make sure your extension is included in your Theia application, list it as a dependency in your browser or electron app, e.g. like this:

```json
{
"private": true,
"name": "browser-app",
"version": "0.1.0",
"version": "0.0.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"
...
"hello-world": "0.0.0"
},
"devDependencies": {
"@theia/cli": "latest"
},
"scripts": {
"prepare": "theia build",
"bundle": "yarn rebuild && theia build --mode development",
"rebuild": "theia rebuild:browser --cacheRoot ..",
"start": "theia start",
"watch": "theia build --watch"
"watch": "yarn rebuild && theia build --watch --mode development"
},
"theia": {
"target": "browser"
}
}
```

Now we have all pieces together to build and run the application.
To run the browser app, enter:

```bash
cd browser-app
yarn start <path to workspace>
```

Point your browser to <http://localhost:3000>. Then choose _Edit > Say Hello_ from the menu: A message "Hello World!" should pop up.

## Executing the Extension In Electron

The `package.json` for the Electron app looks almost the same, except for the name and the target property.

```json
{
"name": "electron-app",
...
"theia": {
"target": "electron"
}
}
```

Before running the electron app, you additionally have to rebuild some native modules:

```bash
yarn rebuild:electron
cd electron-app
yarn start <path to workspace>
```

## Deploying the Extension
JonasHelming marked this conversation as resolved.
Show resolved Hide resolved

If you want to make your extension publicly available, we recommend publishing it to npm. This can be achieved by calling `yarn publish` from the extension package's directory. Of course, you need a valid account for that.
10 changes: 10 additions & 0 deletions src/docs/authoring_vscode_extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,13 @@ To also enable debugging from your development IDE (VS Code), you need to setup
]
}
```

## Troubleshooting

### Plugins not appearing

If no plugins are available in the running Theia instance, it may be that you need to tell Theia where to find the downloaded plugins.
The example above sets the `--plugins` switch in the `start` command which should be sufficient.
However, if running `theia start` directly, you can alternatively set an environment variable to achieve the same thing:

export THEIA_DEFAULT_PLUGINS=local-dir:plugins
Loading
Loading