Vue で RichTextEditor に画像ギャラリーを追加する

Vite + Vue のサンプルの RichTextEditor。Express サーバーのギャラリーから挿入した画像が本文に表示されている

Vue アプリで RichTextEditor の「Insert Gallery」ダイアログを使うには、3 つのものが必要です。アプリより前に読み込むエディターのスクリプト、マウント時にエディターを作成してアンマウント時に破棄するコンポーネント、そしてダイアログのリクエストを API に転送する開発サーバーです。このページでは、この 3 つをすべて満たし、Express のサンプルを相手にテストした Vite + Vue 3 の構成を紹介します。

アプリより前にエディターのスクリプトを読み込む

エディターは、window.RichTextEditor を定義する通常のブラウザー用スクリプト群です。ES モジュールではないため、Vue をマウントするモジュールスクリプトより前の index.html に置きます。

HTML · index.html
<!-- Plain browser scripts from public/, loaded before the Vue bundle. -->
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<script src="/rte-files.js"></script>

ファイルは public/richtexteditor/ に置きます。Vite はここにあるファイルをそのまま配信します。rte-files.js は、エディターとギャラリーの「Upload」ボタンが呼び出すアップロード関数を提供します。<head> 内の従来型のスクリプトはモジュールバンドルより前に実行されるため、コンポーネントがマウントされる時点でグローバル変数は存在しています。

コンポーネント

Vue · src/components/RichTextEditor.vue
<script setup>
import { onBeforeUnmount, onMounted, useTemplateRef } from "vue";

// window.RichTextEditor comes from the scripts in index.html.
const props = defineProps({
  options: { type: Object, required: true },
  initialHtml: { type: String, default: "" },
});
const container = useTemplateRef("container");
let editor = null; // a plain variable: the editor object does not need to be reactive

onMounted(() => {
  editor = new window.RichTextEditor(container.value, props.options);
  editor.setHTMLCode(props.initialHtml);
});
onBeforeUnmount(() => {
  editor?.destroy();
  editor = null;
});

defineExpose({ getHTML: () => editor.getHTMLCode() });
</script>

<template>
  <div ref="container"></div>
</template>
  • onMounted は、テンプレートの div がドキュメントに追加された後に実行されます。useTemplateRef("container")(Vue 3.5)はその要素を返します。
  • onBeforeUnmount は、要素がまだ存在しているうちに destroy() を呼び出します。app.unmount() でアプリ全体をアンマウントして確認したところ、ドキュメントにエディターの要素は残っておらず、ページのエラーもありませんでした。
  • インスタンスは通常の変数です。テンプレートはこれに依存していないため、リアクティブにする必要はなく、Vue がプロキシで包む理由もありません。
  • defineExpose は、親に getHTML() メソッドを公開します。サンプルの「Show HTML」ボタンは、テンプレート ref を通じてこれを呼び出し、<p>Hello from Vue.</p> と出力しました。

親はオプションを 1 回だけ渡します。コンポーネントはエディターの作成時にオプションを読み取り、その後は監視しません。

Vue · src/App.vue
const options = {
  toolbar: "full",
  galleryEndpoint: "/rte/gallery", // forwarded to Express by vite.config.js
  maxUploadFileSize: 4000000,
};

開発中に API サーバーへ届ける

ギャラリーのエンドポイントは相対パスの /rte/gallery で、ページはポート 5112 で Vite が配信します。Vite の開発用プロキシは、ギャラリーとアップロードのルート、そして画像を、ポート 5101 で動く Express のサンプルに転送します。

JavaScript · vite.config.js
// /rte/ and /uploads/ go to the Express sample (node-express, port 5101).
// Keep the trailing slashes: "/rte" alone would also catch /rte-files.js.
proxy: {
  "/rte/": "http://localhost:5101",
  "/uploads/": "http://localhost:5101",
},

ブラウザーからはオリジンが 1 つに見えるため、CORS を設定する必要はありません。末尾のスラッシュは残してください。Vite は単純なキーを URL の先頭と比較するので、"/rte" では /rte-files.js も Express に転送されてしまいます。React のサンプルで同じプロキシ設定を使ったときに、実際にそうなりました。"/rte/" にすると、Vite が自分のコピーを配信します。

プロキシは開発時にだけ使ってください。本番環境では、ビルドした dist/ と API を同じオリジンから配信します。

ダイアログに表示されるもの

ページ上部の画像は、この Vue アプリのエディターで、ギャラリーから画像を挿入した後の状態です。フォルダーと画像は Express サーバーから取得しています。サーバーについては Node.js のギャラリーガイドで説明しています。フレームワークにかかわらず、リリース版のギャラリーに由来する制限が 2 つあります。形式のヒントには JFIF、BMP、AVIF が含まれていますが、サンプルのサーバーはこれらを拒否します。また、拒否されたアップロードについては、サーバーのメッセージではなく「Upload failed for <name>.」と表示されます。

テストする

このガイドのサンプルプロジェクトと Express のサンプルをダウンロードします。まず Express を起動します(エディターの richtexteditor フォルダーを server.mjs と同じ場所に置き、npm install、npm start を実行します)。次に、Vue プロジェクトで以下を実行します。

Shell
npm install
npm run dev
  ➜  Local:   http://localhost:5112/

npm run dev は、最初にエディターのファイルを public/ にコピーします。RTE_DIR にエディターのフォルダーを設定するか、そのフォルダーを自分で public/richtexteditor に置いてください。http://localhost:5112 を開き、Insert Gallery をクリックします。ダイアログにはサーバーのフォルダーが一覧表示されます。New folder と Upload はプロキシを通じて動作し、Insert を押すと本文に <img src="/uploads/..."> が追加されます。当社では、Chromium 上で Vue 3.5.44 と Vite 8.3.5 を使い、まさにこの手順を実行しました。

ほかのプラットフォーム向けの同じガイド

React · Angular

次に読む