BurgerEditor v4は、再利用性とプラットフォーム非依存性を重視したモノレポ構成を採用しています。
graph TD
utils["@burger-editor/utils<br/>(共通ユーティリティ)"]
frozen["@burger-editor/frozen-patty<br/>(HTML⇄JSONデータ変換)"]
core["@burger-editor/core<br/>(エディタエンジン)"]
blocks["@burger-editor/blocks<br/>(標準ブロック定義)"]
client["@burger-editor/client<br/>(Svelte UI)"]
custom["@burger-editor/custom-element<br/>(TipTap Web Components)"]
migrator["@burger-editor/migrator<br/>(バージョン移行)"]
inspector["@burger-editor/inspector<br/>(HTML検査・検索)"]
local["@burger-editor/local<br/>(ローカルファイルシステム CMS)"]
mcp["@burger-editor/mcp-server<br/>(AI統合サーバー)"]
legacy["@burger-editor/legacy<br/>(v3互換性サポート)"]
css["@burger-editor/css<br/>(blocks全CSS統合配布)"]
runtime["@burger-editor/runtime<br/>(ブラウザ用ランタイム)"]
%% Core dependencies
utils --> frozen
frozen --> core
utils --> core
%% Block dependencies
core --> blocks
utils --> blocks
%% CSS distribution
blocks --> css
%% Client dependencies
core --> client
custom --> client
migrator --> client
utils --> client
%% Migrator dependencies
blocks --> migrator
core --> migrator
legacy --> migrator
utils --> migrator
%% Inspector dependencies
core --> inspector
%% Local dependencies
inspector --> local
blocks --> local
%% MCP Server dependencies
core --> mcp
legacy --> mcp
migrator --> mcp
utils --> mcp
%% Independent packages
legacy
runtime
@burger-editor/utils
- 共通ユーティリティ関数
- 依存関係: dayjs, marked, turndown
- 責任: 日付処理、マークダウン変換等の汎用機能
@burger-editor/frozen-patty
- HTMLとJSONデータの相互変換ライブラリ
- 依存関係: utils
- 責任: HTMLからのデータ抽出、JSONからHTMLへの適用、XSS対策
- 特徴: テンプレートエンジン不要、
data-field属性ベースのマッピング
@burger-editor/core
- エディタエンジンの中核実装
- 依存関係: frozen-patty, utils, jaco, semver
- 責任: ブロック管理、編集機能、イベント処理
- プラットフォーム非依存: どのCMSでも利用可能
@burger-editor/blocks
- 標準ブロックとアイテムの定義
- 依存関係: core, utils
- 責任: HTMLテンプレート、ブロック仕様、デフォルトカタログ
@burger-editor/client
- Svelteベースのクライアント側UI
- 依存関係: core, custom-element, migrator, utils
- 責任: ブロック選択UI、ファイル管理UI、エディタUI
@burger-editor/custom-element
- TipTap統合のWeb Components
- 依存関係: @tiptap/* packages
- 責任: WYSIWYG編集機能
@burger-editor/inspector
- HTML検査・検索ユーティリティ
- 依存関係: core, jsdom
- 責任: HTML解析、CSS変数検索、jsdom互換性サポート
- プラットフォーム非依存: Node.js環境で動作
- 主要機能:
- CSS変数検索(シンプル、ワイルドカード、OR、AND検索)
- jsdom要素のブラウザAPI互換化
- DOM解析ユーティリティ
- jsdom互換性:
- jsdomの
CSSStyleDeclarationはiterableではないため、Proxyを使用してブラウザAPI互換にする proxyJsdomElementForIterableStyle関数でel.styleをiterableにラップ- coreの
exportStyleOptionsをそのまま再利用可能
- jsdomの
- 将来の拡張:
- ブロック構造検索
- アイテム検索
- コンテンツ検索
- 依存関係分析
@burger-editor/local
- ローカルファイルシステム向けCMS実装
- 依存関係: inspector, Hono, Node.js関連パッケージ
- 責任: ローカルサーバー、ファイルIO、設定管理、CLI機能、プログラマティックAPI
- 環境固有: ローカルファイルシステム専用
- CLI機能:
bge- 開発サーバー起動bge search- HTML内のCSS変数検索(@burger-editor/inspectorを使用)
- プログラマティックAPI:
- ファイルアップロード機能をプログラムから利用可能
- Honoサーバーと同じロジックを共有
EncodedFileName型で誤ったファイル名を防止- エクスポート:
@burger-editor/local/get-candidate-name- ファイル名候補生成@burger-editor/local/upload- ファイルアップロード
- 内部構造:
helpers/scan-directory.ts- ファイルスキャン共通ロジック(EXCLUDE_FILE_NAMES定義)helpers/get-max-file-id.ts- 最大ファイルID取得helpers/get-candidate-name.ts- 候補ファイル名生成(EncodedFileName型エクスポート)helpers/upload.ts- ファイルアップロード実装model/FileListManager- 上記helpers関数を使用してアップロード処理を実装
@burger-editor/migrator
- バージョン間移行機能
- 依存関係: blocks, core, legacy, utils
@burger-editor/mcp-server
- MCP (Model Context Protocol) サーバー実装
- 依存関係: core, legacy, migrator, utils
- 責任: AIアシスタント(Claude等)にBurgerEditor機能を提供
- 機能: ブロック作成、パラメータ取得、v3互換性サポート
@burger-editor/legacy
- v3互換性サポート
- 依存関係: なし
@burger-editor/css
- blocksの全CSSファイル(general.css + 各アイテムのstyle.css)を統合配布
- 依存関係: blocks(ビルド時)
- 責任: blocksのスタイルを単独で利用可能にする配布パッケージ
@burger-editor/runtime
- BurgerEditorで生成されたコンテンツをブラウザで動作させるためのランタイムライブラリ
- 依存関係: なし(独立パッケージ)
- 責任: ブラウザ側のインタラクティブ機能の提供
- プラットフォーム非依存: どのCMSで生成されたコンテンツでも利用可能
- 主要機能:
- 画像モーダル表示(Invoker Commands API使用)
- 将来的な拡張機能の基盤
各レイヤーは明確な責任を持ち、上位レイヤーのみが下位レイヤーに依存します:
- Platform Layer: 特定環境への統合機能
- UI Layer: ユーザーインターフェース
- Content Layer: コンテンツ構造定義
- Core Layer: プラットフォーム非依存のエンジン
Core Layerは特定のプラットフォームに依存しない設計により、WordPress、MovableType、その他のCMSで再利用可能です。
新機能を実装する際の配置判断:
Core Layerに配置する機能:
- 全プラットフォームで共通して必要な機能
- エディタの基本動作に関わる機能
- 例: ブロック管理、編集状態管理、イベント処理
UI Layerに配置する機能:
- UIフレームワーク固有の実装(Svelte コンポーネント等)
- core が定義するファクトリインタフェースの具象実装
- 例: ブロックメニュー、初期挿入ボタン、ダイアログシェル、編集エリアシェル
Platform Layerに配置する機能:
- 特定環境に依存する機能
- 環境固有の設定や統合機能
- 例: ファイルシステム操作、サーバー設定、環境固有API
core パッケージは UI コンポーネントの生成をファクトリインタフェースとして定義し、client パッケージが Svelte ベースの具象実装を注入します。これにより、core は UI フレームワークに依存せず、UI の差し替えが可能です。
ファクトリインタフェース:
BlockMenuCreator— ブロックメニューUI生成InitialInsertionButtonCreator— 初期挿入ボタンUI生成EditorDialogShellCreator— ダイアログシェル生成EditableAreaShellCreator— 編集エリアシェル生成
依存関係の流れ:
core(インタフェース定義) ← client(Svelte 実装を注入)
BurgerEditorEngineOptions の blockMenu, initialInsertionButton, dialogShell, editableAreaShell プロパティを通じて注入されます。
client パッケージは Svelte 5 ルーン($state, $derived, $effect, $props())を使用してリアクティブな UI を構築しています。
EngineState ブリッジ層:
core の ComponentObserver が発行するイベントを Svelte 5 の $state に変換する橋渡し層です。これにより、core のイベントベースの状態管理と Svelte のリアクティブシステムを統合しています。
Invoker Commands API:
ダイアログの開閉制御に HTML の command/commandfor 属性(Invoker Commands API)を使用しています。
テストは vitest を使用し、パッケージごとに適切な実行環境を使い分けます。
| プロジェクト | 実行環境 | 対象パッケージ |
|---|---|---|
| core | Playwright Chromium(ブラウザ) | core, blocks, custom-element |
| client | jsdom | client |
- core プロジェクト: iframe の
contentWindow等、実ブラウザ API が必要なテストはブラウザ環境で実行 - client プロジェクト: Svelte コンポーネントのテストは jsdom 環境で実行
- 全パッケージが協調してリリース
- 互換性の保証
- core → blocks → client の段階的機能統合
- 依存関係の明確化
- localパッケージと同様の構造で他プラットフォーム対応可能
- 共通機能の重複実装を回避
BurgerEditorでは、将来のAPIが確定していない機能をexperimentalプロパティ配下で提供する設計パターンを採用しています。
- オプトイン方式: 実験的機能はデフォルトで無効であり、明示的な設定により有効化
- 後方互換性の保持: 実験的機能が無効の場合、既存の動作を完全に維持
- APIの柔軟性: 実験的機能のAPIは将来のバージョンで変更される可能性がある
- 段階的安定化: 実験的機能が成熟した場合、通常のAPIとして昇格
// Config型の実験的機能部分
{
experimental?: {
itemOptions?: {
[itemName: string]: {
// アイテム固有の実験的オプション
};
};
};
}WYSIWYGエディタのテキスト編集モード機能は実験的機能として実装されています。
設定フロー:
- ユーザーが
Config.experimental.itemOptions.wysiwyg.enableTextOnlyModeを設定 @burger-editor/coreのBurgerEditorEngineが設定を保持defineCustomElementコールバックでexperimental設定を渡す@burger-editor/clientがdefineBgeWysiwygEditorElementに転送@burger-editor/custom-elementがUI動作を制御
実装ファイル:
packages/@burger-editor/core/src/types.ts- Config型定義packages/@burger-editor/core/src/engine/engine.ts- 設定の伝搬packages/@burger-editor/client/src/index.ts- カスタム要素への転送packages/@burger-editor/custom-element/src/bge-wysiwyg-editor-element/index.ts- UI制御
新しい実験的機能を追加する際は、以下の手順に従ってください:
- Config型の拡張:
@burger-editor/core/src/types.tsのConfig.experimentalに追加 - 設定の伝搬: 必要に応じて
defineCustomElementコールバックで設定を渡す - デフォルト動作の保証: 実験的機能が無効の場合、既存動作を維持するテストを追加
- ドキュメント更新: 以下のファイルに実験的機能として明記
- 影響するパッケージのREADME
@burger-editor/core/README.mdの「設定 (Config)」セクション- このARCHITECTURE.mdファイル
@burger-editor/custom-elementパッケージにTiptap拡張機能を追加する際のガイドラインです。
Tiptapには2種類の拡張タイプがあります:
- 用途: テキストレベルの装飾やフォーマット
- 特徴:
- インライン要素(
<strong>,<em>,<sup>,<sub>など) - 複数のMarkを同時に適用可能(例:太字+斜体)
- テキストに対して適用される
- インライン要素(
- 例: bold, italic, underline, strikethrough, subscript, superscript, link
- 用途: ブロックレベルの構造や要素
- 特徴:
- ブロック要素(
<p>,<h1>,<div>,<blockquote>など) - 属性を持つことができる
- 階層構造を持つ
- ブロック要素(
- 例: paragraph, heading, blockquote, bulletList, orderedList
Tiptap公式拡張がある場合は、それを使用します。
メリット:
- 信頼性が高い
- メンテナンスされている
- エッジケースが考慮されている
- 相互排他性などの複雑な動作が実装済み
実装例(subscript/superscript):
// 1. 依存関係追加
// package.json
{
"dependencies": {
"@tiptap/extension-subscript": "^3.0.0",
"@tiptap/extension-superscript": "^3.0.0"
}
}
// 2. インポートして登録
// src/tiptap-extentions/index.ts
import Subscript from '@tiptap/extension-subscript';
import Superscript from '@tiptap/extension-superscript';
export const BgeWysiwygEditorKit = Extension.create({
name: 'bge-wysiwyg-editor-kit',
addExtensions() {
return [
Subscript,
Superscript,
// ...
];
},
});独自の属性や動作が必要な場合は、カスタム拡張を実装します。
実装例(ParagraphWithAlign):
// src/tiptap-extentions/paragraph-with-align.ts
import Paragraph from '@tiptap/extension-paragraph';
declare module '@tiptap/core' {
interface Commands<ReturnType> {
paragraphWithAlign: {
setAlign: (alignment: ParagraphAlignment) => ReturnType;
unsetAlign: () => ReturnType;
toggleAlign: (alignment: ParagraphAlignment) => ReturnType;
};
}
}
export type ParagraphAlignment = 'start' | 'center' | 'end';
export const ParagraphWithAlign = Paragraph.extend({
name: 'paragraph', // 既存のParagraphを上書き
addAttributes() {
return {
...this.parent?.(), // 親の属性を継承
'data-bgc-align': {
default: null,
parseHTML: (element) => {
const align = element.dataset.bgcAlign;
// バリデーション: 不正な値はnullに
if (align && ['start', 'center', 'end'].includes(align)) {
return align;
}
return null;
},
renderHTML: (attributes) => {
if (!attributes['data-bgc-align']) {
return {}; // 属性なしの場合はHTMLに出力しない
}
return {
'data-bgc-align': attributes['data-bgc-align'],
};
},
},
};
},
addCommands() {
return {
setAlign:
(alignment) =>
({ commands }) => {
return commands.updateAttributes('paragraph', {
'data-bgc-align': alignment,
});
},
unsetAlign:
() =>
({ commands }) => {
return commands.resetAttributes('paragraph', 'data-bgc-align');
},
toggleAlign:
(alignment) =>
({ commands, editor }) => {
// トグル動作: 同じalignmentなら解除、異なればset
if (editor.isActive('paragraph', { 'data-bgc-align': alignment })) {
return commands.unsetAlign();
}
return commands.setAlign(alignment);
},
};
},
});新しいTiptap拡張を追加する際は、以下の手順に従ってください:
-
packages/@burger-editor/custom-element/package.jsonに依存関係を追加 -
yarn installを実行
- 公式拡張の場合:
src/tiptap-extentions/index.tsでインポート - カスタム拡張の場合:
src/tiptap-extentions/に新規ファイル作成 - カスタム拡張の場合: TypeScript型定義を追加(
declare module '@tiptap/core') -
BgeWysiwygEditorKitのaddExtensions()に追加
-
src/bge-wysiwyg-element/types.tsのEditorNode型に追加
type EditorNode =
| 'bold'
| 'subscript' // 追加例
| 'superscript' // 追加例
| 'alignStart'; // 追加例
// ...-
src/bge-wysiwyg-element/index.tsにメソッドを追加
toggleSubscript() {
this.editor.chain().focus().toggleSubscript().run();
}-
#transaction()メソッドにステート情報を追加
subscript: {
disabled: !editor.can().chain().focus().toggleSubscript().run(),
active: editor.isActive('subscript'),
},-
src/bge-wysiwyg-editor-element/index.tsのアイコンをインポート
import IconSubscript from '@tabler/icons/outline/subscript.svg?raw';-
static defaultCommands配列にコマンド名を追加
static defaultCommands = [
'bold',
'subscript', // 追加
// ...
] as const;- テンプレート内にボタンHTMLを追加
${commands.includes('subscript') ?
`<button type="button" data-bge-toolbar-button="subscript">${IconSubscript}</button>`
: ''}-
bindToggle()関数にハンドラを追加
case 'subscript': {
wysiwygElement.toggleSubscript();
break;
}-
updateButtonState()関数にステート更新を追加
case 'subscript': {
button.disabled = state.subscript.disabled;
button.ariaPressed = state.subscript.active ? 'true' : 'false';
break;
}-
src/bge-wysiwyg-element/index.spec.tsに以下のテストを追加:- 要素が保持されるか(
expectHTMLテスト) - 属性が保持されるか(カスタム属性の場合)
- 不正な値が適切に処理されるか(カスタム属性の場合)
- HTMLモードとWysiwygモードの切り替えが可能か
- 構造変更として検出されないか(
hasStructureChangeテスト)
- 要素が保持されるか(
テスト例:
test('expectHTML preserves <sup> elements correctly', () => {
document.body.innerHTML = '<bge-wysiwyg><p>x<sup>2</sup></p></bge-wysiwyg>';
const element = document.querySelector('bge-wysiwyg') as BgeWysiwygElement;
const originalHTML = '<p>x<sup>2</sup></p>';
const expectedHTML = element.expectHTML(originalHTML);
expect(expectedHTML).toBe('<p>x<sup>2</sup></p>');
});
test('expectHTML preserves data-bgc-align attribute', () => {
document.body.innerHTML =
'<bge-wysiwyg><p data-bgc-align="center">Text</p></bge-wysiwyg>';
const element = document.querySelector('bge-wysiwyg') as BgeWysiwygElement;
const originalHTML = '<p data-bgc-align="center">Text</p>';
const expectedHTML = element.expectHTML(originalHTML);
expect(expectedHTML).toBe('<p data-bgc-align="center">Text</p>');
});-
packages/@burger-editor/custom-element/README.mdの「使用可能なコマンド」セクションに追加 - 必要に応じて依存関係リストを更新
yarn lint # コードの静的解析
yarn build # ビルド確認
yarn test # テスト実行原因: defaultCommands配列への追加漏れ
解決方法: src/bge-wysiwyg-editor-element/index.tsのstatic defaultCommandsに必ずコマンド名を追加する
原因: parseHTMLとrenderHTMLの実装漏れ
解決方法:
parseHTML: DOM要素から属性を読み取るrenderHTML: 属性をHTML出力に含める- nullの場合は空オブジェクト
{}を返す(属性なしで出力)
原因: バリデーション不足
解決方法: parseHTML内で値を検証し、不正な値はnullを返す
parseHTML: (element) => {
const value = element.getAttribute('data-custom');
if (value && ['valid1', 'valid2'].includes(value)) {
return value;
}
return null; // 不正な値は削除
},原因: StarterKitのParagraphが優先されている
解決方法: カスタムParagraph拡張をBgeWysiwygEditorKitでロードする(StarterKitより後に読み込まれるため上書きされる)
原因: Tiptapが要素を認識できず、再構築している
解決方法:
- 拡張機能が正しく登録されているか確認
parseHTMLとrenderHTMLの実装を確認- テストで
hasStructureChangeをチェック
原因: 対応するCSSスタイルの追加漏れ
解決方法:
カスタム属性(特に見た目に影響するもの)を追加した場合、対応するCSSを@burger-editor/blocksパッケージに追加する必要があります。
-
general.cssへの追加 - Wysiwyg内で使用する属性の場合
/* packages/@burger-editor/blocks/src/general.css */ :where([data-bgc-align]) { &[data-bgc-align='start'] { text-align: start; } &[data-bgc-align='center'] { text-align: center; } &[data-bgc-align='end'] { text-align: end; } }
-
アイテム固有のstyle.cssへの追加 - 特定のアイテムでのみ使用する属性の場合
各アイテムのディレクトリ内の
style.cssに追加します
注意: @burger-editor/cssパッケージは@burger-editor/blocksのCSSを自動的に統合するため、blocksパッケージにスタイルを追加すれば、cssパッケージにも自動的に反映されます。
const editor = document.querySelector('bge-wysiwyg') as BgeWysiwygElement;
editor.addEventListener('transaction', (event: CustomEvent) => {
console.log('Transaction state:', event.detail.state);
});const editor = document.querySelector('bge-wysiwyg') as BgeWysiwygElement;
console.log('Active marks:', editor.editor.state.storedMarks);
console.log('Current node:', editor.editor.state.selection.$from.parent);const editor = document.querySelector('bge-wysiwyg') as BgeWysiwygElement;
console.log('Output HTML:', editor.editor.getHTML());実際の実装例として、subscript, superscript, paragraph alignment機能の実装を参照してください:
- 機能: テキストの上付き・下付き文字、段落整列
- 実装ファイル:
src/tiptap-extentions/paragraph-with-align.ts- カスタム拡張src/tiptap-extentions/index.ts- 統合src/bge-wysiwyg-element/index.ts- メソッド・ステートsrc/bge-wysiwyg-editor-element/index.ts- ツールバーsrc/bge-wysiwyg-element/index.spec.ts- テスト
この実装は本ガイドのベストプラクティスに従っており、参考になります。
以下の項目について確認が必要です:
-
モノレポ構成の選択理由
- 技術的制約や設計思想の詳細
-
将来のプラットフォーム拡張計画
- WordPress、MovableType等の具体的な対応予定
-
レイヤー間の厳密な境界定義
- インターフェース設計の詳細ルール