急に diffusers 系じゃない記事になりますが、
普段は Web アプリがメインなのでこういう記事も書きます。
1ヶ月ほど前にハッカソンでハマって大変な思いをしたので、
まとめておこうと思います。
- obniz とは
- 本記事中で使用するモジュールのバージョン等
- Next.js プロジェクトで node_modules の Obniz を使おうとするとエラーになる
- 結局どう対応したか?
- TypeScript だと型エラーになってしまう
- 以降、試行錯誤のメモ
obniz とは
Web から操作できる IoT デバイス です。
IoT には全然詳しくないですが、Web アプリはわかるので obniz は操作しやすいです。
LED を付けられたときには感動しました。
なお、オームの法則はわかりますが、どれくらいの抵抗を付ければ良いとかがわからないレベルです。
本記事中で使用するモジュールのバージョン等
・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#
どうやら、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> ); }
TypeScript だと型エラーになってしまう
しかし、グローバルに定義されている Obniz クラスを使用しているため、TypeScript では型エラーになってしまっています。これを解決するために以下のファイルを追加します。
【src/global.d.ts】 declare const Obniz: 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;
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 を試してくださっていました
・次にjson, yaml, cssを読み込めるように Webpack のモジュールが必要である旨が記載されている
・最後にwebpack.config.jsの変更が必要な旨が記載されている
上記記事の webpack.config.js を見ると client.min.js が生成されていて、 index.html を見ると client.min.js を読み込んでいます。つまり node_modules の obniz をインストールしているものの、自分でリンクして自分で Webpack ビルドし直していることになるようです。
create-react-app や create-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;
まとめ
脱 create-react-app や 脱 create-next-app をして、React の開発環境を Webpack や Vite で自分で作っていたら参考記事が有用だったかもしれません。
Webpack, Vite についての知識はあまり無いため、webpack 設定では解決には至りませんでした。
特に今回は Next.js で使いたかったため、create-next-app を使わずに Next.js 環境を整えるのも大変そうです。
CDN 経由を並行して使うことで動かすことはできたのでヨシとしましょう。