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
330
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
3
580
Recoilを剥がしている話
kirik
5
11k
Other Decks in Technology
See All in Technology
バッチ処理で悩むバックエンドエンジニアに捧げるAWS Glue入門
diggymo
3
200
roppongirb_20250911
igaiga
1
220
オブザーバビリティが広げる AIOps の世界 / The World of AIOps Expanded by Observability
aoto
PRO
0
360
CDK CLIで使ってたあの機能、CDK Toolkit Libraryではどうやるの?
smt7174
4
140
なぜスクラムはこうなったのか?歴史が教えてくれたこと/Shall we explore the roots of Scrum
sanogemaru
5
1.6k
「全員プロダクトマネージャー」を実現する、Cursorによる仕様検討の自動運転
applism118
21
10k
ChatGPTとPlantUML/Mermaidによるソフトウェア設計
gowhich501
1
130
20250903_1つのAWSアカウントに複数システムがある環境におけるアクセス制御をABACで実現.pdf
yhana
3
550
Android Audio: Beyond Winning On It
atsushieno
0
110
Obsidian応用活用術
onikun94
2
480
スマートファクトリーの第一歩 〜AWSマネージドサービスで 実現する予知保全と生成AI活用まで
ganota
2
210
Terraformで構築する セルフサービス型データプラットフォーム / terraform-self-service-data-platform
pei0804
1
170
Featured
See All Featured
Building Adaptive Systems
keathley
43
2.7k
GitHub's CSS Performance
jonrohan
1032
460k
Balancing Empowerment & Direction
lara
3
620
RailsConf 2023
tenderlove
30
1.2k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
What's in a price? How to price your products and services
michaelherold
246
12k
The Invisible Side of Design
smashingmag
301
51k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.5k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
36
2.5k
Building Applications with DynamoDB
mza
96
6.6k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.5k
KATA
mclloyd
32
14k
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で、