定義
文章を「見るだけ」でよい画面(プレビューカード、ブログ埋め込みなど)で、編集用の重いライブラリを一切ダウンロードさせないようにしたいときに使うパターンである。編集機能は不要なのに編集用コードが付いてくると、その画面が遅くなるからだ。
具体的には、文書パッケージから**編集用の入口(editor entry)と読み取り用の入口(viewer entry)**を分けてエクスポートする(入口 = 消費するアプリがimportする開始ファイル)。編集用にはリッチテキスト編集エンジン(例: ProseMirror)、ドラッグ&ドロップライブラリ(dnd-kit)、ツールバーのような編集UI(chrome)が入り、読み取り用にはそれが抜ける。この分離を、export一覧・CSS・import graph(どのファイルがどのファイルをimportするかの依存グラフ)の3つのレベルすべてで行わなければならない。
なぜ必要か
パッケージ1つがすべてを1つの入口からエクスポートすると(barrel export — 複数のモジュールをindexから一度にre-exportする方式)、プレビューカード・ブログ埋め込み・SSRプレビューまで編集機能全体を引きずってくる。画面に見えるDOMが読み取り専用のエディタと同じでも、import graphが実際に異なってこそtree-shaking(使わないコードをバンドルから自動的に取り除く最適化)が効き、エディタのコードが抜ける。
動作原理
@pkg/viewersubpath +viewer.css(chrome・toolbar layerを除外) — 消費者はviewer-onlyのアプリでeditor entryをimportしない。- viewer module graphでPM/dnd importが0件であることをlintまたはdependency-cruiserでCI検証する — dev HMRのtree-shake錯覚を防ぐためproduction analyzeが必須である。
- readOnly propでBlockRendererを共有する — DOMはeditorと同じで、import graphだけが異なってこそpeek・gallery・embedのLCP/TTIが下がる。
- publish manifestの
exports/filesにCSS entryを含める — 漏らすとnpm install後にunstyledなviewerがデプロイされる。
実務適用
- バンドルsize budgetのCI(viewer gzipの上限)。
- Storybook: ViewerOnly vs Editable side-by-side。
- peek・gallery・embedはviewer entryだけをimport(lint rule)。
トレードオフ
- dual entry維持のコスト vs peekのLCP/TTIの利得。
- tokenは共有、chromeだけ除外 — visual parityテストが必要。
使ってはいけないcase
- パッケージが常にfull editorだけを露出するなら、splitは不要。
よくある間違い
- dev HMRでのtree-shake錯覚 → production analyzeが必須。
- npm publishにCSSを漏らす → 消費者のunstyledなviewer。
関連概念
- [[webpack-splitchunks-vendor-shared-chunk]]
- [[css-cascade-layers-theming]]