Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8883

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

$
0
0

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')constworker=newWorker('./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'newWorker(__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'newWorker(__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'newWorker(__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されたファイルをいくら修正しても、自動再コンパイル&再起動はされません。

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


Viewing all articles
Browse latest Browse all 8883

Trending Articles