概要
viteのdevServerではうまく動いていたのに、ビルドして本番環境にあげたら下記のエラーが発生。
Failed to load module script: The server responded with a non-JavaScript MIME type of "video/mp2t". Strict MIME type checking is enforced for module scripts per HTML spec.
isTrusted: true
currentTarget: Worker {onmessage: null, onerror: null}
index-OVCDg4Z6.js:1 Uncaught Error: Worker error occurred
対応
apps/frontend/src/workers/BaseWorkerClient.ts
import type { BaseWorkerRequest, BaseWorkerResponse } from './types';
export abstract class BaseWorkerClient<
TRequest extends BaseWorkerRequest,
TResponse extends BaseWorkerResponse,
> {
protected worker: Worker | null = null;
private messageId = 0;
private pendingRequests = new Map<
number,
{ resolve: (value: unknown) => void; reject: (error: Error) => void }
>();
/**
* Worker URL/コンストラクタを返す(サブクラスで実装)
*/
- protected abstract getWorkerUrl(): URL;
+ protected abstract getWorkerUrl(): URL | (new () => Worker);
async initialize(): Promise<void> {
if (this.worker) {
throw new Error(`${this.constructor.name} is already initialized`);
}
// Workerインスタンス作成
const workerUrl = this.getWorkerUrl();
- this.worker = new Worker(workerUrl, { type: 'module' });
+ this.worker =
+ typeof workerUrl === 'function'
+ ? new workerUrl()
+ : new Worker(workerUrl, { type: 'module' });
// Workerからのレスポンスハンドラー設定
this.worker.addEventListener(
'message',
this.handleWorkerMessage.bind(this),
);
this.worker.addEventListener('error', this.handleWorkerError.bind(this));
// 初期化処理(サブクラスでオーバーライド可能)
await this.onInitialize();
}
/**
* 初期化時の追加処理(サブクラスでオーバーライド)
*/
// eslint-disable-next-line class-methods-use-this
protected async onInitialize(): Promise<void> {
// デフォルトでは何もしない
}
/**
* Workerからのメッセージを処理
*/
private handleWorkerMessage(event: MessageEvent<TResponse & { id: number }>) {
const { id, ...response } = event.data;
const pending = this.pendingRequests.get(id);
if (!pending) {
console.warn(`No pending request found for message ID: ${id}`);
return;
}
this.pendingRequests.delete(id);
if ('error' in response && response.error) {
pending.reject(new Error(response.error as string));
} else {
pending.resolve(response);
}
}
/**
* Workerエラーハンドラー
*/
private handleWorkerError(error: ErrorEvent) {
console.error(`${this.constructor.name} error:`, error);
// 全ての待機中リクエストをreject
this.pendingRequests.forEach((pending) => {
pending.reject(new Error('Worker error occurred'));
});
this.pendingRequests.clear();
}
/**
* Workerにリクエストを送信し、レスポンスを待機
*/
protected sendRequest<T extends TResponse>(request: TRequest): Promise<T> {
if (!this.worker) {
return Promise.reject(
new Error(`${this.constructor.name} is not initialized`),
);
}
const id = this.messageId + 1;
this.messageId = id;
return new Promise<T>((resolve, reject) => {
this.pendingRequests.set(id, {
resolve: resolve as (value: unknown) => void,
reject,
});
this.worker!.postMessage({ id, ...request });
});
}
/**
* Workerを終了
*/
terminate(): void {
if (this.worker) {
this.worker.terminate();
this.worker = null;
this.pendingRequests.clear();
}
}
}
apps/frontend/src/workers/dbWorkerClient.ts
import { BaseWorkerClient } from './BaseWorkerClient';
import type { DBWorkerRequest, DBWorkerResponse } from './db.worker';
+ import DBWorker from './db.worker?worker';
/**
* DBWorkerクライアント(汎用)
* エンティティ固有のロジックは含まない
*/
class DBWorkerClient extends BaseWorkerClient<
DBWorkerRequest,
DBWorkerResponse
> {
// eslint-disable-next-line class-methods-use-this
protected getWorkerUrl(): URL | (new () => Worker) {
- return new URL('./db.worker.ts', import.meta.url);
+ // 開発環境では new URL() を使用(HMR対応)
+ if (import.meta.env.DEV) {
+ return new URL('./db.worker.ts', import.meta.url);
+ }
+ // 本番ビルドでは ?worker インポートを使用
+ return DBWorker;
}
/**
* 初期化時にマイグレーションを実行
*/
protected async onInitialize(): Promise<void> {
await this.sendRequest({ type: 'migrate' });
}
/**
* 汎用リクエスト送信メソッド
* エンティティAPIから直接使用される
*/
async request<T = unknown>(type: string, payload?: unknown): Promise<T> {
const response = await this.sendRequest<DBWorkerResponse & { data: T }>({
type,
payload,
});
return response.data as T;
}
}
// シングルトンインスタンス
export const dbWorkerClient = new DBWorkerClient();