Node-RED 1.3の新機能: Plugin framework

Node-REDのversion 1.3が2021年4月8日に公開されました(参照: [Version 1.3 released](https://nodered.org/blog/2021/04/08/version-1-3-released), [バージョン1.3がリリースされました](https://nodered.jp/blog/2021/04/08/version-1-3-released))。
この記事では、Node-RED 1.3の新機能の一つである「Plugin framework」について紹介します。

## Plugin frameworkとは?

これまで、Node-REDに機能を追加する方法には下記の方法がありました。

1. Node-RED本体のコードを直接書き換える
2. Node-REDのノードとして新機能を実装し、npmパッケージとしてインストールする

1つめの方法は一番自由度が高く任意の機能を追加可能となりますが、追加した機能を切り出して配布するのは困難です。またNode-RED本体がバージョンアップした際には、更新したコードを改めて新しいバージョンへと適合させる必要があります。

2つめの方法は機能を実装したコードがnpmパッケージとして分離されているため、機能の配布も容易になりますし、新しいバージョンへの追従も、APIの変更にのみ注意すればよいこととなり追従が容易になります。しかし、単にエディタ側に機能を追加したいときなどは、ノードのランタイム側の機能部として空のノード定義をしなければならないなど、不自然な記述が増えてしまいます。

Plugin frameworkでは、これを改善するために下記の特長を持っています。
- エディタ側・ランタイム側のうち、拡張が必要な側のみのコードを記述すればよい。
- プラグインから他のプラグインの機能を利用することができ、プラグイン自体を拡張可能になる。
- プラグイン内の特定のフォルダにいれたファイルを、エディタ側からHTTP APIを介して読み込むことができるため、任意の画像ファイルやJavascriptライブラリをエディタ側に追加するのが容易になる。

## Plugin frameworkの機能の詳細

Pluginは、npmパッケージの形で実装します。フォルダの構造は下記のようになります。

```
|- package.json
|- plugin.js
|- plugin.html
\- resources
    |- resourcefile.js
```

### package.json
`package.json`には、通常のnpmパッケージとして記述する項目以外に、このモジュールがNode-REDのプラグインであると認識させるための下記の情報を記述します。

```json
"node-red": {
    "plugins": {
        "sampleplugin": "plugin.js"
    }
}
```

`plugins`の値となるオブジェクトは、キーとしてプラグインの名前(この例では`sampleplugin`)、対応する値としてプラグインを実装しているファイル(この例では`plugin.js`)を持ちます。もし、エディタ側のみに拡張が必要な時には.htmlファイルを指定します。

### ランタイム側の拡張
ランタイム側のコードでは、プラグインを登録するために`RED.plugins.registerPlugin()`を実行します。

```javascript
module.exports = function(RED) {
    // ...
    RED.plugins.registerPlugin("sample-runtime-plugin-identifier", {
        type: "sample-runtime-plugin-type",
        onadd: function() {
          // プラグインが登録されるときに実行される
        },
        onremove: function() {
          // プラグインが登録解除される時に実行される
        },
        settings: {
          // $HOME/.node-red/settings.js から読み込む設定項目と、エディタ側に公開するかのフラグ
          sampleSetting: { value: "sample", exportable: true }  
        }
    });
}
```

上記のコードでは、プラグインの識別子およびタイプの登録と、プラグインが登録/登録解除されたときに実行される関数、および`settings.js`から読み込む設定項目を指示しています。

これにより、ランタイムにプラグインが組み込まれ、`RED.plugins.getPluginsList()`などのランタイムAPIから
プラグインが参照できるようになります。通常、`onadd`で定義した関数内で各種イベントに対するハンドラを登録することがプラグイン実装の中心になります。

### エディタ側の拡張
エディタ側にもほぼ同様の枠組みが用意されています。ただし、settings.js内の設定項目の参照は
ランタイム側で設定するためエディタ側のコードでは設定がありません。

```html
<script type="text/javascript">
  //...
  RED.plugins.registerPlugin("sample-editor-plugin-identifier", {
      type: "sample-editor-plugin-type",
      onadd: function() { /* ... */ },
      onremove: function() { /* ... */ }
  });
</script>
```

ランタイムと同様、上記APIの呼び出し以降は`RED.plugins.getPluginsList()`などでプラグインを参照することが
できます。

### Resources ディレクトリ
エディタ側の機能拡張では、追加の画像ファイルやHTML/CSSファイルを用意することが多くなります。
ノードのプラグインでは、これらのファイルを読み込むためには独自でAPIエンドポイントを用意し、
そのエンドポイントを使ってファイルをエディタ側に読み込むことが必要でした。

Plugin frameworkでは、パッケージの`resources`フォルダに格納したファイルが
`/resources/<プラグインパッケージの名前>/<resourceフォルダ内のファイル名>`として見えるようになります。
これにより、エディタ側のHTMLファイルで
`<img src="/resources/plugin-package-name/foo.svg">`と記述することで`resources/foo.svg`を読み込めるようになります。なお、エディタ/ランタイムで`RED.plugins.registerPlugin()`を実行しなくても、package.jsonに`node-red.plugins`を記述すれば公開されます。


なお、Plugin frameworkの仕様の詳細は、下記デザインノートに書かれています(注: 2021年4月27日現在、Pull Requestがマージされていないため見つけづらいです。)

- [Node-RED Plugins](https://github.com/node-red/designs/blob/6da69a2f93db768ccb837a724fe44bc140b87139/designs/plugins.md)

## Plugin frameworkをつかった機能追加の実例
ここでは、「Node-REDのフローエディタのメニューに新たな項目"Greetings"を作り、それを選ぶとアラートに"Hello, plugin"と表示されるようにする」というシンプルなプラグインを作成します。

フォルダの構成は下記の通りになります。

```
|- package.json
|- greetings.html
\- resources
   |- hello.png
```

まず、package.jsonを記述します。

```json
{
  "name": "node-red-contrib-plugin-greetings",
  "version": "1.0.0",
  "description": "add greeting menu item",
  "author": "Your Name",
  "license": "Apache-2.0",
  "node-red": {
    "plugins": {
      "greeting": "greeting.html"
    }
  }
}
```

つぎに、プラグインの本体となる`greeting.html`を作成します。

```html
<script type="text/javascript">
   RED.plugins.registerPlugin("greeting-menu", {
       type: "greeting-menu",
       onadd: function() {
         RED.actions.add("greeting:selected", function() {
            RED.notify('<p><img src="/resources/node-red-contrib-plugin-greeting/hello.png">Hello, plugin!</p>', "success");
         });
         RED.menu.addItem("red-ui-header-button-sidemenu", {
             id: "menu-item-greeting",
             label: "Greeting",
             onselect: "greeting:selected"
         });
       }
   });
</script>
```


ここでは、`RED.plugins.registerPlugin()`でプラグインを登録し、登録完了時のコールバック関数`onadd`内で
メニューを選んだ時に表示される内容の設定(`RED.actions.add()`)と、
メニューへの項目の追加(`RED.menu.addItem()`)を実行しています。

最後に、通知に表示される画像ファイルを`resources/hello.png`に配置します。

![通知に表示される画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/072f9885-10cd-e61b-34bc-5acea9ca5734.png)

これでnpmパッケージが出来上がりましたので、Node-REDのユーザディレクトリ(通常はホームディレクトリの.node-red)にインストールします。(`<...>/node-red-contrig-plugin-greeting`には上記パッケージ一式を格納したフォルダのパスが入ります)

```sh
% cd ~/.node-red
% npm install <...>/node-red-contrib-plugin-greeting

```


それでは実行してみましょう。

![メニュー画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/d2f4aa48-0dca-83b9-c2d6-ec4c6db217e9.png)

メニュー最下部に"Greeting"という項目ができているのがわかります。

ここで"Greeting"を選ぶと、下記のように"Hello, plugin!"とかかれた通知画面が表示されます。

![通知画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/a0d89ed1-b93f-df1b-621c-53e7bbc5612c.png)

ごく簡単な機能ですが、Node-RED本体のコードに手を入れることなくエディタの機能が拡張できることが理解できたのではないでしょうか。

## おわりに

これまではNode-REDの拡張には、Node-REDのコアを注意深く改造していく必要がありましたが、このPlugin frameworkや[Runtime Editor Split](https://github.com/node-red/node-red/wiki/Design%3A-Runtime-Editor-Split)、[Pluggable Message Routing](https://github.com/node-red/designs/tree/master/designs/pluggable-message-routing)などの形で拡張機能が比較的容易に実装できるようになってきました。Pluginの形で革新的な機能が導入され、成熟した時点でNode-REDに取り込まれる、という形でNode-REDの機能がコミュニティの手で磨かれていく流れができることが楽しみです。

## Node-RED 1.3新機能紹介
- Node-RED1.3の新機能: Plugin framework (本記事)
- Node-RED1.3の新機能: Functionノード
- [Node-RED1.3の新機能: サブフローのノード化](https://qiita.com/HiroyasuNishiyama/items/7e80dc9faf5518233568)