nextjs+TypeScript環境でマルチスレッド処理したくなったので、web workerを使うための環境構築メモ
いろいろ試したけど、結局以下のURLのやり方でいけた
https://github.com/zeit/next.js/issues/4768#issuecomment-501082855
今回はすでにTypeScript環境は構築済みのものとする
サンプルプロジェクトは以下
https://github.com/kuwabataK/next-template/tree/service-worker-test
環境
- nextjs ver 9.0.5
- typescript ver 3.6.2
install
worker-loader
と @zeit/next-workers
をインストールする
npm install worker-loader @zeit/next-workers
tsconfig
の設定
lib
に webWorker
を追加する
{
"compilerOptions": {
... 略
"lib": [
"dom",
"es2017",
"webworker" // 追加
],
... 略
}
next.config.jsの設定
以下のようなnext.config.js
をプロジェクト直下に作成する
/* eslint-disable @typescript-eslint/explicit-function-return-type */
module.exports = {
webpack(config) {
config.module.rules.push({
test: /\.worker\.js$/,
loader: 'worker-loader',
options: {
// inline: true,
name: 'static/[hash].worker.js',
publicPath: '/_next/'
}
})
// Overcome webpack referencing `window` in chunks
config.output.globalObject = `(typeof self !== 'undefined' ? self : this)`
return config
}
}
typesの設定
TypeScriptのmodule importを解決するため以下のようなdeclareファイルを作成する。
本当はworkerごとに作ったほうが良いけれど、とりあえず動かすだけなら以下でOK
declare module 'worker-loader?name=static/[hash].worker.js!*' {
class WebpackWorker extends Worker {
constructor()
}
export default WebpackWorker
}
とりあえずここまでで準備は完了
workerの作成
以下のようにexample.worker.ts
を作る。workerの名前は、hogehoge.worker.ts
にしないと動かないので注意。今回はpostMessageで受け取った値を2乗して返すだけのWorkerを作る
import { square } from '../utils/calc' // 他のtsファイルをimportして使うこともできるよ
/* eslint-disable @typescript-eslint/no-explicit-any */
const ctx: Worker = self as any
ctx.addEventListener('message', async event => {
console.log('worker側だよ!! 受け取った値は', event.data)
const res = square(event.data)
ctx.postMessage({ input: event.data, output: res }) // 呼び出し元にEventを発火して結果を返す
})
export default ctx
/**
* 引数を2乗して返すだけの関数
* このメソッドはWebWorker関係なくプロジェクトのどこからでも呼び出せる
* @param a
*/
export function square(a: number): number {
return a * a
}
pagesでの呼び出し方
以下のような感じでworker
を呼び出せる
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import * as React from 'react'
import Layout from '../src/components/template/Layout'
import Link from 'next/link'
// worker import時はfileパスの頭に 'worker-loader?name=static/[hash].worker.js!' をつける
import MyWorker from 'worker-loader?name=static/[hash].worker.js!../src/workers/example.worker'
let worker: MyWorker
const WebWorker: React.FC = () => {
// mount時にworkerを作成する
React.useEffect(() => {
worker = new MyWorker()
// unmount時にworkerをterminateする
return () => worker.terminate()
}, [])
/**
* workerから帰ってくるイベント発火時に実行される関数
* @param event
*/
const onWorkerMessage = (event: { data: number }) => {
console.log('Workerから結果が帰ってきたよ!!!', event.data)
}
/**
* workerの処理を実行する
*/
const exec = () => {
worker.onmessage = onWorkerMessage // workerから帰ってくる処理結果をひろうリスナーを登録
worker.postMessage(33) // イベント経由でworkerに処理を依頼
}
return (
<Layout title="WebWorkerテスト | Next.js + TypeScript Example">
<h1>WebWorkerテスト</h1>
<button onClick={exec}>WebWorker呼び出し</button>
<p>
<Link href="/">
<a>Go home</a>
</Link>
</p>
</Layout>
)
}
export default WebWorker
型定義ファイルを真面目に作ってないせいで型推論がうまく効いてくれないんだけれども、とりあえず動くので良しとする