ジャコ Lab

プログラミング関連のメモ帳的ブログです

Next.js で obniz を使う際にハマったこと

急に diffusers 系じゃない記事になりますが、
普段は Web アプリがメインなのでこういう記事も書きます。

1ヶ月ほど前にハッカソンでハマって大変な思いをしたので、
まとめておこうと思います。

obniz とは

obniz

Web から操作できる IoT デバイス です。
IoT には全然詳しくないですが、Web アプリはわかるので obniz は操作しやすいです。

LED を付けられたときには感動しました。
なお、オームの法則はわかりますが、どれくらいの抵抗を付ければ良いとかがわからないレベルです。

LEDを含め、ハード側の仕様の見方とかがわからない!

本記事中で使用するモジュールのバージョン等

・Node.js: 18.18.0
・npm: 10.2.0
・create-next-app: 14.1.0
・obniz: 3.29.0

Next.js プロジェクトで node_modules の Obniz を使おうとするとエラーになる

Next.js のプロジェクトについて

使用する Next.js のプロジェクトは以下により生成したものです。

$ npx create-next-app

そして obniz を npm install しました。

$ npm install obniz

実行コード

【src/app/page.tsx】

"use client"
import Obniz from "obniz";
import { useEffect } from "react";

export default function Home() {
  useEffect(() => {
    console.log("useEffect:");

    const obniz = new Obniz("");
    obniz.onconnect = () => {
      console.log("onconnect");
      obniz.display.print("Hello!");
    };

    return () => {
      console.log("useEffect: cleanup");
    };
  }, []);

  return (
    <main>
      <h1>Hello World!</h1>
    </main>
  );
}

エラー内容

⨯ ./node_modules/obniz/dist/src/obniz/libs/webpackReplace/dialogPollyfill-browser.js:11:42

Module not found: Can't resolve 'dialog-polyfill'
モジュールが足りないらしい

とりあえず dialog-polyfill を入れてみる

yarn add dialog-polyfill
⨯ ./node_modules/obniz/dist/src/json_schema/index.yml
Module parse failed: Unexpected character '#' (1:1)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> ## YAML Template.
| ---
| $schema: http://json-schema.org/draft-04/schema#
YAML 系のエラーで全然ダメ

どうやら、npm module の Obniz は Node.js とは相性が良いが、Browser との相性が悪いらしいです。

結局どう対応したか?

obniz の GitHub や公式ガイドを見ていると、ブラウザから利用される場合は CDN 経由のサンプルしかないです。ということは CDN 経由であればブラウザと相性が良いはずです。

Next.js で CDN 経由で JavaScript ファイルを読み込むときは <Script>コンポーネント を使うのが良さそうです。

【src/app/page.tsx】

"use client";
import Script from "next/script";

export default function Home() {
  const handleOnLoad = () => {
    console.log("handleOnLoad");
    const obniz = new Obniz("");
    obniz.onconnect = () => {
      console.log("onconnect");
      obniz.display.print("Hello!");
    };
  };

  return (
    <main>
      <Script src="https://unpkg.com/obniz/obniz.js" onLoad={handleOnLoad} />
      <h1>Hello World!</h1>
    </main>
  );
}
これで obniz の画面に Hello! と出ました

TypeScript だと型エラーになってしまう

しかし、グローバルに定義されている Obniz クラスを使用しているため、TypeScript では型エラーになってしまっています。これを解決するために以下のファイルを追加します。

【src/global.d.ts】

declare const Obniz: any;
しかし、これでは any 型なので使いづらい!

インスタンス生成後は、node_modules の Obniz で型を付ける

CDN の Obniz クラスを扱う箇所を限定して、基本的には node_modules の Obniz で型付けしたものを扱おうと思います。

まずは、インスタンス生成を行うコンポーネントを作成しようと思います。わかりやすいように ObnizProvider という名前を付けておきます。

import Script from "next/script";

type ObnizProviderProps = {
  obnizId: string;
  onConnected: (obniz: any) => void;
};

const ObnizProvider: React.FC<ObnizProviderProps> = ({
  obnizId,
  onConnected,
}) => {
  const handleOnLoad = () => {
    const obniz = new Obniz(obnizId);
    obniz.on("connect", () => {
      onConnected(obniz);
    });
  };

  return (
    <>
      <Script src="https://unpkg.com/obniz/obniz.js" onLoad={handleOnLoad} />
    </>
  );
};

export default ObnizProvider;
ここでは、CDN の Obniz クラスを使いたいので、グローバル定義のObniz クラス (any型)を扱います

ObnizProvider の利用者は node_modules の Obniz で型付けする

"use client";
import ObnizProvider from "@/components/ObnizProvider";
import Obniz from "obniz";
import { useState } from "react";

export default function Home() {
  const [obniz, setObniz] = useState<Obniz | null>(null);

  const handleObnizConnected = (obniz: Obniz) => {
    setObniz(obniz);
    obniz.display?.print("Hello!");
  };

  return (
    <main>
      <ObnizProvider obnizId="" onConnected={handleObnizConnected} />
      <h1>Hello World!</h1>
      {obniz && obniz.connectionState}
    </main>
  );
}

これで node_modules の Obniz クラスを new することなく、型付けされたインスタンスを利用することができます。

以降、試行錯誤のメモ

参考情報

こちらの記事React x obniz を試してくださっていました

・まずdialog-polyfillが必要である旨が記載されている
・次にjson, yaml, cssを読み込めるように Webpack のモジュールが必要である旨が記載されている
・最後にwebpack.config.jsの変更が必要な旨が記載されている

上記記事の webpack.config.js を見ると client.min.js が生成されていて、 index.html を見ると client.min.js を読み込んでいます。つまり node_modules の obniz をインストールしているものの、自分でリンクして自分で Webpack ビルドし直していることになるようです。

create-react-appcreate-next-app でプロジェクトを作成した場合、自分で webpack.config.js や index.html を作らなくて良いので next.config.mjs などで Loader を差し込む必要がありそうですが、next.config.mjs に以下のように loader を追加しても解決できませんでした。

const nextConfig = {
  webpack(config, options) {
    config.module.rules.push({
      test: /\.(yml|yaml)$/,
      use: [
        {
          loader: "yaml-loader",
        },
      ],
    });

    return config;
  },
};

export default nextConfig;
json-loaderraw-loaderを入れたらビルドすら通らなく...

まとめ

脱 create-react-app や 脱 create-next-app をして、React の開発環境を Webpack や Vite で自分で作っていたら参考記事が有用だったかもしれません。

Webpack, Vite についての知識はあまり無いため、webpack 設定では解決には至りませんでした。

特に今回は Next.js で使いたかったため、create-next-app を使わずに Next.js 環境を整えるのも大変そうです。

CDN 経由を並行して使うことで動かすことはできたのでヨシとしましょう。