【2020年1月】ラズパイ4のNode-REDにAngular Elements差し込んでHugoからデプロイ


昨年作ったDJサービス、スッピン過ぎて辛い

昨年のアドベントカレンダーにて作った、音声認識APIを使った"DJに声でリクエストする"サービスですが・・・

・・・はい、デザインまったくしてないスッピンのHTMLでございます。
せっかくのDJリクエストフォームなのにもうちょとなんとかならないかと…
これにテーマ的なモノをビシッと嵌め込んでもうちょいイケてるサイトにならないもんかと考えておりました。

しかしAngularは基本的にSPAを作成するフレームワークなので、"テーマ的なモノ"=ボイラープレートといえばページやパーツといった"コンポーネント群"を大量に投入することになります。
う〜ん、いや、そう言う大掛かりなことじゃなくて、もっとこうサクッとハメ込めるようにならないもんかな…と。

また別の方面で"テーマをサクッと当てられる"、と言えば静的サイトジェネレーター(Static Site Generator = SSG)ですが良さげなSSGもいろいろ探ってみてました。
Jekyll、Gatsby、Hugo ・・・最近のイケてるサービスはHugoかなぁ、Hugoのサイトの一部にAngualr混ぜるかHugoもPWA対応してくれないかとか…いろいろ探っていると…

"静的なコンテンツに部分的にAngularを埋め込む方法"だとっ?!

Angular Elements、これじゃないか。

と言うわけで、"ラズパイ4に乗っているNode-REDにAngular Elementsのコンポーネントが差し込まれたHugoで生成されたHTMLをデプロイする手順"、行ってみたいと思います。いつも通りてんこ盛りです。

対象環境

OS: Raspbian Buster 4.19
Node-RED: 1.0.3
Angular: 8.2.14
Hugo: 0.62.1 linux/arm

Angular や Hugo も全てラズパイ4上にて環境を構築してます。
作業はVSCodeのRemote SSH経由にてラズパイ上で行っております。

1. Hugo のインストール

Hugoをインストールするにはいろいろ方法がありますが、今後のことも考えてsnapからインストールしてみましょう。
と言うわけで、snapから行きます。

1-1. snap のインストール

以下のsnap公式サイトの案内に従ってインストールを行います。

とは言えコマンドは簡単です。aptからsnapdをインストールするだけです。

$ sudo apt update
$ sudo apt install snapd

ラズパイ4にsnapをインストールするのは以上です。

1-2. Hugo のインストール

SnapからHugoのインストールを行います。

$ sudo snap install hugo
2019-12-30T12:58:33Z INFO Waiting for restart...
hugo 0.61.0 from Hugo Authors installed

1-3. Hugo の調整?

Hugoのインストールは上記で完了なのですが、hugoコマンドを叩くと妙なエラー?が出ます。

$ hugo new site quickstart
ERROR: ld.so: object '/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
ERROR: ld.so: object '/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
Congratulations! Your new Hugo site is created in /home/pi/quickstart.
...

うーん、エラーメッセージが出ています。しかしhugoではちゃんとプロジェクトが生成できている模様です。ちゃんと生成できているのかよくわからないので気になりますね・・・
ちょっと調べてみると1件だけビンゴなPOSTが。

/etc/ld.so.preload/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so# でコメントアウトしただけ、の模様です。(そのまんまかい。。。)

/etc/ld.so.preload
#/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so

これでとりあえずエラーは出なくなりましたが、これで良いのかな・・・?
と、とりあえずこれで進めたいと思います。

2. Hugo でプロジェクト作成

気を取り直してHugoのサンプルプロジェクトを作成してみましょう。

$ hugo new site quickstart
...
$ cd quickstart
$ git init
$ git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

最初のテーマとしてanankeを追加してみました。(チュートリアルまんまです。。。)

で、設定ファイルにanankeを追記しましょう。

config.toml
...
theme = "ananke"

さらにチュートリアルに従ってブログの記事を追加してみます。

$ hugo new posts/my-first-post.md

ここで一旦、hugoの開発サーバーを起動してみます。

$ hugo server -D

ブラウザでlocalhost:1313を開いてみましょう。

はい、hugoの準備は完了です!

3. Angular を Angular Element 対応にする

今回のメインエベントのAngular Elementですが、ざっくり言うと以下のような感じです。

Angular Elements は、 Web Components としてパッケージ化される Angular コンポーネントであり、Web Components は、フレームワークに依存しない形で新たな HTML 要素を定義するウェブ標準技術です

とにかく、Angularで定義したコンポーネントタグがそのまま使えるってことです。

詳しくは以下をご覧ください。

冒頭で紹介したAngular Elements で作ったアプリケーションを Hugo に埋め込む方法の記事を参考に、前回までの記事で作成したAngularプロジェクトを Angular Elementsに対応させたいと思います。

ng コマンドで @angular/elementsプラグインを追加します。

$ ng add @angular/elements
...

実は、以上で終了です!

今回はAppComponentをそのまま使いたいと思いますので、<app-root></app-root> をそのまま利用します。
Angularはデフォルトでapp-rootタグを起点とするので設定の変更などはしなくてOKです。設定ファイルはいじらずに、このままjsの生成だけ行ってしまいます。

$ ng build --prod --output-hashing=none
...

で、

Angular8 から es5 と es2015 で別ファイルになっています。
なので以下のように package.json のスクリプトで es5 と es2015 それぞれ単一の jsファイルにまとめています。

とのことなので、distに出力された js を単一の js にします。
このAngularプロジェクトはNode-REDのstaticサイトに直接デプロイしておりますので、ちょっとややこしいですがコマンドは以下のようになりました。

