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

March 01, 2020

概要

はじめてのノード開発で紹介されている、受け取ったメッセージの文字列をすべて小文字に変換するノードをサンプルに、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 に生成されます。
想定するディレクトリ構成は次のようになります。

.
├── 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

    01

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

独自プロパティの型定義

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

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

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

02

<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 にキャストする。