0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Typescript + WebWorker + Vite でビルドすると、 data:video/mp2t;base64 で期待しないビルド結果となったことに対応したメモ

Posted at

概要

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();

参考

vitejs: issue

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?