wysiwygエディタのライブラリを調査・選定してみた

# 背景
今後開発していくプロダクト内ではエディタを組み込む必要があり、その中では文字色変更やルビを振るなどリッチなテキストエディタとしての要件があります。
リッチなテキストエディタを実現する方法の一つとしてmarkdownが挙げられます。
これはエンジニアには馴染みがありますが、一般ユーザーが記法を覚えるにはハードルが高く利用を諦めてしまう恐れがありました。
そこで、より直感的に操作することができるwysiwygを取り入れることとなりました。

# wysiwyg（ウィジウィグ）とは
コンピュータのUIに関する用語で、ディスプレイの表示と処理内容が一致するように表現する技術のことを指し、
What You See Is What You Get（見たままが得られる）の頭文字を取ったもの。


# 前提となる使用技術
- Typescript
- React（またはVue）

基本的にはReactでの実装を前提に調査を進めました。

# 結論
### Quillに決定
https://github.com/quilljs/quill

https://quilljs.com/

# 選定理由
以下のような理由で決定しました。
一つずつ説明していきます。

- クロスプラットフォーム対応である
- 利用者数が多く評価も高い
- 情報が多く調査しやすい
- ドキュメントが充実している

### クロスプラットフォーム対応

> Quillの場合、単に動くというだけでなく、同じように動いたり、動作したりすることが重要です。機能性だけでなく、ユーザーや開発者の体験もクロスプラットフォームで考慮されます。あるコンテンツがOSX上のChromeで特定のマークアップを生成する場合、IEでも同じマークアップを生成します。Windows版Firefoxでエンターキーを押したときに太字の書式が保持されるなら、モバイル版Safariでも保持されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475547/636eec68-247d-1f1a-54dd-5e225b7de322.png" width=600px>


### 利用者数が多く評価も高い、情報が多く調査しやすい
他の主要ライブラリと比較した際、スター数やダウンロード数が最も多いことが分かります。
これは情報の多さにも繋がっており、実際に触ってみて直面する不明点の多くがgithubのissueに上がっていたりと、
調査がしやすく感じました。
また、情報が多いため調査時間が短く済み、スムーズに開発が進められそうな印象がありました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475547/e4f3e8f0-1aa9-6200-7c86-9ef0962f03bc.png" width=600px>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475547/27147697-8b6d-bd74-f33b-6b8ebf09506e.png" width=600px>

### ドキュメントが充実している
他のライブラリに比べてドキュメントが非常に親切でした。
サンプルコードやAPIが詳細に説明されており、調査したライブラリの中で、処理内容への理解度が最も高まったように思います。
また、ドキュメントの充実度は学習コストの低下につながり、今後別のメンバーが参入する際も着手しやすいでしょう。

https://quilljs.com/docs/quickstart/

### 最小構成
```
npm install quill@1.3.6
npm install --save @types/quill
```

```editor.tsx
import Quill from "quill";
import  "quill/dist/quill.snow.css";

setTimeout(() => {
  let quill = new Quill('#editor', {
    modules: { toolbar: true },
    theme: 'snow'
  });
}, 10);

export default () => {
  return (
    <div id="quill">
      <div id="editor" />
    </div>
  );
};
```
#### なぜsetTimeoutを使用しているのか

使用しない場合は、quill.js:1987 quill Invalid Quill container #editorというエラーが発生するからです。
下の回答によるとその理由は、QuillがDOMコンテナがレンダリングされる前に呼び出されていることのようです。new Quillを遅らせるために、 setTimeoutを使用しています。

https://stackoverflow.com/questions/49274106/quill-error-quill-invalid-quill-container


# 他の比較対象
他に触ってみたライブラリを以下に紹介します。

## Editor.js
### 特徴

> ブロックスタイルのエディター。重いHTMLマークアップの代わりに、**きれいなデータ**をJSONで出力する。また、API拡張やプラグイン導入が可能なように設計されている。
Editor.jsのワークスペースは、段落、見出し、画像、リスト、引用などのブロックに分かれている。それぞれは独立したコンテンツ化可能な要素（またはより複雑な構造）で、プラグインによって提供され、Editor's Coreによって統合されている。
同時に、矢印ナビゲーション、コピー＆ペースト、クロスブロック選択などの便利な機能は、使い慣れたエディタとほぼ同じように動作する。

https://editorjs.io/base-concepts

### 最小構成
```
npm i @editorjs/editorjs --save
```

```editor.tsx
import EditorJS from '@editorjs/editorjs';

const Editor = () => {
  const editor = new EditorJS();
  return (
    <>
      <h1>Editor.js sample</h1>
      <div className='editor-container'>
        <div id="editorjs"></div>
      </div>
    </>
  );
}

export default Editor;
```

