Node-REDのノードをTypeScriptで開発する

Blog

はじめてのノード開発で紹介されている、受け取ったメッセージの文字列をすべて小文字に変換するノードをサンプルに、TypeScriptで開発する方法について説明します。

npmにNode-RED の型定義が公開されているので、これを利用します。

動作を確認した環境

  • Node.js v12.14.0
  • TypeScript v3.8.3
  • Node-RED v0.20.5

プロジェクトの作成

  1. Node-REDノードのプロジェクトを作成します。

    mkdir node-red-contrib-lowercase
    cd node-red-contrib-lowercase
    npm init -y
    
  2. Node-RED、TypeScriptとNode-REDの型定義をインストールします。

    npm install --save-dev node-red
    npm install --save-dev typescript @types/node-red
    

TypeScript設定ファイルの作成

コンパイル対象のファイルはsrcの下に配置し、コンパイル後のファイルはdistに生成されます。<br /> 想定するディレクトリ構成は次のようになります。

.
├── dist
└── nodes
   ├── lower-case.html
   └── lower-case.js
├── node_modules
├── package-lock.json
├── package.json
├── src
└── nodes
   ├── lower-case.html
   └── lower-case.ts
└── tsconfig.json
  1. 次のコマンドを実行し、設定ファイルを作成します。tsconfig.jsonがプロジェクトのルートディレクトリに生成されます。

    npx tsc --init
    
  2. tsconfig.jsonを次のように編集します。

    {
      "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "outDir": "./dist",
        "rootDir": "src",
        "lib": ["es5", "dom"],
        "moduleResolution": "node",
        "typeRoots": [
          "node_modules/@types",
          "../../node_modules/@types"
        ],
        "esModuleInterop": true
      },
      "include": ["src/**/*"]
    }
    

ノードの作成

HTMLファイル

src/nodes/lower-case.htmlに、Node-REDエディタに表示されるノードの表示部分を作成します。

ここははじめてのノード開発 - lowercase.htmlと同じです。

<script type="text/javascript">
  RED.nodes.registerType('lower-case',{
    category: 'function',
    color: '#a6bbcf',
    defaults: {
      name: {value: ""}
    },
    inputs: 1,
    outputs: 1,
    icon: "file.png",
    label: function() {
      return this.name || "lower-case";
    }
  });
</script>

<script type="text/html" data-template-name="lower-case">
  <div class="form-row">
    <label for="node-input-name"><i class="icon-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
  </div>
</script>

<script type="text/html" data-help-name="lower-case">
  <p>A simple node that converts the message payloads into all lower-case characters</p>
</script>
  • registerTypeでノードを登録します。第1引数で指定する文字列がノード名です。

TypeScriptファイル

src/nodes/lower-case.tsに、ノード実行時の処理部分を作ります。

import { Red, Node, NodeProperties } from 'node-red';

module.exports = function(RED: Red) {
  function LowerCaseNode(this: Node, props: NodeProperties) {
    RED.nodes.createNode(this, props);
      const node = this;
      node.on('input', (msg) => {
        msg.payload = msg.payload.toLowerCase();
        node.send(msg);
    });
  }
  RED.nodes.registerType("lower-case", LowerCaseNode);
}
  • Node-REDオブジェクトと、Node、Propeties(ノードの設定項目)に型をつけます(3-4行目)。
  • RED.nodes.registerTypeで、HTMLで登録したノード名に対し、実行する関数を登録します。

package.jsonの編集

  1. コンパイル時に、distの下にNode-REDのノードに必要なファイル群がコピーされるようにします。

      "scripts": {
        "build": "npm run copy:html && tsc ",
        "copy:html": "mkdir -p dist/nodes/ && cp -a src/nodes/*.html dist/nodes/"
      },
    
  2. Node-REDにこのプロジェクトがNode-REDのノードだと認識されるよう、設定を追記します。

      "node-red": {
        "nodes": {
          "lower-case": "dist/nodes/lower-case.js"
        }
      }
    

動作確認

  1. 次のコマンドを実行してtsファイルをjsファイルに変換します。distの下に必要なファイルが生成されます。

    npm run build
    
  2. Node-REDをローカルにインストールします。

    npm install -g node-red
    
  3. グローバルなnode_modulesにシンボリックリンクを張って、Node-REDにノードを登録します。

    npm link
    
  4. Node-REDを起動します。

    node-red
    
  5. http://127.0.0.1:1880/にアクセスし、Node-REDの起動を確認します。

  6. エディタ上に次のノードを配置して、それぞれノードをつなぎます。

