Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Tiptapで実現する堅牢で柔軟なエディター開発
Search
kirik
July 22, 2025
Technology
1
88
Tiptapで実現する堅牢で柔軟なエディター開発
2025/7/22 に行われたエディター勉強会の発表資料
https://prtimes.connpass.com/event/358977/
kirik
July 22, 2025
Tweet
Share
More Decks by kirik
See All by kirik
Recoil脱却の現状と挑戦
kirik
2
330
Recoilを剥がしている話
kirik
5
10k
Other Decks in Technology
See All in Technology
OTel 公式ドキュメント翻訳 PJ から始めるコミュニティ活動/Community activities starting with the OTel official document translation project
msksgm
0
210
Data Engineering Study#30 LT資料
tetsuroito
1
550
AI工学特論: MLOps・継続的評価
asei
10
1.5k
Wasmで社内ツールを作って配布しよう
askua
0
110
東京海上日動におけるセキュアな開発プロセスの取り組み
miyabit
0
110
claude codeでPrompt Engineering
iori0311
0
430
The Madness of Multiple Gemini CLIs Developing Simultaneously with Jujutsu
gunta
1
2.5k
MCP とマネージド PaaS で実現する大規模 AI アプリケーションの高速開発
nahokoxxx
1
1.4k
FAST導入1年間のふりかえり〜現実を直視し、さらなる進化を求めて〜 / Review of the first year of FAST implementation
wooootack
1
120
増え続ける脆弱性に立ち向かう: 事前対策と優先度づけによる 持続可能な脆弱性管理 / Confronting the Rise of Vulnerabilities: Sustainable Management Through Proactive Measures and Prioritization
nttcom
1
140
SRE with AI:実践から学ぶ、運用課題解決と未来への展望
yoshiiryo1
1
680
PHPでResult型やってみよう
higaki_program
0
190
Featured
See All Featured
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.8k
Building an army of robots
kneath
306
45k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
181
54k
Making the Leap to Tech Lead
cromwellryan
134
9.4k
The Art of Programming - Codeland 2020
erikaheidi
54
13k
Music & Morning Musume
bryan
46
6.7k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
108
19k
Speed Design
sergeychernyshev
32
1k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
18
1k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
30
2.2k
Transcript
Tiptapで実現する堅牢で柔軟なエディター開発 2024/7/22 PR TIMES.DEV エディター勉強会 #1 株式会社PR TIMES 桐澤 康平
@kiririLee
ProseMirrorのスキーマを活用したHTMLの正規化 Reactコンポーネントの組み込みと プラグインによる機能拡張 Tiptap の Extension 単位での単体テスト 今日話すこと
ProseMirrorのスキーマを活用した HTMLの正規化
ProseMirror ?? Tiptapの話じゃないの?
TiptapはProseMirrorのラッパー ・ProseMirrorはTypeScriptで実装された WYSIWYGエディターライブラリ ・Tiptapはヘッドレスで React、Vue、Svelte などの モダンなUIフレームワークとの統合を実現している ・TiptapはProseMirrorの概念を一部抽象化しているが、 ProseMirrorの哲学は知っておく必要がある。スキーマもその一つ
Tiptapによる抽象化の例
ProseMirror Tiptap https://tiptap.dev/docs/editor/core-concepts/schema
エディタで最も基本的な機能である 段落機能(p要素)のスキーマ定義 👉 スキーマの定義をしないと段落すら 扱えない エディタで使用するHTMLは 全てスキーマを定義する TiptapはスキーマをExtensionという 概念で抽象化している Paragraph
Extension
contentで子要素に持てるコンテンツを指定 parseHTMLで読み込むHTMLを指定 renderHTMLで出力するHTMLを指定 重要な点 ・ content 指定に違反するコンテンツは破棄される (子要素のタグ消去) ・ 読み込んだHTMLと出力するHTMLを変えることができる
Extensionの概要
実務での適用例
CKEditor から Tiptap へリプレイス ・ jQuery+CKEditorのレガシー実装でバグ修正・機能追加が困難 ・ Tiptapへのリプレイス目的は機能追加であったため、 UIと機能を保ったまま PR
TIMESにおけるエディタの歴史
Tiptapでリプレイス後、リニューアル ・ UIのアップデートに加え、新機能が追加された PR TIMESにおけるエディターの歴史
一 jQuery+CKEditor のエディターを v1 として Tiptapでリプレイスしたエディターを v2 リニューアルにより新機能追加したエディターを v3 3つのエディタが出てきたので整理
Tiptapで2つのHTML構造を扱う必要性 V1 V2 V3 HTMLは同じ 機能アップデートにより HTMLが変わる jQuery+CKEditor Tiptap+React Tiptap+React
v1からv2へのリプレイス時のHTML v2からv3へのリニューアル時のHTML
CKEditor から Tiptap へのリプレイス 前提として、エディターから出力されるHTMLはそのままDBに保存され、 他システム用に加工される CKEditorのHTML メール用のコンテンツ RSS用のコンテンツ
ブラウザ用のコンテンツ
CKEditor から Tiptap へのリプレイス バックエンドの工数削減のためリプレイス後のTiptapから 出力されるHTMLは保つ必要がある CKEditorのHTML メール用のコンテンツ RSS用のコンテンツ
ブラウザ用のコンテンツ バックエンドの実装は変えない TiptapのHTML
CKEditorとTiptapの統合
CKEditorから出力された画像機能のHTML構造
https://prtimes.jp/main/html/rd/p/000001300.000000112.html 公開されたプレスリリースのHTML
https://prtimes.jp/main/html/rd/p/000001300.000000112.html 公開されたプレスリリースのHTML
公開されるプレスリリースのHTMLでは必要ない属性は 消されている ブラウザ用に加工されている
例えば、「data-nheight」属性はメールに必要な情報で欠落すると メールが壊れる。 このようにほかシステムとの依存関係を持った情報が HTMLにはたくさん埋め込まれている。 重要なのは、、、
このHTMLをどうやって読み込み、壊さずにそのまま出力するか?
まずは parseHTML で読み込み CSSセレクタで指定 priorityで 通常の段落機能(pタグ) 読み込みとの競合を 避ける
tagでパースした Elementの子要素が 全て参照できる getAttrs の働き pタグの子要素である imgタグの属性を 取得する
取得した属性は オブジェクトで return getAttrs の働き
そして renderHTML で出力 HTMLAttributesで パースした属性が 受け取れる 元々のHTMLと 辻褄を合わせるために 属性の値を加工する
画像機能で出力するHTML構造を 配列で定義 ちなみにTiptap v3からJSXで定義できる!! span、imgは他のExtensionで 読み込まれていない このExtensionでもパースしてないが 最終的な出力には含められる 細かいけど重要
CKEditor と Tiptap の統合は以上
リプレイス後、リニューアルによる新機能追加
リプレイス後、リニューアルによる新機能追加 ・ v2をβ版としてリリースし、v1を完全廃止してから リニューアルプロジェクト開始 ・ 画像機能に大幅なアップデート ・ メールなど他システムで扱いやすいようにこのタイミングで 画像機能で出力するHTML構造も大幅に変更
画像機能に大幅なアップデート 1種類のみだった画像機能が 7種類に増えた 画像機能で出力するHTMLも 大幅に変える CKEditorで出力していた HTMLとの統合が必要 https://prtimes.jp/main/html/rd/p/000001357.000000112.html
v2とv3で画像機能を統合する ・ v1 から v2 は機能差がなくHTMLを保つだけで廃止が完了 ・ v2 から v3
は機能差があったため、v2 と v3 を同時に 運用する並行期間を設けていた ・ お客様は v2 から v3 へと編集中のプレスリリースを 切り替えることができる ・ よって、画像機能を例にすると v2 の画像機能のHTML構造をv3で 読み込んだ時に v3 の大画像もしくは中画像機能のHTML構造に する必要がある ※ 現在、v2エディターは廃止されており、v3のみ利用可能
v3 の大画像と中画像 大画像 中画像
大画像のHTML構造 ※ 例として src の URL は placeholder を指定
中画像のHTML構造 figure class 属性の --large を --medium に切り替えて区別 ※ 例として
src の URL は placeholder を指定
再掲: v2の画像機能のHTML構造
v2 -> v3 でもスキーマを活用しよう
まずは parseHTML を定義 配列で読み込むHTMLを 複数指定できる v3で出力した画像と v2で出力した画像をどちらも指定 getFigureNodeAttrsFromV2で v3用に属性を統合する
getFigureNodeAttrsFromV2 v2の画像機能で お馴染みの data-nheight属性 などを取得
getFigureNodeAttrsFromV2 v2で取得した属性値によって v3の大画像にするか中画像に するかを決定する --large or --medium を 切り替える
そして renderHTML する このExtensionではfigureの パースのみ行なっている img 要素のパースは他の Extensionで行なっている 0 は
hole といって子要素のパースを 他のExtensionに委ねられる 重要
小要素の img のパースとレンダー
画像機能は全部で4つのExtensionを 組み合わせている div, figure, img, figcaption の4つ ※ 例として src
の URL は placeholder を指定
・ Extensionはそれぞれの単一のタグのみパースしている ・ HTML構造を強制したい! div.pr_img の小要素のみで必ず figure.pr-img__item-large が存在するようにしたい ・ メールなど他システムでも決まったHTML構造を期待しているため
この↓HTML構造を強制したい
スキーマをより堅牢にする
Extensionの Content と Group が役に立つ Content 子要素に持つことができる HTML(Extension)を 定義できる Group
自分がどのExtensionに所属 するかを定義できる
大画像で扱う Extension の Content と Group を定義する
・ SingleImageExtensionは block要素の内側にしか 存在できない ・ 子要素に pr_figure の Extension しか存在
させない (blockの指定は結構広め、トップレベルのDocExtension配下に存在できる 一番外側の div 要素
・ pr_single_image の内側に しか存在できない ・ 子要素に pr_figure_imageと pr_figure_captionしか 存在させない div
要素の子要素である figure 要素
・ pr_figure の内側にしか存在 できない figure 要素の子要素であるimg要素
・ pr_figure の内側にしか存在 できない ・ (Tips) inline* 指定と hole 指定でカーソル入力を
設置できる figure 要素の子要素である figcaption 要素
各Extensionの定義によってHTML構造を強制できる!!
Reactコンポーネントの組み込みと プラグインによる機能拡張
ブログ書きました 📝 プレスリリースのエディターでTiptapを 使って新機能開発をした話 https://developers.prtimes.jp/2024/09/05/developing-new-features-in-editor-using-tiptap-react-typescript/ 詳しくはWEBで、
Tiptap の Extension 単位での単体テスト
ブログ書きました 📝 Tiptapエディターのテスト戦略:Playwright、Vitest Browser Mode、Editorインスタンスを用いたテスト https://developers.prtimes.jp/2025/02/20/press-release-editor-frontend-testing-tips/ 詳しくはWEBで、