## Draft.js
### 特徴
> Draft.js は facebook が開発している React のエディターライブラリです。クロスブラウザ対応しており、EditorState と呼ばれるステートで、エディターの状態を抽象化しています。内部では immutable.js を使用しており、immutable の特性を生かして、履歴機能などを実現しているのが特徴です。

https://draftjs.org/

Editor Reactコンポーネントは、制御されたContentEditableコンポーネントとして構築されています。
制御された入力は2つの主要な部分を含んでいます。
- 入力の状態を表す値
- 入力の状態を表す値、入力の更新を受け取る onChange プロップ関数

このアプローチにより、入力を構成するコンポーネントが入力の状態を厳密に制御することができ、同時に DOM を更新してユーザが書いたテキストに関する情報を提供することができます。
ContentEditable要素には、そのようなonChangeイベントがないため、不変なEditorStateオブジェクトがスタックされることでコンテンツ、カーソル、アンドゥ/リドゥ履歴などの様々な変更が保持されます。

### 最小構成
```
npm install draft-js react react-dom
```

```editor.tsx
import React from 'react';
import {Editor, EditorState} from 'draft-js';
import 'draft-js/dist/Draft.css';

function MyEditor() {
  const [editorState, setEditorState] = React.useState(
    () => EditorState.createEmpty(),
  );

  return (
		<Editor editorState={editorState} onChange={setEditorState} />
	);
}

export default MyEditor;
```
下の記事の説明が非常に分かりやすかったです。

https://qiita.com/YudaiTsukamoto/items/264f333e90a1edb818a3#editorstate

## Slate.js
### 特徴
> Slateは現在ベータ版です。そのコアAPIは現在使用可能ですが、高度なユースケースのために修正をプルリクエストする必要があるかもしれません。いくつかのAPIは「最終版」ではなく、私たちがより良い解決策を見つけるにつれて（壊れて）変化していくでしょう。

https://docs.slatejs.org/

### 最小構成

```
npm install --save slate slate-react
```

```editor.tsx
// Import React dependencies.
import React, { useState } from 'react'
// Import the Slate editor factory.
import { createEditor } from 'slate'
// Import the Slate components and React plugin.
import { Slate, Editable, withReact } from 'slate-react'
import { CustomElement } from '../custom-types';

const initialValue: CustomElement[] = [
  {
    type: 'paragraph',
    children: [{ text: 'A line of text in a paragraph.' }],
  },
];

export default () => {
  // Create a Slate editor object that won't change across renders.
  const [editor] = useState(() => withReact(createEditor()))
  return (
    <>
      <Slate editor={editor} value={initialValue}>
        <Editable />
      </Slate>
    </>
  );
};
```

```custom-types.d.ts
// TypeScript users only add this code
import { BaseEditor, Descendant } from 'slate'
import { ReactEditor } from 'slate-react'

type CustomElement = { type: 'paragraph'; children: CustomText[] }
type CustomText = { text: string }

declare module 'slate' {
  interface CustomTypes {
    Editor: BaseEditor & ReactEditor
    Element: CustomElement
    Text: CustomText
  }
}
```

## tui.editor
本ライブラリにはReact版もありましたが、ドキュメントが不十分な部分があったため、通常版を利用する前提で調査しました。
### 特徴
定期的にメンテナンスされており、リリースも1ヶ月に２回前後くらいされていました。
ソースがほぼTypescriptで書かれており、React、Vue用コンポーネントも公式のリポジトリ内にあります。

Markdownモードとwysiwygモードがあり、簡単に切り替えが可能。ブラウザ、スクリーンショット、エクセル、パワーポイントなどから何でも貼り付けることができるそうです。
Markdownモードの方が推されている印象はありますが、wysiwygモードも十分使えそうです。
ただし、スクラッチで開発した機能を追加することやReactとの相性が悪く使用を断念しました。


https://nhn.github.io/tui.editor/latest/

### 最小構成

```
npm install --save @toast-ui/editor
```

```editor.tsx
import Editor from '@toast-ui/editor';
import '@toast-ui/editor/dist/toastui-editor.css'; 

setTimeout(() => {
  const editor = new Editor({
    el: document.querySelector('#editor')!,
    height: '500px',
    initialEditType: 'wysiwyg'
  });
}, 1);

export default () => {
  return (
    <div id="editor"></div>
  );
};
```
setTimeoutしているのは、editorのelがnullになってしまうのを防ぐためです。

<div id="editor"></div>がレンダリングされる前にquerySelector('#editor')が実行されてしまうとnullになり、エラーが発生し画面が真っ白になります。（おそらくQuillと同様の理由）

# まとめ
今回は新規プロダクトの~~最も重い~~核となる部分のライブラリ調査を行いました。
この記事がこれからwysiwygを扱う方々の助けとなれば幸いです！