
# 开发第三方扩展
CodeArts IDE为扩展开发者提供了扩展创建、开发调试以及打包发布的完整能力。
#### 安装环境
在开始扩展开发之前，请在本地命令行客户端（打开cmd）或CodeArts IDE终端中检查是否已安装Node.js 16.10.0或以上版本 (<https://nodejs.org/en/>)。若已安装，可使用命令行node -v以及npm -v查看相应的安装版本。
#### 扩展创建
1. 打开CodeArts IDE，单击菜单"文件 \>新建 \>工程"，选择"扩展"。如下图所示： 
   图1创建扩展   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002382944169.png "点击放大")
   - 类型：支持以下三种类型。
     - 简单扩展：不包含后端模板。
     
     - 支持可展示的Webview以及弹窗的扩展：包含前端和后端的模板。
     
     - 支持注册创建项目向导的扩展：包含前端和后端的模板。
      
   
   - 发布商：发布商必须为扩展市场中已创建的发布商，否则将无法在扩展市场上发布扩展，可在发布前在已创建扩展工程中的package.json 文件中修改"publisher"字段。
   
   
   
   
2. 单击"创建"，等待扩展工程创建完成，选择是否在当前窗口打开新建的扩展工程。如下图所示： 
   图2选择文件夹打开方式   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002334270914.png "点击放大")
   
   
 