項目ノード備考
入力inject「ペイロード」を「文字列」にして、変換したい文字列を入力します。
機能lower-case
出力debug

  1. 左の実行ボタンをクリックして、injectノードで指定した文字列が小文字になっていればOKです。

独自プロパティの型定義

lower-caseノードの場合、ノードのプロパティはデフォルトの「Name」のみでした。

しかし、実際にノードを開発する場合、ユーザーに設定させるオリジナルのプロパティを追加することもあります。 このときの型定義は、NodePropertiesを継承したインターフェースを作成して利用します。

例として、先頭の一文字のみ大文字にするか制御するプロパティを追加してみます。

<script type="text/javascript">
  RED.nodes.registerType('lower-case',{
    category: 'function',
    color: '#a6bbcf',
    defaults: {
      name: {value: ""},
      isCapitalize: {value: false}
    },
    inputs: 1,
    outputs: 1,
    icon: "file.png",
    label: function() {
      return this.name || "lower-case";
    }
  });
</script>

<script type="text/html" data-template-name="lower-case">
  <div class="form-row">
    <label for="node-input-name"><i class="icon-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
  </div>
  <div class="form-row">
    <label for="node-input-isCapitalize"><i class="icon-font"></i> Capitalize</label>
    <input type="checkbox" id="node-input-isCapitalize">
  </div>
</script>

<script type="text/html" data-help-name="lower-case">
  <p>A simple node that converts the message payloads into all lower-case characters</p>
</script>

この状態で、TSファイル側でprops.isCapitalizeを参照するとコンパイル時に次のエラーが出力されます。

src/nodes/lower-case.ts:15:19 - error TS2339: Property 'isCapitalize' does not exist on type 'NodeProperties'.

12       if (props.isCapitalize) {

独自プロパティの型を定義する

  1. src/lib/nodes/interfaces.tsを作成し、以下を記載します。

    import { NodeProperties } from 'node-red';
    
    export interface LowerCaseProps extends NodeProperties {
      isCapitalize: boolean;
    }
    
    • NodePropertiesインターフェースを継承するLowerCasePropsインターフェースを定義します。その中で、独自のプロパティを定義します。
  2. TSファイルでLowerCasePropsを利用するように変更します。

    import { Red, Node } from 'node-red';
    import { LowerCaseProps } from './lib/interfaces';
    
    const capitalize = (str: string) => {
      return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
    };
    
    module.exports = function(RED: Red) {
      function LowerCaseNode(this: Node, props: LowerCaseProps) {
        RED.nodes.createNode(this, props);
          const node = this;
          node.on('input', (msg) => {
            if (props.isCapitalize) {
              msg.payload = capitalize(msg.payload)
            } else {
              msg.payload = msg.payload.toLowerCase();
            }
            node.send(msg);
        });
      }
      RED.nodes.registerType("lower-case", LowerCaseNode);
    }
    

これで、さきほどと同様にコンパイルがとおり、無事にjsファイルに変換されます。

設定ノードの型定義

Node-REDでは、ノード内の設定値を別のノードとして定義しておき、利用するノード内でプロパティとして読み込む設定ノードというしくみがあります。 このときの型定義はNodeインターフェースを継承します。

  • 型定義

    import { Node, NodeProperties } from 'node-red';
    export interface ExampleProps extends NodeProperties {
      config: string;
    }
    export interface ConfigNode extends Node {
      credentials: {
        username: string;
        password: string;
      };
      url: string;
    }
    
    • RED.nodes.getNodeでノードのIDが渡されるように、プロパティ側のconfigの型をstringにする。
    • 設定ノードの型はNodeを継承する。
  • TSファイル

    import { Red, Node } from 'node-red';
    import { ExampleProps, ConfigNode } from './lib/interfaces';
    ......
    export const exampleNode = (RED: Red) => {
      RED.nodes.registerType('example', function(
        this: Node,
        props: ExampleProps
      ) {
        RED.nodes.createNode(this, props);
        const configNode = RED.nodes.getNode(props.config) as ConfigNode;
        const node = this;
    ......
    
    • RED.nodes.getNodeで取得したノードの型をConfigNodeにキャストする。