https://github.com/murasuke/qr-reader-react-webworker.git を基にsolcでのCompileをwebWorkerで試みました。
参考ソースの段階でQRReader.tsx内の 141行目:drawImage プロパティ 'drawImage' は型 'OffscreenRenderingContext' に存在しません。 プロパティ 'drawImage' は型 'ImageBitmapRenderingContext' に存在しません。
142行目:getImageData プロパティ 'getImageData' は型 'OffscreenRenderingContext' に存在しません。 プロパティ 'getImageData' は型 'ImageBitmapRenderingContext' に存在しません。
の2箇所に問題があるが実装は出来る事を確認
以前作成したQRコード認識Reactコンポーネントを格好よくするため、上下に移動する「緑色のバー」を追加しました(CSS animation)。
※ ↓ 画像なのでわかりづらいですが「緑色のバー」が上下します。
ところが、バーの動きがガクガクになり、スムーズにアニメーションしてくれません。 QRコードの認識処理が描画処理がブロックしているようです。(タイマーで定期的に実行しているため)
こういう場合はWeb Workerをを利用すれば、バックグラウンドで認識処理行うことができます。 ところが簡単には導入できないようです。
-
create-react-appがWeb Workerをサポートしていない
-
TypeScriptをWeb Workerで動作させるにはejectする必要がある
試行錯誤の上、ejectしなくても動く手段が見つかりましたので顛末をまとめます。
comlink-loaderを使うと、容易に導入できます
Web Workerで実行する処理をTypeScriptの通常のメソッドとして
書くことができます。
呼び出しも通常の非同期メソッド(postMessageは不要)として呼びだすことができる優れものです。
Web Workerを意識せず、メソッドの呼び出しとして処理できてしまいます!
- QRコード認識についてはQRコード認識Reactコンポーネントをご確認ください。
comlink-loaderの組み込み手順
- ./src/worker フォルダに下記3ファイルを作成します
ファイル名 | 説明 |
---|---|
custom.d.ts | 型定義。worker.tsの型に合わせる(戻り値はPromis<>でラップする) |
index.ts | workerのインラインローダー。説明を読んでもよくわかりません・・・・ |
worker.ts | Web Workerに実行させる処理(function)定義 |
/* ./worker/custom.d.ts */
declare module 'comlink-loader!*' {
class WebpackWorker extends Worker {
constructor();
// Add any custom functions to this class.
// Make note that the return type needs to be wrapped in a promise.
processData(data: ImageData): Promise<QRCode>;
}
export = WebpackWorker;
}
/* ./worker/index.ts */
// eslint-disable-next-line
import Worker from 'comlink-loader!./worker'; // inline loader
export default Worker;
- QRコードの認識処理を記載します。
/* ./worker/worker.ts */
import jsqr, { QRCode } from 'jsqr';
export function processData(data: ImageData): QRCode {
// Process the data without stalling the UI
const qr = jsqr(data.data, data.width, data.height);
if (qr) {
console.log(qr.data);
return qr;
}
return null;
}
- 利用側ソース
Workerを生成して、Promiseを返す非同期メソッドとして呼び出すだけです。 (workerはレンダリング毎に生成されるのを防ぐためuseMemo()でキャッシュしています)
PostMessage()を使わず、普通のメソッドとしてWeb Workerが呼び出せてしまいます。
/* ./QRReader.tsx */
const QRReader: React.FC<QRReaderProps> = (props) => {
const worker = useMemo(() => new Worker(), [])
// ~~~ 途 中 略 ~~~
timerId.current = setInterval(() => {
context.drawImage(video.current, 0, 0, width, height);
const imageData = context.getImageData(0, 0, width, height);
worker.processData(imageData).then(qr => {
if (qr) {
console.log(qr.data);
if (props.showQRFrame) {
drawRect(qr.location.topLeftCorner, qr.location.bottomRightCorner);
}
if (props.gecognizeCallback) props.gecognizeCallback(qr);
}
});
}, props.timerInterval);
Create React AppでWeb Workerを使うには からの引用です。Web Workerを使うのはかなりしんどいようです。
Web Workerは.js
ファイルを読み込むため、publicフォルダにworker.js
ファイルを別途作成して配置しておくか、worker.ts
を別途tscでビルドしてpublicフォルダに配置するようにする必要がある。
const worker = new Worker('worker.js');
worker.postMessage(`hoge`);
処理内容をTypeScriptで書きたい、別にビルドするのが面倒なので却下
ejectしたくない、WebPackを直接使いたくない却下
WebPackを直接使いたくないので却下・・・
わからんでもないが、トリッキー過ぎるので却下・・・
Is it possible to use load webworkers? #1277 で上記1.~4.の議論が行われていますが、結論が良く割りませんでした。