Angular2+ SPA に JavaScript 実装の QR コード読み取り機能を搭載するにあたり、既存の JavaScript ライブラリを選定した結果発表

現在開発中の Angular2+ SPA プロジェクト ( モジュールバンドラーは webpack を採用 ) に、デバイスのカメラを使って QR コードを読み取る機能を搭載することとなりました。

そこで、QR コードを検出・読み取りする JavaScript 実装のライブラリを選定することとしました。

# 結論: jsQR

- https://www.npmjs.com/package/jsqr ( [GitHub](https://github.com/cozmo/jsQR) )
- Apache-2.0

イメージを渡すと、そのイメージ内の QR コードを検出・読み取って返すのみに特化されているライブラリです。
カメラや video 要素の制御については、このライブラリでは引き受けていません。
それら制御は自前で実装する必要があります。

使い方ですが、jsQR を使用したい TypeScript コード中で、下記のようにインポートすることで、jsQR 関数を使用することができるようになります。
jsQR 関数の引数にイメージを渡せば検出・読み取り結果が戻り値として返ります。

```ts
import jsQR from "jsQR";
```

ただし、下記のような型定義ファイルを自分で書いてプロジェクトに追加する必要はありました。

```ts:jsQR.d.ts
declare module 'jsQR' {
  export default function jsQR(imageData: Uint8ClampedArray, width: number, height: number): {
        data: string
  } | null;
}
```

動作はバッチリでした。

API も関数呼び出しだけで、すごくシンプルでわかりやすく、扱いやすい感じです。
変にコールバックや Promise とか要求しないのが好印象です。

もっとも引数に渡すイメージは Uint8ClampedArray であって data URL では渡せないなど、要件によっては面倒かもしれません。

しかし今回はむしろ、video -> canvas からの getImageData() で取得したものを渡したいので、Uint8ClampedArray を引数に渡せるのは Good.

いちいち data URL などコスト高な表現に変換もしないことから、処理性能も期待できそうです。
実際、かなりの反応の良さで QR コードを検出、読み取ってくれました。

# [NG] Instascan

- https://www.npmjs.com/package/instascan ( [GitHub](https://github.com/schmich/instascan) )
- MIT

カメラデバイスおよび video 要素の制御もコミコミの、オールインワンな感じのライブラリです。
API も素直で使いやすそうだったので、当初の本命でした。