#### 后端调试
在扩展的src/node/目录下存放的是扩展的后端代码，后端代码运行在nodejs环境中，扩展工程在创建的时候已经默认生成了一个后端文件backend.ts，对于轻量级的扩展，只需要在该文件中添加自己想要实现的业务功能即可，该文件包含了三个默认的方法 init()、run()、stop()。另外还默认添加了一个doSomething方法，这个方法仅仅是作为示例使用，开发者可以根据需要进行修改或删除。
此处简单介绍init，run和stop方法：
- init 函数：作为后端实例的初始化方法，可以在扩展启动的时候进行一些初始化操作，写在该函数中的代码一定会先于 run 和其他函数被调用，这里需要注意的是，对于前端暴露给后端的函数不能在 init 函数中进行调用，也就是不能在 init 方法中执行 this.plugin.call 调用。
- run 函数：作为后端实例的主逻辑函数，承担着业务功能入口的作用，在该函数中可以方便地调用 CodeArts IDE 的 API，比如codearts.window.showInformationMessage(\`hello world!\`); 也可以调用前端暴露出来的函数，也就是可以在该方法中执行 this.plugin.call 调用。
- stop 函数：将会在扩展被停止前被调用，如有需要可以进行一些资源清理的操作。
1. 添加断点：在backend.ts 的 run() 函数中添加一个断点。如下图所示： 
   图3添加断点   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002368309073.png "点击放大")
   
   
2. 打开调试窗口：按 F5 或者单击右上角调试工具栏中的开始调试按钮，打开"扩展开发宿主"窗口。如下图所示： 
   图4单击调试按钮   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002349285428.png "点击放大")
   
   
3. 进入断点，开始调试。如下图所示： 
   图5进入断点，开始调试   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002368468953.png "点击放大")
   
   
 
#### 前端调试
与扩展的后端不同，前端的代码最终将被编译并运行于浏览器环境中，前端的代码存放于 src/browser 目录中，扩展工程在创建的时候会默认生成两个前端源码文件 frontend.ts 和 dynamic-webview.ts。这两个文件的内容与后端 backend.ts 的结构非常相似，只不过运行的环境不同而已，这里就不再重复对这两个文件中 init()、run()、stop() 方法进行介绍。由于前端运行在浏览器环境中，代码调试将借助于浏览器自带的调试功能。如果需要自动重新编译前端代码，可以在终端中执行命令npm run watch-browser，然后再运行调试。在启动调试后如果修改了代码，只需在调试窗口按 Ctrl+R 重新加载窗口即可看到修改后的效果。
1. 前端调试前，需要先把 webpack.config.js 文件中的 devtool 配置为 'inline-source-map'，然后在命令行执行 npm run prepare。如下图所示： 
   图6修改webpack文件   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002368468945.png "点击放大")
   
   
2. 添加断点：在frontend.ts的run() 函数中添加一个断点。
3. 打开调试窗口：按 F5 或者单击右上角调试工具栏中的开始调试按钮，打开"扩展开发宿主"窗口。如下图所示： 
   图7添加断点，单击调试按钮   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002334270902.png "点击放大")
   
   
4. 打开扩展注册的视图，进入断点，进行前端的调试，若无法进入断点，可以使用"Ctrl + Shift + I"打开"开发人员工具"，再"Ctrl + R"重新加载当前窗口。如下图所示： 
   图8进入断点   
   ![](https://support.huaweicloud.com/usermanual-codeartside/zh-cn_image_0000002334270906.png "点击放大")
   
   
 
#### 后端调用前端
- **在前端定义暴露给后端的方法**
  打开 src/browser/frontend.ts 文件，其中 Frontend 类继承自 AbstractFrontend，除了需要实现的 init()、run()、stop() 这三个方法，自定义了一个 myApi(message: string) 方法，如果想要把 myApi 方法暴露给后端去调用，只需要在函数上添加 @expose('function_id') 修饰器。
  **注意：多个expose修饰器中的function_id不能重复**。
  ```
  @expose('myplugin.page.myApi')public myApi(message: string): string {
      console.log(message);
      return 'this is a return value from frontend function';
  }
  ```
  

- **在后端调用前端暴露的方法**
  打开src/node/backend.ts 文件，其中Backend类继承自AbstractBackend，需要实现 init()， run()， stop() 这三个方法，可以在run() 方法中通过this.plugin.call() 调用在前端定义的 myApi 方法并获取到返回值。
  ```
  public async run(): Promise<void> {
  const=awaitthis.plugin.call('view_type_of_your_plugin_view::myplugin.page.myApi','this is a function call from backend');    const retValue = await this.plugin.call('view_type_of_your_plugin_view::myplugin.page.myApi', 'this is a function call from backend');
  this.log(.INFO,);   
  this.plugin.log(LogLevel.INFO, retValue);
  }}
  ```
  
 
#### 前端调用后端
类似的，可以在后端定义自己的方法并将方法暴露给前端调用。
- **在后端定义暴露给前端的方法**
  打开src/node/backend.ts文件，自定义一个 doSomething(name: string) 方法。
  ```
  @expose('your_backend_function_identifier')public doSomething(name: string): boolean {
      codearts.window.showInformationMessage(`hello ${name}!`);
      return true;
  }
  ```
  

- **在前端调用后端暴露的方法**
  打开src/browser/frontend.ts文件，在 run() 方法中通过 this.plugin.call() 调用在后端定义的 doSomething 方法。
  ```
  run(): void {
      this.plugin.call('your_backend_function_identifier', 'world');
  }
  ```
  
 
#### 事件订阅：发布和监听事件
- **在扩展后端监听事件**
  打开src/node目录下的backend.ts文件，在Backend类的run() 方法中添加如下代码注册监听一个文件删除的事件。
  ```
  const registeredEvent = codearts.workspace.onDidDeleteFiles((event) => {
       codearts.window.showInformationMessage(`${event.files.join(',')} deleted.`);
  });
  this.plugin.context.subscriptions.push(registeredEvent);
  ```
  如果想要删除这个事件的监听可以直接调用registeredEvent的dispose() 方法即可。
  大家可以尝试注册一些其他的事件并测试效果。
  

- **在扩展前端监听事件**
  打开src/browser下的frontend.ts文件，通过在Frontend类的run() 方法中添加如下代码注册监听一个改变当前活动的编辑器的事件。
  ```
  const eventHandler = (eventType: any, evt: any) => {
      // do something
  };
  this.plugin.subscribeEvent(EventType.WINDOW_ONDIDCHANGEACTIVETEXTEDITOR, eventHandler);
  ```
  前端取消事件注册的方式和后端并不相同，需要使用 plugin 对象的 unsubscribeEvent 方法取消注册的事件处理句柄。
  ```
  this.plugin.unsubscribeEvent(EventType.WINDOW_ONDIDCHANGEACTIVETEXTEDITOR, eventHandler);
  ```
  
 
#### 国际化
扩展创建完后，在根目录下默认生成了package.nls.json和package.nls.zh-cn.json文件，package.nls.json文件用来记录默认情况下的翻译词条，比如没有找到对应语言的翻译文件扩展框架将默认采用该文件中的词条。package.nls.zh-cn.json则是中文简体的翻译词条文件，如果扩展需要支持其他语言也可以自行添加翻译文件。
localize方法需要提供了一个key参数来指定使用国际化文件中的词条索引键值，后续的不定参数用来对翻译词条中的占位符进行替换，词条中支持使用"{0} {1} {2}"这样的格式进行占位，localize 方法的第二个参数开始会被依次替换到占位符中。
```
localize(key: string, ...args: any[]): string;
```
示例如下：
```
{
    "plugin.welcome": "Welcome!",
    "plugin.hello": "Hello {0}!"
}
```
- **内置成员plugin的localize方法**
  还在前后端内置的 plugin 成员变量中实现了localize方法。Frontend类 (src/browser/frontend.ts) 和Backend类 (src/browser/backend.ts) 分别继承了AbstractFrontend前端类和AbstractBackend后端类，可以直接使用 this.plugin.localize 方法进行本地化翻译。
  ```
  // 不带参数
  this.plugin.localize('plugin.welcome');
  // 带参数
  this.plugin.localize('plugin.hello', 'world');
  ```
  

- **直接引入localize方法**
  ```
  import { localize } from '@cloudide/nls';
  ```
  使用如下代码就可以将词条填充为： Hello World!
  ```
  localize('plugin.hello', 'World');
  ```
  
- **页面文件中的国际化方法**
  通用扩展可以使用ejs和pug引擎来渲染界面，无论是ejs还是pug引擎扩展框架都为开发者提供了一个 l10n 内置对象，里面存储了当前所选语言的翻译词条列表。
  对于选择ejs引擎来做界面渲染的开发者可以在ejs文件中使用如下方式来对需要本地化的文案进行翻译：
  ```
  <%= l10n['plugin.hello'] %>
  ```
  对于使用 pug 引擎的开发者可以使用如下方式：
  ```
  #{l10n['plugin.hello']}
  ```
  
 
