LoginSignup
5
1

More than 1 year has passed since last update.

create-react-app(TypeScript)で作成したアプリにWeb Workerを導入する方法

Last updated at Posted at 2021-07-05

はじめに

以前作成したQRコード認識Reactコンポーネントを格好よくするため、上下に移動する「緑色のバー」を追加しました(CSS animation)。

※ ↓ 画像なのでわかりづらいですが「緑色のバー」が上下します。

scanbar.png

ところが、バーの動きがガクガクになり、スムーズにアニメーションしてくれません。
QRコードの認識処理が描画処理をブロックするためです。(タイマーで定期的に認識処理を実行)

こういう場合はWeb Workerを利用すれば、バックグラウンドで認識処理行うことができます。
ところがcreate-react-appで作成したプログラムには、簡単には導入できないようです。

  • create-react-appがWeb Workerをサポートしていない

  • Web Workerは.jsファイルを要求するため、ejectしてからWebPackを設定する必要がある。

試行錯誤の上、eject無しに動く手段が見つかりました。


comlink-loaderを使うと、容易に導入できます

Web Workerで実行する処理をTypeScriptの通常のメソッドとして書くことができます。
呼び出しも通常の非同期メソッド(postMessageは不要)として呼びだすことができる優れものです。

Web Workerを意識せず、メソッドの呼び出しとして処理できてしまいます!

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.onRecognizeCode) props.onRecognizeCode(qr);               
    }
    });
  }, props.timerInterval);

create-react-appで作ったアプリケーションとWeb Workerを組み合わせた場合に発生する技術的な課題について

Create React AppでWeb Workerを使うには からの引用です。Web Workerを使うのはかなりしんどいようです。

1. publicフォルダにWorkerのJSファイルを配置して読み込む

Web Workerは.jsファイルを読み込むため、publicフォルダにworker.jsファイルを別途作成して配置しておくか、worker.tsを別途tscでビルドしてpublicフォルダに配置するようにする必要がある。

const worker = new Worker('worker.js');
worker.postMessage(`hoge`);

処理内容をTypeScriptで書きたい、別にビルドするのが面倒なので却下

2. ejectしてからWebPackの設定に worker-loader または worker-plugin を追加する

ejectしたくない、WebPackを直接使いたくないので却下

3. ejectせずに react-app-rewired を使ってWebPackの設定に worker-loader または worker-plugin を追加する。

WebPackを直接使いたくないので却下

4. WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する。

わからんでもないが、トリッキー過ぎるので却下

Is it possible to use load webworkers? #1277 で上記1.~4.の議論が行われていますが、結論が良く割りませんでした。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1