ちびのはてな

「ちび(猫)」と「キノコ」から生まれた「ちびキノコ」。ドラゴンボール最強!純粋なサイヤ人のように生きたいと思っているモノ作りを楽しむ人です。IT技術で少しでも多くの人が笑顔になってくれたらいいなと。

obniz にプルリクを出した話

f:id:chibi929:20190605182221p:plain

先日、obniz にプルリクを出しました。
https://github.com/obniz/obniz/pull/172

そしてマージされ、リリースされました!
https://github.com/obniz/obniz/releases/tag/v2.1.0

本人のスペック

IoT は全然できません。
obniz だからこそLチカやモーターが動かせているようなものです。。。
アノードやカソードも obniz で初めて知りました。
(正確にいうとデジモンのゲームで単語だけは聞いたことありました。)

ずっと C++Java(Android) を触っていて、
ここ最近 Web アプリケーション開発に携わるようになり、
Frontend, Backend 等の知識がなんとなく身について来た感じです。

※ちなみにスマホアプリ開発時代は API や Cloud も全くイメージ沸かなかった。。。

何故、プルリクを出そうと思ったか

簡潔にいうと、今までの経歴が C/C++, Java(Android) と来て、
型の無い JavaScript を気持ち悪いと思いつつ、
Web アプリケーション開発で TypeScript を触って JavaScript と少し仲良しになった。

でも、TypeScript で書いていると any な型がどうしても辛い。
型を作りたくなる病になるわけです。

そんなこんなでやっぱり型が欲しい!

ハンズオンに参加する際は HTML に直接 JS を書くようなコードを書きますが、
自分でコードを整理する際は、やっぱりファイルを分けたりします。
そして TypeScript 化したり Webpack を導入したり・・・。

obniz は、当初 TypeScript を導入しても any 型のまま扱っていました。
ですが、ソースコードを共有したり、時間が経ってから再び触ったりすると型が欲しくなっちゃうんですよね。

どう対応していたかというと、自分のプロジェクト内に interface を作るだとか hoge.d.ts とか作って、必要最低限のみで any 型を無理矢理キャストして使う感じです。

import * as Obniz from 'obniz';

interface Obniz {
  wired(name: string, options: any): any
}

interface LED {
  on(): void;
  off(): void;
}

const obniz: Obniz = new Obniz('1234-5678');
obniz.onconnect = () => {
  const led: LED = obniz.wired('LED', {anode: 0, cathode: 1});
  led.on();
}

必要分定義して、こんな感じだったりです。
時には index.d.ts とか作ってみたり。

徐々に使用するパーツが増えてくる・・・

IoT には疎いので、ハンズオンで学んだことくらいしか出来ませんが、
百均で「電飾」や「ミニプラレール」を買ってきたりして遊んでいます。
でも HTML で操作するにも API 経由で操作するにもやっぱり TypeScript で書きたい!!

キノコなどの仲間内でも obniz を購入する人が増えたり、
TypeScript を使うようになった人が増え、型定義が無いことが辛みになってくる。

じゃあ、途中まで型定義あるから、
少し定義不足があってもみんなが小さいプルリクをたくさん出せるように
土台だけでも作るか!そんなノリで始めた型定義生活。

型定義と過ごした日々...

一つ目の難関 (new)

トランスパイル後の以下のような形になる定義の仕方...

var Obniz = require('obniz');
var obniz = new Obniz('1234-5678');

試行錯誤を繰り返す。。。

定義1 (NG)

export class Obniz {}

定義が、上記の場合

import * as Obniz from ('obniz');
const obniz = new Obniz.Obniz('1234-5678');

----- トランスパイル後 -----

var Obniz = require("obniz");
var obniz = new Obniz.Obniz('1234-5678');

となってしまうので違う。
もしくは、

import { Obniz } from 'obniz';
const obniz = new Obniz('1234-5678');

----- トランスパイル後 -----

var obniz_1 = require("obniz");
var obniz = new obniz_1.Obniz('1234-5678');

となってしまって、違う。。。

定義2 (NG)

export interface ObnizStatic {
  hello(): void;
}

declare const Obniz: ObnizStatic;

export default Obniz;

axios を真似てこんな感じの定義だと、

import Obniz from 'obniz';
const obniz = new Obniz('1234-5678');  // new できないよエラー

定義3 (OK)

そういえば、Node で Error を作る時に、
new Error() するし、変わった型定義だったなーと思い、Error の定義を見てみることに。

interface Error {
    name: string;
    message: string;
    stack?: string;
}

interface ErrorConstructor {
    new(message?: string): Error;
    (message?: string): Error;
    readonly prototype: Error;
}

declare var Error: ErrorConstructor;

declare しているのが varError で型は ErrorConstructor という interface
new の戻り値で Error interface を返している。。。

new はこうやって定義するんだな!

--

そして、Express は以下のように使うなー。。。
と思って定義を見てみることに。

import * as express from 'express';
const app = express();

定義はこんな感じ。
export = e ...! これか...!!

declare function e(): core.Express;
export = e;

そして出来上がったのがこれ。

interface Obniz {}

interface ObnizConstructor {
  new (id: string, options?: ObnizOptions): Obniz;
}
declare const Obniz: ObnizConstructor;

export = Obniz;

二つ目の難関 (wired)

obniz.wired() は、第一引数のパーツ名に合わせて、戻り値の型が変わります。 これは document.createElement() を参考にしました。

createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];

こちらはそれほど迷わずにクリア!

三つ目の難関 (パーツライブラリ)

私は IoT には疎いので、使うパーツは2~3種類です。
Light 系や Motor 系

その辺はまぁ良かったのですが。。。
パーツライブラリの数が多いこと多いこと。
こんなに充実していたとは。。。

公式パーツライブラリ には 63 種類のパーツが載っています。
開発中なのかパーツライブラリに載っていないパーツも少なからずありました。
とりあえず、公式パーツライブラリに載ってないので wired 的にはコメントアウトしておきましたが。。。

途中で挫折しそうになりながらもなんとか。。。

テスト

とりあえず初めの段階で、手元でフォークしたリポジトリを npm install して動作確認。
ts ファイルで LED や DC Motor を動かしてみる。
そして、最後にも同じことをやる。

自分の持っているパーツではこの程度しかテストできないので、
この時点でプルリクを出そうとも考えたが、最低限全体的に型エラーにならないことを確認したい。

実コードには手を入れていなく、型定義だけなのでユニットテストは違う。
どうしようか考えた結果、パーツライブラリに載っている全てのサンプルコードを ts ファイルで記述して型エラーにならなければ良いだろう。と考えた。

とりあえず test ディレクトリ以下に ts ファイルを作って、
パーツライブラリに載っているサンプルコード全ての ts ファイルに記述した。 型エラーになる項目を検出することをテストとしました。

そして型定義を直す。
ざっと15ファイル程度に定義ミスを発見できました。

そしてプルリクに至る。

最後に

思った以上のパーツが対応していて、そして全てサンプルコードがある。
そして、ほぼ全ての I/F を知った。
IoT が疎い私でもパーツライブラリに載っている配線を真似すればなんでもできそうな気がする!
そんな気持ちになりました。