🔨

Vue3 + Vite で Monaco Editor の Web Worker を設定する

はじめに

monaco-editorをインストールし、monacoEditor.editor.create() を行うとエディターがレンダリングされます。
一見正しいように見えますが、コンソールを開くと Web Worker に関するエラーが発生しています。
今回は Vue + Viteの構成における Web Worker の設定方法をまとめます。

サンプルコードに関して

今回のプロジェクトは create-vue を利用して作成されることを想定しています。
作成直後のプロジェクトに Monaco Editor に関する以下の変更を加えたソースコードに対して変更を加える想定でサンプルコードを記載します。

  • monaco-editor のインストール
  • 任意のSFCにおいて、 monacoEditor.editor.create() を実行する

エラーの詳細

発生するエラーは以下のようなものです。

chunk-2APFOYHI.js?v=62e343f8:40838 Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/microsoft/monaco-editor#faq
logOnceWebWorkerWarning @ chunk-2APFOYHI.js?v=62e343f8:40838
Show 1 more frame
Show less
chunk-2APFOYHI.js?v=62e343f8:40840 You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker
logOnceWebWorkerWarning @ chunk-2APFOYHI.js?v=62e343f8:40840
Show 1 more frame
Show less
chunk-2APFOYHI.js?v=62e343f8:9832 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toUrl')
    at _FileAccessImpl.toUri (chunk-2APFOYHI.js?v=62e343f8:9832:40)
    at _FileAccessImpl.asBrowserUri (chunk-2APFOYHI.js?v=62e343f8:9788:26)
    at chunk-2APFOYHI.js?v=62e343f8:45899:32
    at new Promise (<anonymous>)
    at EditorSimpleWorker.$loadForeignModule (chunk-2APFOYHI.js?v=62e343f8:45891:12)
    at chunk-2APFOYHI.js?v=62e343f8:83747:22
    at async tsMode-HJUKZYNU.js?v=62e343f8:81:16
    at async WorkerManager.getLanguageServiceWorker (tsMode-HJUKZYNU.js?v=62e343f8:87:20)
    at async DiagnosticsAdapter._doValidate (tsMode-HJUKZYNU.js?v=62e343f8:368:20)

要約すると以下のようなことが書かれています。

  • Monaco Editor が Web Worker を作成しようとしたが失敗し、処理がメインスレッドで実行されている
  • MonacoEnvironment.getWorkerUrl または MonacoEnvironment.getWorker が未定義のため Web Worker を作成できない
  • toUrl 関数の呼び出しで undefined を読み取ろうとしている

対応

Viteのエントリーポイントのファイルに MonacoEnvironment.getWorker を追加する処理を記述することで対応できます。

MonacoEnvironment.getWorker を追加する

monaco-editor のGitHubリポジトリに React + Vite での実装例があります。
これを拝借します。

src/userWorker.ts
import * as monaco from 'monaco-editor';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';

// @ts-ignore
self.MonacoEnvironment = {
 getWorker(_: any, label: string) {
  if (label === 'json') {
   return new jsonWorker();
  }
  if (label === 'css' || label === 'scss' || label === 'less') {
   return new cssWorker();
  }
  if (label === 'html' || label === 'handlebars' || label === 'razor') {
   return new htmlWorker();
  }
  if (label === 'typescript' || label === 'javascript') {
   return new tsWorker();
  }
  return new editorWorker();
 }
};

monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);

Viteのエントリーポイントのファイルに追加する

先ほどの userWorker.tsmain.ts にインポートします。
side-effects import を利用し、モジュールごとインポートします。

src/main.ts
import "./assets/main.css";

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "@/userWorker"

const app = createApp(App);
app.use(router);
app.mount("#app");

以上で対応は完了です。
コンソールに発生していた警告、エラーがすべて消えているはずです。

Vue・Nuxt 情報が集まる広場 / Plaza for Vue・Nuxt.

Discussion