Node-RED1.3の新機能: サブフローのノード化

株式会社日立製作所 
サービスコンピューティング研究部
西山博泰



## はじめに

 プログラミングの手間を劇的に削減しアプリケーション開発者の裾野を広げるNo CodeやLow Codeといったキーワードが注目を集めています。[Node-RED](https://nodered.org/)はコーディングレスでプログラムを作成できるLow Codeプログラミングツールであり、IoT分野を筆頭に様々な分野で活用されています。

 Node-REDでは、ノードと呼ばれる機能ブロックををつなぎ合わせることで、とても簡単にプログラムを作成することができます。こういったノードには、乱数生成など簡単なユーティリティノードから、SNSからの情報収集、今流行りの機械学習を行うためのノードなど様々なノードが存在し、それらはOSSノードとして[多数公開](https://flows.nodered.org/)されています。

 2021年4月、Node-REDの最新版であるバージョン1.3がリリースされました。Node-RED 1.3では、アプリケーションの開発を容易化するさまざまな機能が新しく導入されました。一連の記事でNode-RED 1.3の新機能を紹介したいと思います。

 この記事では、新機能のうち筆者らが開発に参画したサブフローのノード化について紹介します。

## Node-REDフロー

 Node-REDではノードと呼ばれる部品を組み合わせてプログラムを構成します。例として、文字列から指定した正規表現にマッチした行のみを切り出すフローを作成してみましょう。

 以下に示すフローは、splitノードで入力文字列を1行毎のメッセージに分解(「行分割」)し、switchノードで正規表現にマッチするメッセージのみを選択(「正規表現マッチ」)、選択したノードをjoinノードで結合(「再結合」)します。これにより、正規表現にマッチした行のみが切り出されます。

![grep-flow-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/5c2f0d4d-26c9-1b9b-1365-8a65dda1906a.png)

 switchノードの設定は次の通りです。「^[JU]」という正規表現にマッチする入力メッセージをフィルタリングして出力します。設定では、「メッセージ列の補正」というチェックボックスをチェックしています。splitは文字列を改行文字(\n)で分割してメッセージグループを構成します。「メッセージ列の補正」をチェックすると、出力ポート毎にメッセージグループを構成し、joinノードで再結合可能となります。この例では、正規表現にマッチする行のみからなるメッセージグループをフィルタリングし、joinノードで結合します。

![switch-node.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/54ca21be-c82c-a882-9c32-12cff17d751c.png)

 例えば、入力として次の文字列を与えると、

```json
Canada
France
Geermany
Italy
Japan
Russia
United Kingdom
United States
```

先頭が「J」または「U」から始まる行が切り出されて出力されます。

```json
Japan
United Kingdom
United States
```

## サブフロー

 先程の例では、3つのノード(split、switch、join)を使って行を切り出す処理を定義しました。この一連の処理を再利用したい場合、一つのノードの形でまとまっていると便利です。サブフローはこのような再利用を行いたいフローの一部を一つのノードのように扱うための仕組みです。

 先程の例で、split、switch、joinの3つのノードを選択し、メニューから「サブフロー→選択部分をサブフロー化」を選択すると、選択したノードをまとめたサブフローが作成され、パレットに追加されます。

![SUBFLOW-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/32eae72e-8fc8-2da7-30ca-49d07d8ef5d0.png)

 サブフローの定義を「サブフローテンプレート」、そのフロー中での使用を「サブフローインスタンス」と呼びます。パレットに追加されたサブフローをダブルクリックするか、サブフローインスタンスの設定から「サブフローのテンプレートを編集」ボタンをクリックすると、サブフローテンプレートの編集タブがオープンします。

![SUBFLOW-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/bbb9b95a-0eb4-f335-c0bb-6554f3786442.png)

 サブフローテンプレートの編集タブを以下に示します。

![SUBFLOW-Template.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/7e68f2a3-6c19-7ccc-f7d2-a55eee1d980f.png)

 サブフローテンプレートの編集タブでは、サブフローを構成するノードを編集するのに加えて、「プロパティを編集」ボタンをクリックすることでサブフローに関する付加情報を設定できます。設定可能な付加情報としては、

- 環境変数およびその設定UI
- カテゴリ、色、アイコン
- 説明文
  
などを設定できます。先程の例で、検索に用いる正規表現を設定できるようにしてみましょう。サブフローの設定は、環境変数を用いて行います。

サブフローのカスタマイズには環境変数を利用します。環境変数はノードの設定に用いる変数で、ノードの実行開始前にその値が設定されます。サブフローに対する環境変数の設定では、入力フィールド識別用のアイコンやラベル、入力形式などを定義できます。

![env-var-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/f0c3745f-425a-c8cd-6b31-966c60e97f8c.png)

 この例では、PATという名称の環境変数とその入力UIを定義しています。生成される設定UIは、「UIプレビュー」タブで確認できます。抽出対象の行にマッチする正規表現は検索フィールドに指定します。

![Preview-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/ed11111b-4cf9-76f1-042e-1c53d725e23d.png)

 switchノードのマッチングパターンには、環境変数「PAT」を指定します。これにより、サブフローテンプレートのUI定義で定義した正規表現を用いて行の抽出を行うことができます。

![switch-node-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/2f547d43-d6b9-cdfd-d759-f1e32c877976.png)

ノードの外観も変更しましょう。

![Appearance-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/ea23befb-fc4e-acae-aaaa-6a1ab1cad4f8.png)

 サブフローの見た目を変更することによって、より通常のノードらしく見えるようになります。

![grep-flow-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/53d7dcae-aa4b-dd9e-0d90-89b52345238a.png)

## サブフローのノード化

 さて、先程の例では、入力文字列から正規表現にマッチする行を抜き出すサブフローを定義しました。Node-RED 1.2まで、サブフローはフローの一部としてエクスポートすることは可能でしたが、一般のノードのように独立して配布・再利用することはできませんでした。
 この問題に対処するため、サブフローをJSONファイルとしてダウンロードし、それをノードとして読み込む機能がNode-RED 1.3で[新機能]([Packaging a Subflow as a module : Node-RED](https://nodered.org/docs/creating-nodes/subflow-modules))として導入されました。
 先程作成したサブフローを例に、サブフローのノード化を行ってみましょう。

### 1. サブフローの作成
  先程のサブフローを定義します。


### 2. モジュール情報の定義
  サブフローテンプレート編集パネルの「モジュールプロパティ」設定タブにモジュール情報を定義します。この情報はNode-REDのノード開発の際のパッケージ化における`package.json`の記述内容に沿ったものですので、そちら([Packaging : Node-RED](https://nodered.org/docs/creating-nodes/packaging))も参考にしてください。  
![module-prop-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/74c20bdf-fa42-8ec3-fc3e-37478a9f3cc9.png)
  それぞれの定義項目は次の通りです。
  - モジュール: モジュールの名称を定義します。通常、`node-red-contrib-`で始まる名称とします。
  - ノードの型: ノードの内部名を定義します。
  - バージョン: *x.y.z*形式(*x*=メジャーバージョン、*y*=マイナーバージョン、*z*=パッチバージョン)のバージョンを指定します。
  - 説明: 簡単な説明を記入します。
  - ライセンス: 公開する際のライセンスを指定します。
  - 作者: 作者の情報を記入します
  - キーワード: ノードに関するキーワードをカンマ(,)区切りで記入します。`node-red`をキーワードに含める必要があります。
    
### 3. 作業ディレクトリの作成
  作業のためのディレクトリを作成します。以下の作業はこのディレクトリで行います。
  
### 4. `package.json`の作成
  Node-REDに読み込み可能なNPMモジュールを定義します。入力するデータの多くは、(2)のモジュールプロパティ編集タブで指定したものと同じですので、将来的にはツールにより自動生成されることが期待されます。  

  ```
  {
    "name": "node-red-contrib-grep",
    "version": "0.1.0",
    "description": "Node-RED node for extracting matching lines",
    "node-red": {
        "nodes": {
            "grep": "subflow.js"
        }
    },
    "keywords": [
      "node-red",
      "grep",
      "regular-expression"
    ],
    "author": "hiroyasu.nishiyama.uq@hitachi.com",
    "license": "Apache-2.0"
  }
  ```

### 5. ノードの説明
  説明をMarkdown形式で記載した`README.md`ファイルを作成します。
  
### 6. ライセンス条件
  再配布を行う場合のライセンス条件を定義した`LICENSE`ファイルを作成します。一般的に利用されるライセンスを選択した場合は、その条項を記載します。
  
### 7. Javascriptテンプレートコード
  
  サブフローの読み込みを行う、JavaScriptファイル(`subflow.js`)を作成します。このファイルは全てのサブフローモジュールで共通して利用できます。なお、このコードではサブフローを含むサブフローには対応していません。
  
  ```javascript
   const fs = require("fs");
   const path = require("path");
  
   module.exports = function(RED) {
       const subflowFile = path.join(__dirname,"subflow.json");
       const subflowContents = fs.readFileSync(subflowFile);
       const subflowJSON = JSON.parse(subflowContents);
       const sf = subflowJSON.shift();
       sf.flow = subflowJSON;
       RED.nodes.registerSubflow(sf);
   }
  ```
  
### 8. サブフローのJSONデータ作成
  
  サブフローテンレートの編集タブを開き、「書き出し」メニューから現在のタブを選択、ダウンロードボタンによりJSON形式のサブフローデータをダウンロード。作業ディレクトリに`subflow.json`という名前でコピーします。
  
![Export-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/c525c73a-4098-53c4-8d5c-ecbd43209b1c.png)
  
### 9. npm pack
  
  `npm pack`を実行し再配布ファイルを作成します。
  
  ```javascript
  % npm pack
  npm notice
  npm notice 📦  node-red-contrib-grep@0.1.0
  npm notice === Tarball Contents ===
  npm notice 0B    LICENSE
  npm notice 0B    README.md
  npm notice 376B  package.json
  npm notice 401B  subflow.js
  npm notice 1.4kB subflow.json
  npm notice === Tarball Details ===
  npm notice name:          node-red-contrib-grep
  npm notice version:       0.1.0
  npm notice filename:      node-red-contrib-grep-0.1.0.tgz
  npm notice package size:  1.3 kB
  npm notice unpacked size: 2.2 kB
  npm notice shasum:        71f9d9380a23f308b5d677447598413266720efa
  npm notice integrity:     sha512-P/B6Qdi0u3/dV[...]Zr9NyOnkR157A==
  npm notice total files:   5
  npm notice
  node-red-contrib-grep-0.1.0.tgz
  %
  ```
  
  `node-red-contrib-grep-0.1.0.tgz`という再配布ファイルが作成されます。
  
### 10. Node-REDへのアップロード
  
  エディタの「パレットの管理」メニュー、「ノードを追加」タブ、からアップロードボタンをクリックします。
  
![upload-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/d2af2e4b-5906-cdc1-315a-f97b933e8e58.png)
  
  先程作成した`node-red-contrib-grep-0.1.0.tgz`を選択しアップロードします。パレットに作成したノードが追加されます。
  
![palette-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546217/a2713814-ca0d-b4d9-78d1-7741dbdfe524.png)

  
  追加したノードはサブフローテンプレートの編集はできず、通常のノードとほぼ同様に扱うことが可能です。
  

## おわりに

 Node-RED 1.3で実装されたサブフローのノード化機能によって、HTMLやJavaScriptでのコーディングを行うことなくNode-REDのエディタでのフロー作成だけで再配布可能なノードを作ることが可能となりました。

 この機能によりノードの作成の手間を大きく削減できるため、別の記事で紹介したFunctionノードの外部ライブラリ利用機能と合わせて、再利用可能なノード開発が活性化することを期待しています。

## Node-RED1.3の新機能紹介

- [Node-RED 1.3の新機能: Plugin framework](https://qiita.com/k-toumura/items/9131b4a7cbce66cc088b#_reference-a682149ec8f0b1b06d03)
- Node-RED1.3の新機能: Functionノード
- [Node-RED1.3の新機能: サブフローのノード化](https://qiita.com/HiroyasuNishiyama/items/7e80dc9faf5518233568)


*Node-RED: Node-REDは，OpenJS Foundationの米国およびその他の国における登録商標または商標である。*
*JavaScript: JavaScriptは、Oracleの米国およびその他の国における登録商標または商標である。*