🦊

TypeScriptで作成したWebWorkerをnpmパッケージにバンドルする方法

2021/10/01に公開

追記

今回記載の内容は、webpack5 で試して動かしたものですが、本来webpack4 で使われていた内容みたいです。

Note that this is specific to webpack 4. To use Web Workers in webpack 5, see https://webpack.js.org/guides/web-workers/.

リンク先のページを見ると webpack5 は worker-loader を使わなくても動くよ、と書かれているけど、インライン化がうまくできなかった。
github を見てみるとasset/inline でを使えばできるみたいだけど、最終的には js の状態の webworker を読み込む話になっているようにみえる。「違う、そうじゃない」。
今回は、WebWorker の呼び出し側のコード上で、WebWorker の Typescript のファイルを指定して、WebWorker を起動させたいのです。
asset/inline で指定した ts をコンパイルしてからバンドルしてくれるようになればいいのだができないみたいです。

なので、とりあえず、いまのところは今回記載の内容に落ち着きました。
(webpack5 だと worker-loader は deprecated だが、今のところ仕方ないという判断。どなたか、いい方法があれば教えてください。。。)

はじめに

これまで様々な機械学習モデルを WebWorkder で動かす npm パッケージを作成してきたのですが、どうにも TypeScript で作成した WebWorker を単一のファイルにバンドルする方法がわかりませんでした。なので、npm install 後に WebWorker のスクリプトを public フォルダにコピーしてもらうようなインストール方法を Readme に記載していました。

たとえば、次のような感じ。

$ npx create-react-app demo  --template typescript
$ cd demo/
$ npm install
$ npm install @dannadori/bodypix-worker-js
$ cp node_modules/\@dannadori/bodypix-worker-js/dist/bodypix-worker-worker.js public/ # <- Here!! Too lame!!

ダサいですね。

いい加減なんとかしようということで調べ直したら、できるようになったので、やり方をここでご紹介します。

前提

今回は webpack5 系を前提とします。4 系では試してないです。

js ファイルで作成したファイルをバンドルする場合

まずは、javascript で作成された WebWorker をバンドルする方法です。

事前設定

webpack を用いて WebWorker をバンドルする場合は、worker-loader を用いると便利です。

$ npm install -D worker-loader

次に webpack.config.js にルールを追記します。 WebWorker の javascript(ここではファイル名の末尾が worker.js のもの)を対象に worker-loader を使用するようにします。
このときに、オプションに inline:"no-fallback"をつけてください。これにより、出力されるファイルに WebWorker の javascript がバンドルされるようになります。

        rules: [
            { test: /\.ts$/, loader: 'ts-loader' },
            {
                test: /worker\.js$/i,
                loader: "worker-loader",
                options: {
                    inline:"no-fallback"
                },
            },
        ],

コード

まずは worker のコードです。この記事を読んでいる方は WebWorker の作成自体はお手の物だと思いますので、説明は省略します。

onmessage = function (event) {
    setTimeout(() => {
        postMessage(`[WORKER_JS] Waited ${event.data}ms`);
    }, event.data)
};

npm パッケージの main コードでは WebWorker のファイルをインポートして呼び出します。

import libJs from '../src-worker/worker.js'

export class WebWorkerLibJs{
    worker:Worker
    constructor(){
        this.worker = libJs()
        this.worker.onmessage = (mess) =>{
            console.log(`[WebWorkerLibJs] ${mess.data}`)
        }
    }

    sendMessage = () =>{
        this.worker.postMessage(2 * 1000);
    }
}

以上です。めちゃめちゃ簡単です。

ts ファイルを作成したファイルをバンドルする場合

次に typescript で作成した WebWorker をバンドルする方法です。

事前設定

webpack を用いて WebWorker をバンドルする場合は、worker-loader を用います(js の場合と同じ)。

$ npm install -D worker-loader

webpack.config.js への追加の設定は不要です。もちろん TS をコンパイルするために ts-loader の設定は必要ですが。

        rules: [
            { test: /\.ts$/, loader: 'ts-loader' },
        ],

コード

まずは型設定で worker-loader を使うように設定します。
./typings/worker-loader.d.ts

declare module "worker-loader!*" {
    class WebpackWorker extends Worker {
        constructor();
    }
    export default WebpackWorker;
}

worker のコードです。

const ctx: Worker = self as any;

onmessage = async (event) => {
    setTimeout(() => {
        ctx.postMessage(`[WORKER_TS] Waited ${event.data}ms`);
    }, event.data)

}

npm パッケージの main コードでは WebWorker のファイルをインポートして new します。
WebWorker のファイルをインポートする際には、?を使って inline パラメータを設定するところがポイントです。
これがないと単一ファイルにバンドルしてくれません。

import libTs from "worker-loader?inline=no-fallback!./worker.ts";

export class WebWorkerLibTs{
    worker:Worker
    constructor(){
        this.worker = new libTs()
        this.worker.onmessage = (mess) =>{
            console.log(`[WebWorkerLibTs] ${mess.data}`)
        }
    }

    sendMessage = () =>{
        this.worker.postMessage(3 * 1000);
    }
}

以上です。こちらもめちゃめちゃ簡単ですね。

リポジトリ

ここに記載した内容は下記のリポジトリに格納されています。

https://github.com/w-okada/bundle-webworker

Readme に従ってコマンドを実行してみてください。
コード量も少ないので、動かしながらで10分位で理解できると思います。

あとがき

ちなみに、この内容は公式のドキュメントにも一部記載があります(JS の部分)。
しかし、TS でバンドルする方法は記載されていないです。
なので、自分の npm パッケージでは、パッケージを使用するユーザが js ファイルをコピーするような導入方式にしてしまっていました。
残念。少しずつ作り直そう。。。

公式

Discussion