[こんな日本語の紹介記事](http://paiza.hatenablog.com/entry/2017/08/17/PC%E3%82%AB%E3%83%A1%E3%83%A9%E3%81%8C%E3%80%8CQR%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AA%E3%83%BC%E3%83%80%E3%83%BC%E3%80%8D%E3%81%AB%E5%A4%A7%E5%A4%89%E8%BA%AB%EF%BC%81JavaScript%E3%81%A7%E7%B0%A1%E5%8D%98)もあったりします。

ところがところが、なんということでしょう、webpack ビルド時に下記エラーが。

```
ERROR in ./node_modules/instascan/src/zxing.js
Module not found: Error: Can't resolve 'fs' in '/Users/enzo/Copy/projects/elevenyellow/coinfy/node_modules/instascan/src'
resolve 'fs' in '/Users/enzo/Copy/projects/elevenyellow/coinfy/node_modules/instascan/src'
```

対策をググったところ、[GitHub の Issue](https://github.com/schmich/instascan/issues/121#issuecomment-357757911) に回避策が書かれているのを発見し、この回避策を web.config.js に仕込むことで、ビルドエラーは発生しなくなりました。

しかしながら今度は実行時に下記の例外が出てしまいました。

```
Error: Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.
Most likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)
```

どうやら、Angular2+ が変更検知のために使用している zone.js と干渉してしまっているようです。

ここで心折れたので、うまく使えたらいちばん楽できたであろうライブラリなのですが、使用を諦めました。

# [NG] zxing-typescript

- https://www.npmjs.com/package/zxing-typescript ( [GitHub](https://github.com/aleris/zxing-typescript) )
- Apache-2.0

試用してみたのですが、なまじ TypeScript ソースコードが npm パッケージに同梱されてしまっているためか、プロジェクトが採用している TypeScript バージョンが 2.4+ だったせいで、下記のような TS2322 TypeScript コンパイルエラーが webpack ビルド時の awesome-typescript-loader で多発...。

```
ERROR in [at-loader] ./node_modules/zxing-typescript/src/browser/BrowserCodeReader.ts:281:9
    TS2322: Type 'CanvasRenderingContext2D | null' is not assignable to type 'CanvasRenderingContext2D'.
  Type 'null' is not assignable to type 'CanvasRenderingContext2D'.
```

npm パッケージには生の TypeScript コードは同梱せず、型定義ファイルだけ含めるようにという教訓の例かも? と思いました。

このライブラリ開発当時のバージョンの TypeScript コンパイラを持ってきて、JavaScript + 型定義ファイルにビルドして使う、という対処もできなくもないとは思います。
しかし Instasacn の対応で疲れたので、(ほかにもよさげなライブラリがあるだろうと見越して) あまりこだわらずに使用を見送りました。

# ngx-zxing

- https://www.npmjs.com/package/ngx-zxing ( [GitHub](https://github.com/werthdavid/ngx-zxing) )
- MIT

QR コードリーダー機能を、Angular2+ の Component として提供するライブラリだそうです。
まさしく現在実装中なのは Angular2+ な SPA であるわけでして、これはうってつけのライブラリかと思ったのですが、README をよく読むと...

> "This library wraps zxing-typescript ...

とのこと。

すなわち、先述のビルド時エラー乱発する zxing-typescript をラップしてるよ、と読めます。
となると、この ngx-zxing をプロジェクトに取り込んでも、またしても TS2322 エラー連発になるのではないかと心配になりました。

ということで、ngx-zxing は試用もせずに採用を見送りました。

# qrcode-reader

- https://www.npmjs.com/package/qrcode-reader ( [GitHub](https://github.com/edi9999/jsqrcode) )
- Apache-2.0

このライブラリは、冒頭で紹介した jsQR 同様、イメージを処理するのみに特化したライブラリです。
カメラデバイスならびに video 要素の制御は範疇ではないようです。

使用したい TypeScript コード中で、下記のようにインポートすることで、QrCode クラスを new することができるようになり、その QrCode オブジェクトを使用・操作することで、イメージからの QR コードの検出・読み取りを行えるそうです。

```ts
import QrCode from 'qrcode-reader';
```

なお、下記のような型定義ファイルを自前で書いてプロジェクトに追加しないと TypeScript コンパイルがとおりませんでしたので、その点は注意。

```ts:qrcode-reader.d.ts
declare module 'qrcode-reader' {
  export default class QrCode {
    // さらにここに、メンバーの型情報を記述する必要あり
  }
}
```

とりあえずビルドと SPA の実行については問題はなさそうでした。
しかしながら先に jsQR を発見し利用開始していたため、 qrcode-reader の実際の動作は未確認に終わっています。

# まとめ

Angular2+ + TypeScript 2.4+ + webpack 3.0+ の環境で、JavaScript 実装による QR コード読み取り機能を選定しましたが、下記のとおりとなりました。

- :grin: **jsQR** - 採用
- :cry: **Instascan** - 実行時エラーで使用不可
- :cry: **zxing-typescript** - ビルド時エラーで使用不可
- :confused: **ngx-zixing** - 動作未確認 - 上記 zxing-typescript のラッパーらしいので zxing-typescript と同じくビルド時エラーを起こす心配あり。
- :neutral_face: **qrcode-reader** - 動作未確認 - ビルドが成功し、qrcode-reader を import したコンポーネントが実行時エラーを吐かないことは確認済み。使えそうな感触。

# 感想

たかだか QR コード読み取りの機能を搭載するためだけなのに、動作確認にかなり時間を食われて消耗気味です。

今日現在ですら、この投稿に記載のとおり互換性の問題などでビルド時エラーや実行時エラーを散発しているのに、5年とか経ったら保守できるのか不安に感じる Web フロント界隈です。