$ cat ~/node-red-static/DjRequestApp/{runtime-es5,polyfills-es5,scripts,main-es5}.js > ~/node-red-static/DjRequestApp/DjRequestApp-es5.js
$ cat ~/node-red-static/DjRequestApp/{runtime-es2015,polyfills-es2015,scripts,main-es2015}.js > ~/node-red-static/DjRequestApp/DjRequestApp-es2015.js

ここで単一のjsとなったDjRequestApp-es5.jsDjRequestApp-es2015.jsがhugoの方で使用するjsファイルとなります。

4. Hugoへの移植

4-1. js,css のコピー

さて、hugoに埋め込む手順に従って、hugoで作成した"quickstart"プロジェクトにAnuglarで生成したjsとstyle.cssをhugoにコピーします。
ディレクトリの構造は以下のようにします。

static
├── css
│   └── styles.css
└── js
    ├── DjRequestApp-es2015.js
    └── DjRequestApp-es5.js

4-2. HTMLでの読み込み、その1

hugoで設定したテーマのテンプレートを上書きして、上記の jsとcss を読み込んでみたいと思います。

まずquickstart/themes/ananke/layouts/partials/site-header.htmlquickstart/layouts/partials/site-scripts.html にコピーします。
これが各ページの

タグで読み込まれますので、ここにstyleタグとscriptタグを追加します。
以下のようになるかと思います。
site-scripts.html
{{ $script := .Site.Data.webpack_assets.app }}
{{ with $script.js }}
  <script src="{{ relURL (printf "%s%s" "dist/" .) }}"></script>
{{ end }}
<script type="module" src="{{ .Site.BaseURL }}/js/DjRequestApp-es2015.js"></script>
<script src="{{ .Site.BaseURL }}/js/DjRequestApp-es5.js" nomodule defer></script>

<link rel="stylesheet" href="{{ .Site.BaseURL }}/css/styles.css">

最初の3行の$script.js でループしているあたりがオリジナルのコードです。それ以下が今回追加したコードとなります。

4-3. HTMLでの読み込み、その2

続いていよいよ本体のテンプレートHTMLに<app-root>を追加します。ヘッダーの時と同様に、テーマのindex.htmlを上書きします。

まず、quickstart/themes/ananke/layouts/index.htmlquickstart/layouts/index.html にコピーします。

続いて、index.htmlに以下のように<app-root>タグを追加します。

index.html
...
...
        {{/* As above, Use $section_name to get the section title, and URL. Use "with" to only show it if it exists */}}
        {{ with .Site.GetPage "section" $section_name }}
          <a href="{{ .Permalink }}" class="link db f6 pa2 br3 bg-mid-gray white dim w4 tc">{{ i18n "allTitle" . }}</a>
        {{ end }}
        </section>
      {{ end }}

      </div>
  {{ end }}
  <div class="pa3 pa4-ns w-100 w-70-ns center">
    <app-root></app-root>
  </div>
{{ end }}

位置としては、ブログ記事の一覧を出力したブロックの後に続けて同様のスタイルでdivを追加し、その中にapp-rootタグを追加しています。
作業としては修正作業としては以上ですが、果たしてこれで本当にAngularのコンポーネント=音声認識APIが動くのでしょうか・・・?

4-4. Hugo の出力先を変更

hugoはデフォルトでpublicフォルダに生成されたHTMLなどが配置されます。hugoで生成してからpublicからコピーするのはめんどいので、node-redのstaticフォルダに直接、デプロイさせてみましょう。
hugoのtomlに以下のように追加します。

config.toml
...
publishDir = "../node-red-static/DjReqChannel"

DjReqChannelは適当です。AngularのAppとぶつからなければOKです。

4-5. ベースアドレスの変更とサイトの生成

ついでに、hugoサイトの起点となるアドレスも指定してしまいましょう。
baseURLを変更します。
設定ファイルの config.tomlは以下のようになりました。

config.toml
baseURL = "https://raspberrypi.local:1880/DjReqChannel"
languageCode = "en-us"
title = "My New Hugo Site"
theme = "ananke"

publishDir = "../node-red-static/DjReqChannel"

今回はlocalhost でなくてraspberrypi.localを使ってみました。

そしてhugoコマンドでHTMLを生成すれば出来上がりです!

$ hugo -D

5. 動作確認

いや、実はここからが長かったのですよ。。。
この記事とは本質的に関係ないのですが、いろいろありまして、お陰様で記事2本12も書けました。笑
まぁ、カクカクシカジカがございましてやっと表示できたのが以下のスクショです。

"ハッピーニューイヤー"とマイクに呟いたところです。音声認識APIもちゃんと動いていますよ!
(シンプルなテーマのお陰で元のスッピンHTMLとあまり変化がない。。。)
で、Node-REDのデバッグログにもちゃんと出てきております。

ふぅ。。。動いてよかった。。(音声認識APIがHTTPS必須でなければこんな苦労はしなかった。。。)

まとめ

Angular Elementsの埋め込みはとっても簡単ですが、今回はapp-rootそのまま使ったのでほとんど何もしなくてOKでした。
が、通常(?)は複数のコンポーネントをエクスポートしてサイトに埋め込むことになると思います。
複数コンポーネントの出力もできるのですが、app.module.tsでいろいろ追記する必要があるらしいので、現時点では正直、実用的ではないかなぁ・・・とも思います。
今回のように本当にワンポイントで、単体の動的なガジェットを埋め込む用途であれば(Angularのプロジェクトをそのように作れれば)、面白いサービスが作れるのではないかと思いました。