生成された UI にカスタム Vue コンポーネントを安全に再生成と共存させる
生成された UI を壊さずにカスタム Vue コンポーネントを追加する方法。隔離パターン、境界ルール、簡単な受け渡しで再生成に強い設計を学びます。

生成された UI を直接編集すると何が壊れるのか
生成された UI は再生成を前提に作られています。AppMaster のようなプラットフォームでは、Vue3 のウェブアプリコードがビジュアルビルダーから生成されます。再生成は画面やロジック、データモデルの整合性を保つ方法です。
問題は単純です:生成されたファイルを直接編集すると、次の再生成であなたの変更が上書きされる可能性があります。
だからチームはカスタムコードを使いたくなります。組み込みの UI ブロックは標準的なフォームやテーブルをカバーしますが、実際のアプリでは複雑なチャート、地図のピッカー、リッチテキストエディタ、署名パッド、ドラッグ&ドロップのプランナーといった特殊な要素が必要になることがよくあります。これらはカスタム Vue コンポーネントを追加する良い理由ですが、重要なのは "拡張" として扱い、直接編集しないことです。
カスタムコードが生成コードと混ざると、問題はあとになってわかりにくく現れます。次の UI 変更で再生成が走るまで気づかないかもしれませんし、別のチームメンバーがビジュアルエディタで画面を調整したときに初めて問題になることもあります。よくある問題は:
- テンプレートが再生成されてカスタムのマークアップが消える。
- ファイル名や構成の変更で import や登録が壊れる。
- 「ちょっとした修正」がデプロイごとにマージコンフリクトになる。
- 生成ロジックとカスタムロジックが乖離して端のケースで失敗し始める。
- 何が置き換わるかわからずアップグレードが怖くなる。
目的はカスタマイズを避けることではなく、再生成を予測可能にすることです。生成された画面とカスタムウィジェットの間に明確な境界を保てば、再生成はストレスではなく日常作業になります。
再生成を安全にする境界ルール
作業を失わずにカスタマイズするには、次のルールに従ってください:生成されたファイルを編集しないこと。これらはコンパイル済みの成果物のように読み取り専用と扱います。
UI を二つのゾーンとして考えます:
- 生成ゾーン:ジェネレータによって作られるページ、レイアウト、画面。
- カスタムゾーン:あなたが手で書く Vue コンポーネントを置く別のフォルダ。
生成された UI がカスタムコンポーネントを利用するべきであって、コンポーネント自体をそこに作るべきではありません。
長期的にうまくいかせるには、"境界" を小さく明確に保ちます。カスタムウィジェットは小さな製品のように振る舞い、契約を持つべきです:
- Props in:表示に必要なものだけ。
- Events out:ページが反応するために必要なものだけ。
ウィジェット内からグローバルステートを操作したり、関連のない API を呼ぶことは、明示的に契約の一部でない限り避けてください。
AppMaster スタイルの生成された Vue3 画面では、多くの場合、生成された画面側では props を渡しイベントを受け取るための最小限の配線だけを行います。その配線は再生成で変わることがありますが、小さく簡単にやり直せるはずです。本当の作業はカスタムゾーンに安全に残ります。
Vue3 と相性の良い隔離パターン
目標は単純です:生成ファイルは自由に置き換えられ、ウィジェットのコードは触られないこと。
実際的な方法は、特注ウィジェットを小さな内部モジュールとしてまとめることです:コンポーネント、スタイル、ヘルパーユーティリティを一箇所に置きます。生成された Vue3 アプリでは、通常カスタムコードは生成ページの外に置き、依存としてインポートされます。
ラッパーコンポーネントが非常に役立ちます。ラッパーは生成アプリと会話し、ページの既存データ形状を読み取り、正規化してウィジェットにクリーンな props を渡します。生成データの形が後で変わっても、しばしばウィジェットを書き換える代わりにラッパーを更新するだけで済みます。
有効なパターンはいくつかあります:
- ウィジェットをブラックボックスとして扱う:props in、events out。
- API レスポンス、日付、ID をウィジェット向けにマッピングするためにラッパーを使う。
- 再生成されたページが偶発的にウィジェットを上書きしないよう、スタイルはスコープ化する。
- 親の DOM 構造やページ固有のクラス名に依存しない。
スタイリングについては、スコープされた CSS(または CSS Modules)を好み、共有クラスには名前空間を付けてください。ウィジェットがアプリのテーマに合わせる必要がある場合は、ページスタイルをインポートする代わりに色・間隔・フォントサイズなどのテーマトークンを props として渡してください。
スロットは小さくオプションのもの(例:空状態のメッセージ)の場合は安全です。スロットがコアなレイアウトや挙動を制御し始めたら、ウィジェットを再び生成レイヤーに近づけてしまい、再生成の問題が起きやすくなります。
安定したコンポーネント契約の設計(props と events)
再生成を苦にしない最も安全な方法は、各ウィジェットを安定したインターフェースとして扱うことです。生成画面は変わり得ます。あなたのコンポーネントは変わらないようにします。
まず入力(props)から始めます。少数で予測可能、検証しやすいものにします。単純なプリミティブや自分で管理するプレーンなオブジェクトを好み、デフォルトを用意して画面がまだ値を渡していない場合でもウィジェットが適切に動くようにします。ID や日付文字列、列挙値のように壊れやすいものは検証して、クラッシュさせずに空状態を表示するようにします。
出力(events)については、ウィジェットがアプリ全体で一貫して感じられるよう標準化します。信頼できるセットの例:
update:modelValue(v-model 用)change(ユーザーが確定した変更、すべてのキーストロークではない)error(コンポーネントが処理を完了できないとき)ready(非同期処理が完了してウィジェットが使用可能になったとき)
非同期処理が関与する場合、それを契約の一部にします。loading や disabled のような props を公開し、サーバー側の失敗に対しては errorMessage を検討します。コンポーネントが自分でデータを取得するなら、親が反応できるように error と ready を emit してください(トースト、ログ、フォールバック UI など)。
アクセシビリティの期待事項
アクセシビリティを契約に組み込みます。label(または ariaLabel)プロップを受け取り、キーボード操作を文書化し、操作後のフォーカスが予測可能であることを保ちます。
例として、ダッシュボード上のタイムラインウィジェットは矢印キーで項目間を移動でき、Enter で詳細を開き、ダイアログが閉じたら開いたコントロールにフォーカスを戻す、といった振る舞いをサポートするべきです。これにより、生成された画面間でウィジェットを再利用しやすくなります。
手順:生成ファイルに触らずにカスタムウィジェットを追加する
小さく始めます:ユーザーが気にする一つの画面、一つのウィジェット。最初の変更を狭く保つと、再生成が何に影響するかが見えやすくなります。
-
生成領域の外にコンポーネントを作る。 所有するフォルダに置き、ソース管理に入れます(多くは
customやextensionsディレクトリ)。 -
公開面を小さく保つ。 必要なデータの少数の props と、いくつかのイベントだけを受け渡します。ページの全状態を渡すのは避けてください。
-
薄いラッパーを追加する。 ラッパーは生成ページデータをウィジェットの契約に翻訳する役割です。
-
サポートされた拡張ポイントを通じて統合する。 生成ファイルを編集しなくても参照できる方法でラッパーを読み込みます。
-
再生成して検証する。 カスタムフォルダ、ラッパー、コンポーネントは変更されずにコンパイルできるはずです。
境界を厳密に保ちます。ウィジェットは表示とインタラクションに集中し、ラッパーはデータをマッピングしてアクションを転送します。ビジネスルールはアプリのロジック層(バックエンドや共有プロセス)に置き、ウィジェットに埋め込まないでください。
簡単なチェック:今すぐ再生成があったとして、別のチームメンバーがアプリを再構築して同じ結果を得られるか? 手動編集をやり直す必要がないなら、そのパターンは堅牢です。
UI を保守しやすくするためにロジックをどこに置くか
カスタムウィジェットは見た目とユーザー入力への反応を主に扱うべきです。ウィジェットにビジネスルールを詰め込みすぎると再利用やテスト、変更が難しくなります。
良いデフォルトは、ビジネスロジックはページやフィーチャーレイヤーに置き、ウィジェットは "ダム" に保つことです。ページがウィジェットにどのデータを渡すか、ウィジェットがイベントを出したときに何をするかを決めます。ウィジェットは描画とユーザーの意図の報告を行います。
ウィジェットに近いロジック(フォーマット、小さい状態、クライアントサイドの検証)が必要な場合は、小さなサービス層の裏に隠します。Vue3 ではそれがモジュール、composable、あるいは明確な API を持つストアになります。ウィジェットはその API をインポートし、ランダムなアプリ内部を直接参照しないようにします。
実用的な分割例:
- Widget(コンポーネント):UI ステート、入力処理、ビジュアル、
select、change、retryのようなイベントを emit。 - Service/composable:データ整形、キャッシュ、API エラーをユーザーメッセージにマッピング。
- Page/container:ビジネスルール、権限、どのデータをロードするか、いつ保存するか。
- 生成アプリ部分:触らずに、データを渡しイベントを受け取る。
ウィジェット内で直接 API を呼ぶのは、そのウィジェットの契約がそれを明示的に含む場合に限ります。取得をウィジェットが担当するなら、そのことが名前やドキュメントで明確であるべきです(例:CustomerSearchWidget)。そうでなければ、items、loading、error を props として渡してください。
エラーメッセージはユーザー向けに一貫させます。生のサーバー文言を表示するのではなく、"データを読み込めませんでした。再試行してください。" のようにアプリで使う少数のメッセージにマップし、可能ならリトライアクションを含め、詳細なエラーはウィジェット外でログに残します。
例:カスタムの ApprovalBadge ウィジェットが請求書が承認可能かどうかを決めるべきではありません。ページが status と canApprove を計算し、バッジは approve を emit、ページが実際のルールを実行してバックエンドを呼び、成功やエラー状態を UI に戻します。
次の再生成で痛みを生む一般的なミス
多くの問題は Vue 自体のせいではなく、カスタム作業をジェネレータが所有する場所に混ぜたり、変更されやすい詳細に依存したりすることから生じます。
しばしば繰り返される失敗は:
- 生成された Vue ファイルを直接編集して変更点を忘れる。
- 広いセレクタやグローバル CSS を使い、マークアップが変わったときに他の画面に影響を与える。
- 生成された状態オブジェクトを直接読み書きして、名前が変わるとウィジェットが壊れる。
- コンポーネントにページ固有の仮定を詰め込みすぎる。
- コンポーネント API(props/events)をマイグレーション計画なしに変更する。
よくあるシナリオ:カスタムのテーブルウィジェットを追加して動いた。しかし一ヶ月後、生成されたレイアウトの変更でグローバルな .btn ルールがログインや管理ページに影響を与えたり、データオブジェクトが user.name から user.profile.name に変わってウィジェットが静かに失敗したりする。問題はウィジェットではなく、不安定な詳細への依存です。
これを防ぐ習慣は二つあります:
第一に、生成コードを読み取り専用と扱い、カスタムファイルは分けて明確なインポート境界を保つこと。
第二に、コンポーネント契約を小さく明確に保つこと。進化が必要なら、apiVersion のような簡単なバージョン prop を追加するか、移行期間中は古い形と新しい形の両方をサポートします。
カスタムコンポーネントを出荷する前のクイックチェックリスト
カスタムウィジェットを生成された Vue3 アプリにマージする前に、簡単な現実チェックを行います。次の再生成を難なく生き残れ、別の人が再利用できることが目標です。
- 再生成テスト: 全面再生成とビルドを実行。生成ファイルを再編集する必要があれば境界が間違っています。
- 明確な入出力: props in、emits out。外部 DOM に触ったり特定のページストアを前提にするのは避ける。
- スタイルの包含: スタイルをスコープ化し、明確なクラス接頭辞(例:
timeline-)を使う。 - 全状態の表示: loading、error、empty の各状態が妥当であることを確認する。
- 複製せずに再利用: 別ページに配置して props とイベント処理を変えるだけで動作するか確認する。
検証の簡単な方法:管理画面にウィジェットを追加し、次にカスタマーポータルページに追加して、どちらも props とイベント処理だけで動くか試します。動くなら安全です。
現実的な例:ダッシュボードにタイムラインウィジェットを追加する
サポートチームはしばしばチケットの履歴(ステータス変更、内部メモ、顧客の返信、支払いや配送のイベント)を一つの画面で見たいと考えます。タイムラインウィジェットは適していますが、生成ファイルを編集して次の再生成で作業を失いたくはありません。
安全なアプローチはウィジェットを生成 UI の外に孤立させ、薄いラッパーを通してページに差し込むことです。
ウィジェットの契約
シンプルで予測可能にします。例えばラッパーが渡すもの:
ticketId(文字列)range(過去7日、過去30日、カスタム)mode(compact か detailed)
ウィジェットが emit するもの:
select(ユーザーがイベントをクリックしたとき)changeFilters(ユーザーが範囲やモードを変更したとき)
ウィジェットはダッシュボードページやデータモデル、リクエストの方法については何も知らず、タイムラインを描画してユーザーの操作を報告します。
ラッパーがページとどうつなぐか
ラッパーはダッシュボードの隣に置かれ、ページデータを契約に変換します。ページ状態から現在のチケット ID を読み取り、UI フィルタを range に変換し、バックエンドのレコードをウィジェットが期待するイベント形式にマップします。
ウィジェットが select を emit したら、ラッパーは詳細パネルを開くかページアクションをトリガーします。changeFilters を受けたらラッパーはページのフィルタを更新してデータを再取得します。
ダッシュボード UI が再生成されても、ウィジェットは生成ファイルの外にあるため無傷で残ります。通常、ページがフィールド名を変えたりフィルタの保存方法を変えたりしたときだけラッパーを見直せば済みます。
サポートとリリースの習慣で驚きを防ぐ
カスタムコンポーネントの失敗は退屈な原因が大半:prop 形状の変更、イベントが飛ばない、生成ページの再レンダリングで期待より多く再描画される、などです。いくつかの習慣がこれらを早期に検出します。
ローカルテスト:境界の壊れを早めに見つける
生成 UI とウィジェットの境界を API として扱い、まずウィジェットだけをハードコードされた props でレンダリングしてテストします。
ハッピーパスの props と欠損値でレンダリングし、保存・キャンセル・選択といった主要イベントをシミュレートして親が正しく処理するか確認します。遅いデータや小さい画面でも試し、契約以外でグローバルステートに書き込まないことを確認します。
AppMaster ベースの Vue3 アプリで開発する場合、何かを再生成する前にこれらのチェックを実行すると原因追跡が容易になります。
再生成後の回帰:まず再確認すること
再生成ごとに、タッチポイントを再チェックします:同じ props が渡され、同じイベントが処理されているか? これが壊れの最初の現れ方です。
読み込みを予測可能に保ちます。ファイルパスに依存する脆い import を避け、カスタムコンポーネント用の安定したエントリーポイントを一つ用意します。
本番ではウィジェット内に軽量ログとエラーキャプチャを入れておくと迅速に原因を特定できます:
- マウント時に主要な props(サニタイズ済)をログする。
- 契約違反(必須 prop の欠落、型の不一致)を検出する。
- API 呼び出し失敗に短いエラーコードを添える。
- 予期しない空状態を記録する。
壊れたときは「再生成で入力が変わったのか、ウィジェット自体が変わったのか」を素早く答えられることが重要です。
パターンをアプリ全体で繰り返せるようにする次のステップ
最初のウィジェットが動いたら、次に重要なのはそれを繰り返し使えるようにすることです。
ウィジェット契約の簡単な社内標準を作り、チームのノートに書いておきます。名前付け、必須/任意の props、小さなイベントセット、エラー挙動、所有権(何が生成 UI にあり何がカスタムフォルダにあるか)を簡潔にまとめます。
境界ルールも平易に書いておきます:生成ファイルを編集しない、カスタムコードは隔離する、データは props と events を通してだけ渡す。そうすれば「ちょっと直して終わり」が恒久的な保守コストになるのを防げます。
2つ目のウィジェットを作る前に小さな再生成試験を実行してください。最初のウィジェットを出荷し、その後少なくとも二回(ラベル変更、レイアウト変更、新しいフィールドなど)の通常の変更で再生成を試し、何も壊れないことを確認します。
AppMaster を使う場合、多くの UI とロジックはビジュアルエディタ(UI ビルダー、Business Process Editor、Data Designer)で管理し、カスタム Vue コンポーネントはビルダーが表現できない本当に特別なウィジェット(タイムライン、特殊なチャート、独特の入力コントロールなど)に限定するのがうまくいくことが多いです。クリーンな出発点が欲しいなら、AppMaster on appmaster.io は再生成を前提に設計されているため、カスタムウィジェットを隔離して使うワークフローが自然に馴染みます。
よくある質問
生成された Vue ファイルを直接編集すると、再生成時にそれらが上書きされる可能性があるため、UI の変更が消えることがあります。ビルダーでの小さな視覚変更がテンプレートを再作成し、手作業の修正を消してしまうことがよくあります。
手書きの Vue コードは別フォルダ(たとえば custom や extensions)に置き、依存として読み込むようにします。生成されたページは読み取り専用として扱い、コンポーネントとは小さく安定したインターフェースでつなげます。
ラッパーは生成ページとウィジェットの間に入る薄いコンポーネントで、ページのデータ形状をクリーンな props に変換し、ウィジェットのイベントをページアクションに変換します。これにより、後で生成データが変わってもラッパーだけを更新すれば済むことが多いです。
契約は小さく保ちます:ウィジェットが必要とするデータの少数の props と、ユーザーの意図を伝える数個のイベント。単純な値や制御するプレーンなオブジェクトを使い、デフォルトを設けて入力を検証し、クラッシュさせずに空状態で失敗させるようにします。
update:modelValue はフォームコントロールのように v-model をサポートしたい場合に使います。change はユーザーが確定した操作(保存や選択完了など)に使うのが適切で、親が各キー入力を処理しないようにできます。
スタイルはスコープ化し、明確なクラスプレフィックス(例: timeline-)を使って、生成ページの変更で意図しない影響が出ないようにします。アプリのテーマが必要なら、ページのスタイルをインポートするのではなく、色や間隔、フォントサイズといったテーマトークンを props として渡してください。
原則としてウィジェットはビジネスルールを持たないほうが再利用性が高くなります。権限や検証、保存の判断はページやバックエンドに任せ、ウィジェットは表示と操作に集中して select や retry、approve といったイベントを出すだけにします。
生成されたファイルパスや親 DOM 構造、内部状態オブジェクトの形状といった不安定な詳細に依存すると壊れやすくなります。必要ならそれらはラッパーで隠蔽し、名前変更(例: user.name → user.profile.name)があってもウィジェットを書き直す必要がないようにします。
ウィジェットを個別に、契約に合ったハードコーディングされた props でレンダリングしてテストします。欠損値や異常値、遅延データ、小さい画面での表示を確認し、グローバル状態を書き換えないことを確かめます。再生成前にこれをやると原因特定がずっと楽です。
ビルダーで表現しにくい要件(複雑なチャート、マップピッカー、署名パッド、ドラッグ&ドロッププランナーなど)に対してはカスタムウィジェットを検討する価値があります。視覚エディタで表現できるものであれば、長期的な保守性のためにそちらを優先します。


