From 6359e0cd83a75f96d12b85bb62ff378fe2733000 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Tue, 5 Dec 2023 14:53:14 +0100 Subject: [PATCH 1/5] Improve documentation on the addition of Plugin API in the plugin host Fixes https://github.com/eclipse-theia/theia/issues/13067 --- .../doc/how-to-add-new-plugin-namespace.md | 238 +++++++++++++++--- 1 file changed, 200 insertions(+), 38 deletions(-) diff --git a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md b/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md index e8f8611374770..e85ffad22a5c3 100644 --- a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md +++ b/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md @@ -1,26 +1,23 @@ -# This document describes how to add new plugin api namespace +# How to add a new plugin API namespace -New Plugin API namespace should be packaged as Theia extension +This document describes how to add new plugin API namespace in the plugin host. +Depending on the plugin host we can either provide a frontend or backend API extension: -## Provide your API or namespace +- In the backend plugin host that runs in the Node environment in a separate process, we adapt the module loading to return a custom API object instead of loading a module with a particular name. +- In the frontend plugin host that runs in the browser environment via a web worker, we import the API scripts and put it in the global context. -This API developed in the way that you provide your API as separate npm package. -In that package you can declare your api. -Example `foo.d.ts`: +In this document we focus on the implementation of a backend plugin API. +However, both APIs can be provided by implementing and binding an `ExtPluginApiProvider` which should be packaged as a Theia extension. -```typescript - declare module '@bar/foo' { - export namespace fooBar { - export function getFoo(): Foo; - } - } -``` +## Declare your plugin API provider + +The plugin API provider is executed on the respective plugin host to add your custom API namespace. -## Declare `ExtPluginApiProvider` implementation +Example Foo Plugin API provider: ```typescript @injectable() -export class FooPluginApiProvider implements ExtPluginApiProvider { +export class FooExtPluginApiProvider implements ExtPluginApiProvider { provideApi(): ExtPluginApi { return { frontendExtApi: { @@ -28,33 +25,66 @@ export class FooPluginApiProvider implements ExtPluginApiProvider { initFunction: 'fooInitializationFunction', initVariable: 'foo_global_variable' }, - backendInitPath: path.join(__dirname, 'path/to/backend/foo/implementation.js') + backendInitPath: path.join(__dirname, 'foo-init') }; } } ``` -## Then you need to register `FooPluginApiProvider`, add next sample in your backend module +Register your Plugin API provider in a backend module: -Example: +```typescript + bind(FooExtPluginApiProvider).toSelf().inSingletonScope(); + bind(Symbol.for(ExtPluginApiProvider)).toService(FooExtPluginApiProvider); +``` + +## Define your API + +To ease the usage of your API, it should be developed as separate npm package that can be easily imported without any additional dependencies, cf, the VS Code API or the Theia Plugin API. + +Example `foo.d.ts`: ```typescript - bind(FooPluginApiProvider).toSelf().inSingletonScope(); - bind(Symbol.for(ExtPluginApiProvider)).toService(FooPluginApiProvider); +declare module '@bar/foo' { + export namespace fooBar { + export function getFoo(): Promise; + } +} ``` -## Next you need to implement `ExtPluginApiBackendInitializationFn`, which should handle `@bar/foo` module loading and instantiate `@foo/bar` API object, `path/to/backend/foo/implementation.js` example : +## Implement your plugin API provider + +In our example, we aim to provide a new API object for the backend. +Theia expects that the `backendInitPath` that we specified in our API provider is a function called `provideApi` that follows the `ExtPluginApiBackendInitializationFn` signature. + +Example `foo-init.ts`: ```typescript -export const provideApi: ExtPluginApiBackendInitializationFn = (rpc: RPCProtocol, pluginManager: PluginManager) => { - cheApiFactory = createAPIFactory(rpc); - plugins = pluginManager; +import * as fooBarAPI from '@bar/foo'; + +// Factory to create an API object for each plugin. +let apiFactory: (plugin: Plugin) => typeof fooBarAPI; + +// Map key is the plugin ID. Map value is the FooBar API object. +const pluginsApiImpl = new Map(); + +// Singleton API object to use as a last resort. +let defaultApi: typeof fooBarAPI; + +// Have we hooked into the module loader yet? +let hookedModuleLoader = false; + +let plugins: PluginManager; - if (!isLoadOverride) { +// Theia expects an exported 'provideApi' function +export const provideApi: ExtPluginApiBackendInitializationFn = (rpc: RPCProtocol, manager: PluginManager) => { + apiFactory = createAPIFactory(rpc); + plugins = manager; + + if (!hookedModuleLoader) { overrideInternalLoad(); - isLoadOverride = true; + hookedModuleLoader = true; } - }; function overrideInternalLoad(): void { @@ -63,14 +93,16 @@ function overrideInternalLoad(): void { module._load = function (request: string, parent: any, isMain: {}) { if (request !== '@bar/foo') { + // Pass the request to the next implementation down the chain return internalLoad.apply(this, arguments); } + // create custom API object and return that as a result of loading '@bar/foo' const plugin = findPlugin(parent.filename); if (plugin) { let apiImpl = pluginsApiImpl.get(plugin.model.id); if (!apiImpl) { - apiImpl = cheApiFactory(plugin); + apiImpl = apiFactory(plugin); pluginsApiImpl.set(plugin.model.id, apiImpl); } return apiImpl; @@ -78,7 +110,7 @@ function overrideInternalLoad(): void { if (!defaultApi) { console.warn(`Could not identify plugin for '@bar/foo' require call from ${parent.filename}`); - defaultApi = cheApiFactory(emptyPlugin); + defaultApi = apiFactory(emptyPlugin); } return defaultApi; @@ -90,23 +122,153 @@ function findPlugin(filePath: string): Plugin | undefined { } ``` -## Next you need to implement `createAPIFactory` factory function +## Implement your API object + +We create a dedicated API object for each individual plugin as part of the module loading process. +Each API object is returned as part of the module loading process if a script imports `@bar/foo` and should therefore match the API definition that we provided in the `*.d.ts` file. +Multiple imports will not lead to the creation of multiple API objects as we cache it in our custom `overrideInternalLoad` function. -Example: +Example `foo-init.ts` (continued): ```typescript -import * as fooApi from '@bar/foo'; export function createAPIFactory(rpc: RPCProtocol): ApiFactory { - const fooBarImpl = new FooBarImpl(rpc); - return function (plugin: Plugin): typeof fooApi { - const FooBar: typeof fooApi.fooBar = { - getFoo(): fooApi.Foo{ - return fooBarImpl.getFooImpl(); + const fooExtImpl = new FooExtImpl(rpc); + return function (plugin: Plugin): typeof fooBarAPI { + const FooBar: typeof fooBarAPI.fooBar = { + getFoo(): Promise { + return fooExtImpl.getFooImpl(); } } - return { + return { fooBar : FooBar }; } +} +``` + +In the example above the API object creates a local object that will fulfill the API contract. +The implementation details are hidden by the object and it could be a local implementation that only lives inside the plugin host but it could also be an implementation that uses the `RPCProtocol` to communicate with the main application to retrieve additional information. + +### Implementing Main-Ext communication + +In this document, we will only highlight the individual parts needed to establish the communication between the main application and the external plugin host. +For a more elaborate example of an API that communicates with the main application, please have a look at the definition of the [Theia Plugin API](https://github.com/eclipse-theia/theia/blob/master/doc/Plugin-API.md). + +First, we need to establish the communication on the RPC protocol by providing an implementation for our own side and generating a proxy for the opposite side. +Proxies are identified using dedicated identifiers so we set them up first, together with the expected interfaces. +`Ext` and `Main` interfaces contain the functions called over RCP and must start with `$`. +Due to the asynchronous nature of the communication over RPC, the result should always be a `Promise` or `PromiseLike`. + +Example `common/foo-api-rpc.ts`: + +```typescript +export interface FooMain { + $getFooImpl(): Promise; +} + +export interface FooExt { + // placeholder for callbacks for the main application to the extension +} + +// Plugin host will obtain a proxy using these IDs, main application will register an implementation for it. +export const PLUGIN_RPC_CONTEXT = { + FOO_MAIN: >createProxyIdentifier('FooMain') +}; + +// Main application will obtain a proxy using these IDs, plugin host will register an implementation for it. +export const MAIN_RPC_CONTEXT = { + FOO_EXT: >createProxyIdentifier('FooExt') +}; +``` + +On the plugin host side we can register our implementation and retrieve the proxy as part of our `createAPIFactory` implementation: + +Example `foo-ext.ts`: + +```typescript +export class FooExtImpl implements FooExt { + // Main application RCP counterpart + private proxy: FooMain; + + constructor(rpc: RPCProtocol) { + rpc.set(MAIN_RPC_CONTEXT.FOO_EXT, this); // register ourselves + this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.FOO_MAIN); // retrieve proxy + } + + getFooImpl(): Promise { + return this.proxy.$getFooImpl(); + } +} +``` + +On the main side we need to implement the counterpart of the ExtPluginApiProvider, the `MainPluginApiProvider`, and expose it in a browser frontend module: + +Example `foo-main.ts`: + +```typescript +@injectable() +export class FooMainImpl implements FooMain { + protected proxy: FooExt; + + init(rpc: RPCProtocol) { + this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.FOO_EXT); + } + + async $getFooImpl(): Promise { + return new Foo(); + } +} + +@injectable() +export class FooMainPluginApiProvider implements MainPluginApiProvider { + @inject(MessageService) protected messageService: MessageService; + + initialize(rpc: RPCProtocol, container: interfaces.Container): void { + this.messageService.info('We were called from an extension!'); + // create a new FooMainImpl as it is not bound as singleton + const fooMainImpl = container.get(FooMainImpl); + fooMainImpl.init(rpc); + rpc.set(PLUGIN_RPC_CONTEXT.FOO_MAIN, fooMainImpl); + } +} + +export default new ContainerModule(bind => { + bind(FooMainImpl).toSelf(); + bind(MainPluginApiProvider).to(FooMainPluginApiProvider).inSingletonScope(); +}); +``` + +In this example, we can already see the big advantage of going to the main application side as we have full access to our Theia services. + +## Usage in a plugin + +When using the API in a plugin the user can simply use the API as follows: + +```typescript +import * as foo from '@bar/foo'; + +foo.fooBar.getFoo(); +``` + +## Packaging + +When bundling our application with the generated `gen-webpack.node.config.js` we need to make sure that our initialization function is bundled as a `commonjs2` library so it can be dynamically loaded. + +```typescript +const configs = require('./gen-webpack.config.js'); +const nodeConfig = require('./gen-webpack.node.config.js'); + +if (nodeConfig.config.entry) { + /** + * Add our initialization function. If unsure, look at the already generated entries for + * the nodeConfig where an entry is added for the default 'backend-init-theia' initialization. + */ + nodeConfig.config.entry['foo-init'] = { + import: require.resolve('@namespace/package/lib/node/foo-init'), + library: { type: 'commonjs2' } + }; +} + +module.exports = [...configs, nodeConfig.config]; ``` From 33b0cde6f248f1d91e9dd3a158c5aaea573ab083 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 14 Dec 2023 16:58:51 +0100 Subject: [PATCH 2/5] Further improve documentation --- packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md b/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md index e85ffad22a5c3..b640cb5244957 100644 --- a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md +++ b/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md @@ -147,7 +147,7 @@ export function createAPIFactory(rpc: RPCProtocol): ApiFactory { ``` In the example above the API object creates a local object that will fulfill the API contract. -The implementation details are hidden by the object and it could be a local implementation that only lives inside the plugin host but it could also be an implementation that uses the `RPCProtocol` to communicate with the main application to retrieve additional information. +The implementation details are hidden by the object and it could be a local implementation that only lives inside the plugin host but it could also be an implementation that uses the `RPCProtocol` to communicate with the main application to trigger changes, register functionality or retrieve information. ### Implementing Main-Ext communication @@ -172,12 +172,12 @@ export interface FooExt { // Plugin host will obtain a proxy using these IDs, main application will register an implementation for it. export const PLUGIN_RPC_CONTEXT = { - FOO_MAIN: >createProxyIdentifier('FooMain') + FOO_MAIN: createProxyIdentifier('FooMain') }; // Main application will obtain a proxy using these IDs, plugin host will register an implementation for it. export const MAIN_RPC_CONTEXT = { - FOO_EXT: >createProxyIdentifier('FooExt') + FOO_EXT: createProxyIdentifier('FooExt') }; ``` From 42de2767f1e1d07b28c76c859522a6e543f43f16 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Thu, 14 Dec 2023 17:18:23 +0100 Subject: [PATCH 3/5] Add another message service message --- packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md b/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md index b640cb5244957..ed432b83ba896 100644 --- a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md +++ b/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md @@ -208,13 +208,16 @@ Example `foo-main.ts`: ```typescript @injectable() export class FooMainImpl implements FooMain { + @inject(MessageService) protected messageService: MessageService; protected proxy: FooExt; init(rpc: RPCProtocol) { + // We would use this if we had a need to call back into the plugin-host/plugin this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.FOO_EXT); } async $getFooImpl(): Promise { + this.messageService.info('We were called from the plugin-host at the behest of the plugin.'); return new Foo(); } } @@ -224,7 +227,7 @@ export class FooMainPluginApiProvider implements MainPluginApiProvider { @inject(MessageService) protected messageService: MessageService; initialize(rpc: RPCProtocol, container: interfaces.Container): void { - this.messageService.info('We were called from an extension!'); + this.messageService.info('Initialize RPC communication for FooMain!'); // create a new FooMainImpl as it is not bound as singleton const fooMainImpl = container.get(FooMainImpl); fooMainImpl.init(rpc); From bd1d132ec02b6a9ef503e76f97398357dbd141b1 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 15 Dec 2023 09:01:09 +0100 Subject: [PATCH 4/5] Rename the how-to-add-new-plugin-namespaces document and refine --- doc/Plugin-API.md | 6 +++--- ...ce.md => how-to-add-new-custom-plugin-api.md} | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) rename packages/plugin-ext/doc/{how-to-add-new-plugin-namespace.md => how-to-add-new-custom-plugin-api.md} (93%) diff --git a/doc/Plugin-API.md b/doc/Plugin-API.md index 729619fc5b04d..fc63f73818803 100644 --- a/doc/Plugin-API.md +++ b/doc/Plugin-API.md @@ -75,7 +75,7 @@ The `Ext` side then gets the cached object, executes appropriate functions and r ## Adding new API -This section gives an introduction to extending Theia’s plugin API. If you want to add a whole new namespace in your own extension, see this [readme](https://github.com/eclipse-theia/theia/blob/master/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md). +This section gives an introduction to extending Theia’s plugin API. If you want to add a complete custom plugin API in your own extension, see this [readme](https://github.com/eclipse-theia/theia/blob/master/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md). For adding new API, the first step is to declare it in the [theia.d.ts](https://github.com/eclipse-theia/theia/blob/master/packages/plugin/src/theia.d.ts) file in the plugin package. In a second step, the implementation for the new API must be made available in the returned object of the API factory in [plugin-context.ts](https://github.com/eclipse-theia/theia/blob/master/packages/plugin-ext/src/plugin/plugin-context.ts). @@ -173,7 +173,7 @@ They can be added here and the added to the API object created in the API factor Talk by Thomas Maeder on writing plugin API: -Adding a new plugin API namespace outside of theia plugin API: [how-to-add-new-plugin-namespace.md](https://github.com/eclipse-theia/theia/blob/master/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md) +Adding a new custom plugin API outside of Theia plugin API: [how-to-add-new-custom-plugin-api.md](https://github.com/eclipse-theia/theia/blob/master/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md) Theia Plugin Implementation wiki page: @@ -183,4 +183,4 @@ Theia versus VS Code API Comparator: -Example of creating a custom namespace API and using in VS Code extensions: https://github.com/thegecko/vscode-theia-extension +Example of creating a custom namespace API and using in VS Code extensions: diff --git a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md b/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md similarity index 93% rename from packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md rename to packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md index ed432b83ba896..f74ba74b8946c 100644 --- a/packages/plugin-ext/doc/how-to-add-new-plugin-namespace.md +++ b/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md @@ -1,17 +1,21 @@ -# How to add a new plugin API namespace +# How to add new custom plugin API -This document describes how to add new plugin API namespace in the plugin host. -Depending on the plugin host we can either provide a frontend or backend API extension: +As a Theia developer, you might want to make your app extensible by plugins in ways that are unique to your application. +That will require API that goes beyond what's in the VS Code Extension API and the Theia plugin API. +You can do that by implementing a Theia extension that creates and exposes an API object within the plugin host. +The API object can be imported by your plugins and exposes one or more API namespaces. + +Depending on the plugin host we can either provide a frontend or backend plugin API: - In the backend plugin host that runs in the Node environment in a separate process, we adapt the module loading to return a custom API object instead of loading a module with a particular name. - In the frontend plugin host that runs in the browser environment via a web worker, we import the API scripts and put it in the global context. -In this document we focus on the implementation of a backend plugin API. +In this document we focus on the implementation of a custom backend plugin API. However, both APIs can be provided by implementing and binding an `ExtPluginApiProvider` which should be packaged as a Theia extension. ## Declare your plugin API provider -The plugin API provider is executed on the respective plugin host to add your custom API namespace. +The plugin API provider is executed on the respective plugin host to add your custom API object and namespaces. Example Foo Plugin API provider: @@ -149,7 +153,7 @@ export function createAPIFactory(rpc: RPCProtocol): ApiFactory { In the example above the API object creates a local object that will fulfill the API contract. The implementation details are hidden by the object and it could be a local implementation that only lives inside the plugin host but it could also be an implementation that uses the `RPCProtocol` to communicate with the main application to trigger changes, register functionality or retrieve information. -### Implementing Main-Ext communication +### Implement Main-Ext communication In this document, we will only highlight the individual parts needed to establish the communication between the main application and the external plugin host. For a more elaborate example of an API that communicates with the main application, please have a look at the definition of the [Theia Plugin API](https://github.com/eclipse-theia/theia/blob/master/doc/Plugin-API.md). From 5e035d780c486f5e2eacbb5cfd5184b512bc3e9b Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 15 Dec 2023 15:31:02 +0100 Subject: [PATCH 5/5] Further improve documentation --- .../doc/how-to-add-new-custom-plugin-api.md | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md b/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md index f74ba74b8946c..8ef05c2e32592 100644 --- a/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md +++ b/packages/plugin-ext/doc/how-to-add-new-custom-plugin-api.md @@ -16,6 +16,7 @@ However, both APIs can be provided by implementing and binding an `ExtPluginApiP ## Declare your plugin API provider The plugin API provider is executed on the respective plugin host to add your custom API object and namespaces. +Add `@theia/plugin-ext` as a dependency in your `package.json` Example Foo Plugin API provider: @@ -50,6 +51,8 @@ Example `foo.d.ts`: ```typescript declare module '@bar/foo' { + export class Foo { } + export namespace fooBar { export function getFoo(): Promise; } @@ -61,7 +64,7 @@ declare module '@bar/foo' { In our example, we aim to provide a new API object for the backend. Theia expects that the `backendInitPath` that we specified in our API provider is a function called `provideApi` that follows the `ExtPluginApiBackendInitializationFn` signature. -Example `foo-init.ts`: +Example `node/foo-init.ts`: ```typescript import * as fooBarAPI from '@bar/foo'; @@ -132,7 +135,7 @@ We create a dedicated API object for each individual plugin as part of the modul Each API object is returned as part of the module loading process if a script imports `@bar/foo` and should therefore match the API definition that we provided in the `*.d.ts` file. Multiple imports will not lead to the creation of multiple API objects as we cache it in our custom `overrideInternalLoad` function. -Example `foo-init.ts` (continued): +Example `node/foo-init.ts` (continued): ```typescript export function createAPIFactory(rpc: RPCProtocol): ApiFactory { @@ -175,19 +178,19 @@ export interface FooExt { } // Plugin host will obtain a proxy using these IDs, main application will register an implementation for it. -export const PLUGIN_RPC_CONTEXT = { +export const FOO_PLUGIN_RPC_CONTEXT = { FOO_MAIN: createProxyIdentifier('FooMain') }; // Main application will obtain a proxy using these IDs, plugin host will register an implementation for it. -export const MAIN_RPC_CONTEXT = { +export const FOO_MAIN_RPC_CONTEXT = { FOO_EXT: createProxyIdentifier('FooExt') }; ``` On the plugin host side we can register our implementation and retrieve the proxy as part of our `createAPIFactory` implementation: -Example `foo-ext.ts`: +Example `plugin/foo-ext.ts`: ```typescript export class FooExtImpl implements FooExt { @@ -195,8 +198,8 @@ export class FooExtImpl implements FooExt { private proxy: FooMain; constructor(rpc: RPCProtocol) { - rpc.set(MAIN_RPC_CONTEXT.FOO_EXT, this); // register ourselves - this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.FOO_MAIN); // retrieve proxy + rpc.set(FOO_MAIN_RPC_CONTEXT.FOO_EXT, this); // register ourselves + this.proxy = rpc.getProxy(FOO_PLUGIN_RPC_CONTEXT.FOO_MAIN); // retrieve proxy } getFooImpl(): Promise { @@ -207,7 +210,7 @@ export class FooExtImpl implements FooExt { On the main side we need to implement the counterpart of the ExtPluginApiProvider, the `MainPluginApiProvider`, and expose it in a browser frontend module: -Example `foo-main.ts`: +Example `main/browser/foo-main.ts`: ```typescript @injectable() @@ -217,7 +220,7 @@ export class FooMainImpl implements FooMain { init(rpc: RPCProtocol) { // We would use this if we had a need to call back into the plugin-host/plugin - this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.FOO_EXT); + this.proxy = rpc.getProxy(FOO_MAIN_RPC_CONTEXT.FOO_EXT); } async $getFooImpl(): Promise { @@ -235,7 +238,7 @@ export class FooMainPluginApiProvider implements MainPluginApiProvider { // create a new FooMainImpl as it is not bound as singleton const fooMainImpl = container.get(FooMainImpl); fooMainImpl.init(rpc); - rpc.set(PLUGIN_RPC_CONTEXT.FOO_MAIN, fooMainImpl); + rpc.set(FOO_PLUGIN_RPC_CONTEXT.FOO_MAIN, fooMainImpl); } } @@ -260,6 +263,7 @@ foo.fooBar.getFoo(); ## Packaging When bundling our application with the generated `gen-webpack.node.config.js` we need to make sure that our initialization function is bundled as a `commonjs2` library so it can be dynamically loaded. +Adjust the `webpack.config.js` accordingly: ```typescript const configs = require('./gen-webpack.config.js');