Help us understand the problem. What is going on with this article?

Node.js Worker Threads: TypeScriptのワーカーを起動する方法 〜ts-node、ts-node-devに対応する方法〜

Node.jsのWorker Threadsは、本物のスレッドプログラミングができます。ワーカーの処理を記述したJavaScriptを与えて、ワーカーを起動するわけですが、TypeScriptのファイルを指定するにはどしたらいいのでしょうか?

本稿でわかること

  • ts-nodeとWorker Threadsを組み合わせて、TypeScriptのワーカーを起動する方法
  • ts-node-devでTypeScriptのワーカーを起動する方法

前提知識

本稿を理解するにあたっては、下記の技術についての基礎的な知識が必要です。

  • Worker Threads
  • ts-node
    • TypeScriptのコンパイルとJavaScriptの実行をコマンド一つでできるツール。
      • tsc && node dist/main.jsを一発でできるようにしたツール。
    • nodeコマンドの感覚でTypeScriptを実行できる。 例: ts-node src/main.ts
  • ts-node-dev
    • ts-nodeとnode-devを組み合わせた開発ツール。
    • TypeScriptのコードに変更が加わると自動的にコンパイルし、プログラムを再起動してくれる。

解決したい課題: WorkerにTypeScriptを指定することはできない

JavaScriptでWorker Threadsを実行する方法は以下のような手順になります。

まず、ワーカー側の処理を実装したJavaScriptファイルを作ります:

worker.js
console.log('Hello from worker')

次に、ワーカーを起動する処理を書くのですが、Workerコンストラクタでワーカーのファイル名を指定する必要があります:

main.js
const {Worker} = require('worker_threads')
const worker = new Worker('./worker.js')

このmain.jsを実行すると、ワーカーが起動することが確認できます:

$ node main.js
Hello from worker

このコードをTypeScriptで書き直し、ts-nodeで同じように実行するとどうなるでしょうか? やってみましょう。

まず、ワーカー側の実装をTSに移植します。内容はworker.jsと全く同じです:

worker.ts
console.log('Hello from worker')

次に、main.jsをTSに移植します。大きな変更は、worker.jsではなくworker.tsを起動するように変える点です:

main.ts
import {Worker} from 'worker_threads'

new Worker(__dirname + '/worker.ts') // tsファイルを指定

このコードを、ts-nodeで起動してみます。すると、次のようなエラーが発生し、ワーカーが起動できないことが分かります。エラー内容は、「ワーカースクリプトの拡張子はjs, mjs, cjsじゃないとダメだよ」というものです。

$ ts-node src/main.ts
The worker script extension must be ".js", ".mjs", or ".cjs". Received ".ts"

このことから、Worker Threadsでは直接TypeScriptファイルが指定できないことが分かったと思います。

解決策: いったんJavaScriptファイルを経由するようにする

Worker Threadsで起動できるコードはJavaScriptのみという制約があるので、直接TypeScriptのワーカーを起動するのはあきらめます。迂回手段として、まずJavaScriptのワーカーを起動し、その中でTypeScriptコードをrequireするようにします。

先述した失敗作TypeScriptコードを手直ししていきましょう。

まず、main.tsはworker.tsではなく、worker.jsを起動するように直します:

main.ts
import {Worker} from 'worker_threads'

new Worker(__dirname + '/worker.js') // jsを起動するように直す

次に、最終的な目的地である、TypeScriptワーカーのファイルを作ります。名前はworker.jsと区別できるようtsWorker.tsにしておきます:

tsWorker.ts
console.log('tsWorker.ts started')

最後に、main.tsとtsWorker.tsを橋渡しする、worker.jsを実装します。worker.jsの重要な役割は、ts-nodeをregisterすることです。これにより、以降のコードではtsファイルをrequireして実行できるようになります:

worker.js
console.log('worker.js started')
require('ts-node').register() // 重要
require(__dirname + '/tsWorker.ts')

このコードを実行してみましょう。

$ ts-node src/main.ts
worker.js started
tsWorker.ts started

出力結果から、まずworker.jsが実行され、次にtsWoekr.tsが読み込まれ実行されたことがわかると思います。

ts-node-devでは、execArgvを空っぽにしてWorkerを起動する

これまでts-nodeでTypeScriptワーカーを起動する方法を説明してきましたが、類似のツールであるts-node-devでも同じ方法で対応できるのでしょうか? 結論を言うと、そのままでは対応できません。

上のmain.tsをts-node-devで実行してみると分かりますが、worker.jsは起動するものの、worker.js内のrequireが動作せず、スレッドが終了してしまいます:

$ ts-node-dev src/main.ts
Using ts-node version 8.8.1, typescript version 3.8.3
worker.js started

worker.jsでexecArgvを確認すると、ワーカー側では不要なts-node-devのフックが渡ってきているのがわかります:

worker.js
console.log('worker.js started')
console.log(process.execArgv)
// require('ts-node').register()
// require(__dirname + '/tsWorker.ts')
実行結果
$ ts-node-dev src/main.ts
Using ts-node version 8.8.1, typescript version 3.8.3
worker.js started
[
  '-r',
  '/var/folders/4l/mrmcxh3x40lbcpwxyz29ppcw0000gn/T/ts-node-dev-hook-1629690677650566.js'
]

解決策としては、main.tsのnew WorkerのオプションでexecArgvをカラにすることです:

main.ts
import {Worker} from 'worker_threads'

new Worker(__dirname + '/worker.js', {execArgv: []})

こうしておくと、まずはts-node-devでもTypeScriptのワーカーが起動できるようになります。

$ ts-node-dev src/main.ts
Using ts-node version 8.8.1, typescript version 3.8.3
worker.js started
tsWorker.ts started

しかし、この方法に問題がないわけではありません。ts-node-devの醍醐味としては、TypeScriptのコードを書き直したら、自動的に再コンパイルして、プロセスを起動しなおしてくれることです。しかし、この対処法では、スレッド側でrequireされたファイルをいくら修正しても、自動再コンパイル&再起動はされません。

この課題の解決策については、また時間を見つけて調べてみたいと思います。

suin
Qiita 4位/TypeScript入門書執筆中/TypeScripterのための座談会「YYTypeScript」主催/『実践ドメイン駆動設計』書籍邦訳レビュア/分報Slack考案/YYPHP主催/CodeIQマガジン執筆/株式会社クラフトマンソフトウェア創設/Web自動テスト「ShouldBee」の開発/TypeScript/DDD/OOP
https://yyts.connpass.com/
craftsman_software
「インフラの心配は、もうおしまい」 インフラ運用を自動化し、手作業を限りなくゼロにする会社
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away