Tauri(Rust + React + TypeScript) から始めるディスクトップアプリ #2[MonacoEditor導入]
はじめに
今回は、monaco Editorの組み込みをします。
前も書いたのですが、理由は下記の通りです。
Editorを自作するのは時間がいくらあっても足りないと思いますし、努力に見合うリターンが得られると思いません。のでライブラリを探します。
有償のものから、MITライセンスのものまで探すとたくさんあります。
今回は、商用利用可能なライブラリで、かつ無償なライブラリを採用したいと思います。
WYSIWYGのEditorが多く検索に引っ掛かりますが、SQLを中心に記述するEditorにしたいため、WYSIWYGは不要です。時間をかけて検索し実績とサンプルの多さでAceEditorとMonacoEditorあたりが候補なりました。メリデメとか見てもさっぱりわからないので、見た目の好みを優先し、みんな大好きVisualStadioCodeで利用されているMonacoEditorを採用します。
2023/8/8 時点最新の MonacoEditor v0.41.0を利用します。
MonacoEditor の組み込み
インストールします。
npm install monaco-editor@0.41.0
monaco react で検索すると様々なパッケージがあります。選定してもいいのですが、バグなど放置されている傾向があるので、今回は避けています。
monaco-editorパッケージだけで特に面倒なく利用可能です。
{
"name": "myAPP",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"monaco-editor": "^0.41.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@tauri-apps/cli": "^1.4.0",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}
MonacoEditor のサンプルコード
GTP様に伺っても微妙な回答しか得られなかったです。一番参考になったのは、
(微妙に記述が古いんであれですがw)
import React from "react";
import ReactDOM from "react-dom/client";
import { Editor } from "./components/Editor";
import "./useWorker.ts";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<>
<h2>MonacoEdit</h2>
<Editor />
</>
</React.StrictMode>
);
Monaco Editorは多くの言語に対応するワーカーモジュールがあり、その組み込みです。
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";
self.MonacoEnvironment = {
getWorker(_, label) {
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);
コンポーネント
import { useRef, useState, useEffect, FC } from "react";
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
import styles from "./Editor.module.css";
export const Editor: FC = () => {
const [editor, setEditor] =
useState<monaco.editor.IStandaloneCodeEditor | null>(null);
const monacoEl = useRef(null);
useEffect(() => {
if (monacoEl) {
setEditor((editor) => {
if (editor) return editor;
return monaco.editor.create(monacoEl.current!, {
value: ["function x() {", '\tconsole.log("Hello world!");', "}"].join(
"\n"
),
language: "typescript",
lineNumbers: "on", // テキストエディタの行番号を表示するかどうか
roundedSelection: true, // 選択範囲を角丸にするかどうか
scrollBeyondLastLine: false, // テキストエディタの最後の行を超えてスクロールするかどうか
readOnly: false, // テキストエディタを読み取り専用にするかどうか
theme: "vs-dark", // テキストエディタのテーマ
});
});
}
return () => editor?.dispose();
}, [monacoEl.current]);
return <div className={styles.Editor} ref={monacoEl}></div>;
};
下記にESLINTのワーニングが出ると思いますが、自分は消しちゃいました。
[monacoEl.current]
rules: {
// 確認をOFFにする。
"react-hooks/exhaustive-deps": "off",
},
.Editor {
width: 100vw;
height: 100vh;
}
実行結果
npm run tauri dev
お疲れさまでした。
あとがき
ファイルにSAVEなど、次回実装しましょう。
Discussion