3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude CodeでVue2→3をやってみたら、Javaのメジャーアップより苦労した話

3
Posted at

はじめに

GMOコネクトの永田です。

少し前、Java のメジャーアップ(11→21)を Claude Code でやったら、かなり楽でした。「これなら、ずっと後回しにしていた Vue 2→3 もいけるのでは」——そう思って手を付けたら、意外と苦労した、という話です😇。

Vue 2→3 が必要なのは何年も前から明確でした(Vue 3 は2020年9月、Vue 2 は2023年末にEOL、EOLからも2年以上)。それでも修正だけで数人月、加えてフロント全体のリグレッション試験が要り、工数が見合わずに寝かせていました。同じ状況の現場は多いはずです。

今回のスコープは修正部分(全リグレッション試験より前)。期待は「Claude Code の進化と移行ノウハウの蓄積」でした。結果、手動よりは遥かに楽。ただ Java と同じ楽さではありませんでした。この記事は、なぜ Vue が Java より苦労したのかを構造で説明し、押さえる所と覚悟する所を持ち帰れるように書いた記録です。

先にまとめ

Java のメジャーアップと Vue2→3 は、AIエージェントから見た御しやすさがかなり違いました。

観点 Java 11→21 + Spring Vue2→3
破壊の出方 主にコンパイル時(javax→jakarta・削除API) 多くが実行時(テンプレの意味論・リアクティビティ・v-model仕様・ライフサイクル)
「緑」の意味 緑=ほぼ動く 緑が嘘をつく(E2E・実機まで見ないと分からない)
Claude Code の効き コンパイルを回せば高確率で収束 緑にする以上に「緑が嘘をつく場所を炙り出す」のが主戦場
ランタイムと依存の分離 旧メジャーが新ランタイム対応なら言語だけ上げられる(Spring 5.3.x は Java 21 対応→javax のまま先送り) 作り直しで旧メジャーが Vue 3 上で動かず、更新が強制バンドル(据え置き不可)
撤去フェーズ (概念上ない) @vue/compat の「撤去」が独立フェーズで一斉に壊れる

要点はこうです。

  • @vue/compat は「入れて動かす」より「外す」ほうが本番。コンパイルが通るのに実行時で白画面になる
  • 工数の山は、公式 Migration Guide に載っていない領域=Vueに依存する多数のライブラリの同時上げ。公式に載る破壊のほうは通読で防げる
  • 「緑にする」より「緑が嘘をつく場所を炙り出す」(E2E・実機)が仕事の中心

だから肝は、着手前の机上検証と計画です。公式ガイドとその上の依存関係を先に確かめ、compat撤去まで工程に織り込む。そこから漏れる「公式に無い」分を、実行時の確認(E2E・実機)で拾う——これが近道でした。

1. 期待値:なぜ「Claude Codeなら楽勝」と思ったか

直前に Java のメジャーアップ(11→21、Spring 込み)を Claude Code でやっていました。これが楽でした。理由は単純で、Java は破壊的変更の多くがコンパイル時に出るからです。javaxjakarta の名前空間移行や削除されたAPIは、ビルドを走らせれば赤くなる。Claude Code にコンパイルループを回してもらい、赤が消えるまで直せば、かなりの確度で動く状態に収束します。型システムとコンパイラが安全網になっていて、「緑になった=ほぼ動く」の相関が高いわけです。

この体験を、そのまま Vue に持ち込みました。「Vue も同じように、ビルドを緑にすれば動くだろう」と。ここが最初の思い込みでした。

2. 現実:@vue/compat は「入れて動かす」より「外す」が本番

Vue2→3 には @vue/compat(Migration Build)という公式の移行用ビルドがあります。Vue 3 上で Vue 2 互換の挙動を出してくれるもので、噛ませると「一旦動く」ところまでは早い。ここは期待どおりでした。ただ注意が要ります。「compat で動いた」と「compat を外せた(=Vue 3 完全移行)」の間には、かなりの隔たりがあります。 前者をゴールと錯覚しやすいのが、最初の落とし穴でした。

問題はその先です。compat はそのまま使い続けるものではありません。公式が一時的な移行用ビルドと位置づけ、いずれ配布も止める予定と明記しています。残したままだと、互換シムと実行時警告のぶんの性能・バンドルのオーバーヘッド、compat 層そのものに起因する不具合(後述の lodash はこれ)、EOL 済み Vue 2 挙動への依存、が残ります。公式ガイドも最後のステップをこう書いています。

When all warnings are fixed, you can remove the migration build and switch to Vue 3 proper. Note you may not be able to do so if you still have dependencies that rely on Vue 2 behavior.
(警告をすべて直せば Migration Build を外して Vue 3 proper に切り替えられる。ただし Vue 2 の挙動に依存する依存関係が残っていると、それができないことがある)

つまり本番は compat の撤去です。ここは正直、目新しい話ではありません。「移行ビルドからの脱却」でつまずいた記録は日本語でもいくつも出ています。この記事で書きたいのは撤去の罠カタログ自体ではなく、なぜ Java と比べてここまで違ったのか(次章)です。なので撤去で何が起きたかは、後の対比に必要な分だけ短く挙げます。

ポイントは1つ。compat を外すとコンパイルは無エラーで通るのに、実行時に白画面になる。 静的解析では何も出ず、画面を開くかボタンを押した瞬間に初めて割れます。代表例だけ(すべて実行時にだけ現れます)。

  • v-model の一斉破断: compat が裏で value/inputmodelValue/update:modelValue に読み替えていた(COMPONENT_V_MODEL)。撤去でこれが消え、独自コンポーネントの v-model が一斉に切れる
  • $parent・ライフサイクル旧名が黙って死ぬ: $parent.データ 参照が no-op になり、beforeDestroy などがエラーも出さず不発になる
  • lodash で全描画クラッシュ: compat のゲッターがグローバル関数値に .bind を呼び、自前 .bind を持つ lodash と衝突して「Expected a function」で落ちる(vuejs/core #4403
  • type: Array | Object で描画クラッシュ: |(ビット演算)が 0 になる自コードの粗を Vue 3 が instanceof 0 で弾いた。Vue 2 が黙認していた負債の露呈

キーメッセージは1つ。ビルドが緑でも終わりではない。破壊は実行時に潜んでいる。

3. なぜJavaより苦労したか(2つの軸に分ける)

「Java は楽、Vue は苦労」と一括りにすると、性質の違う2つの軸が混ざります。分けると、片方は本質的な差、もう片方は状況次第で Java にも起こり得る差でした。

軸1: 破壊がコンパイル時に出るか、実行時に潜るか(本質的)

Java の破壊は型システムが捕まえます。Vue にもコンパイル時に弾かれる破壊はありますが(テンプレートコンパイラが filters や構文を弾く)、厄介なのはその外側——リアクティビティ・v-model仕様・ライフサイクル・コンポーネント間の配線など、コンパイラに引っかからず実行時に潜る破壊です。だから Vue では「緑」が嘘をつく。Claude Code がコンパイルループで収束させられる範囲が、そもそも狭い。仕事の中心は「緑にする」ではなく「緑が嘘をつく場所を、E2Eや実機で炙り出す」に移りました。これは言語・フレームワークの性質そのもので、プロジェクトに依りません。AI移行で Java が本当に有利なのは、この一点です。

軸2: ランタイム更新とライブラリ更新を分離できるか(状況次第)

「多数のライブラリを同時に上げる」痛みは、実は Java 対 Vue の本質ではありません。今回の Java 移行は、言語だけ Java 11→21 に上げて、Spring は旧ライン(5.3.x=javax)に据え置きました。これができたのは、Spring 5.3.x が Java 8〜21 に対応していて、旧メジャーのまま新しい JDK で動いたからです(Spring 5.3 は Java 8〜21 をサポート)。おかげで jakarta 移行(Spring 6)を「先送り」できました。ただし"回避"ではなく先送りで、5.3.x の OSS サポートは 2024年8月に終了済み、いずれ移る必要があります。

Vue 3 にはこの逃げ道がありません。コアが作り直され、Vuetify 1.5 や vee-validate 2 を Vue 3 上で動かす手が無い=「新ランタイムでも動く旧メジャーライン」が存在せず、フレームワーク更新にライブラリ更新が強制バンドルされます。テストツールも例外ではなく、Vue Test Utils も v1→v2 へ上げないと既存テストが走りません(安全網を張り直しながら進むことになる)。つまり差は言語の良し悪しではなく、痛みの大半は「依存がその破壊を生き延びる/先送りできるか」で決まり、型システムとは別の軸だということ。Java も Spring 6 / jakarta へ踏み込めば同型の痛みを食らいます。型システムが決めるのは「破壊をどう見つけるか(コンパイル時か実行時か)」であって、「破壊を食らうか否か」ではありません。

補足: これは「Java 対 Vue」であると同時に「BE 対 FE」でもある

正直に言うと、今回の比較には Java=バックエンド/Vue=フロントエンドという差も混じっています。フロントエンドは、UI の見た目やコンポーネント間の配線を、ユニットテストにきっちり起こすことが多くありません。壊れる箇所がそもそも UT の外にあるので、"緑"の後ろ盾が薄く、確認は E2E や実機に寄ります。これは言語やフレームワークの差ではなく、FE と BE の差です。「Vue だから確認しにくい」と混ぜないよう、切り分けておきます。

+ Vue 固有の「撤去フェーズ」

もう1点、Vue には「@vue/compat を外す」という工程があります。Java の互換ビルドに当たるものが無いのに対し、Vue はこれが独立フェーズとして存在し、しかもそこで軸1の実行時破壊が一斉に噴き出します。

一言でいうと、Java が楽だったのは、型システムが安全網になっていた(軸1)ことに加えて、今回はライブラリの破壊を先送りできた(軸2)から。Vue はそのどちらも味方にできませんでした。Claude Code が悪いのではなく、今回の Vue 移行は Claude Code が最も得意とするコンパイル駆動ループが効きにくい形をしていた、という話です。

なお、この見方(型システムと依存の先送りで難易度を測る)は Vue2→3 に限りません。静的型のない・依存の多い大型移行なら、次に別のメジャーアップをやるときの当たり付けにも使えます。

4. そのハマり、公式のMigration Guideにあった?

軸1・軸2は構造の話でしたが、実際には他にも細かいハマりが色々ありました。それらを、移行が一段落した後で公式 Migration Guide と突き合わせて整理してみたんです。すると、ガイドに載っていたもの(読めば防げた)と、載っていなかったもの(読んでも防げない)に、きれいに分かれました。

軸を「公式 Migration Guide に載っているか」に置いたのには理由があります。各ライブラリの GitHub issue まで事前に漁れ、は要求が高すぎる。でも公式ガイドの通読なら、誰でも取れる準備です。この軸で分けると、こうなりました。

ハマり 公式ガイドに メモ
v-model仕様変更・COMPONENT_V_MODEL ◎ 明記 breaking-changes/v-model
.native削除・filters廃止・$children廃止・$listeners統合・ライフサイクル改名 ◎ 明記 各 breaking-changes ページ
compat撤去でコンパイルは通るのに実行時破断(規模の痛み) △ 原理のみ 「警告を消してから撤去」「Vue 2依存があると撤去できない」までは書くが、大規模で一斉に壊れる体感までは書かれていない
個別ライブラリの非互換(vee-validate・Vuetify等) △ 一般論のみ 「Vue 2依存があると撤去できない」まで公式。個別のライブラリ名は当然載らない(vee-validate #4379
グローバルプロパティの関数バインドcrash(lodash等) ✗ なし vuejs/core #4403 の世界
lodash .bind衝突・$parent の no-op ✗ なし 公式外の細かい個別事例
type: Array | Object のクラッシュ ✗ なし 自コードの負債が露呈しただけ

整理して見えた学びは2つです。

  • ◎は、最初にちゃんと読めば防げた。 教科書的な破壊(v-model・.native・filters・$children・ライフサイクル)は全部公式に載っています。ここは通読でケチらず潰す。「動かしながら気づく」より遥かに安い(=次章の「押さえる」)
  • ガイドを読んでもハマる層は残る。だから次回もリスクと工数に見込んでおく(見込みにくいが)。 時間を溶かしたのは公式外の (1) 個別ライブラリの非互換、(2) compat撤去の「規模」の痛み、(3) 自コードの負債の露呈。事前に具体化しにくくても、「読んでも防げない層がある」と分かっていればバッファは積めます(=次章の「覚悟」)

余談として、「世の中に Vue 3 移行ノウハウが溜まってきたはず」という当初の期待は半分しか当たりませんでした。◎は確かに溜まっている。でも自分が実際に踏む、個々はまれで種類の多い細かい落とし穴は、そもそも世の中にまとまっていません。

5. 次にやる人へ:押さえる/覚悟して

押さえる(準備で減らせる)

  • 公式 Migration Guide の incompatible / partially-compatible リストを最初に通読する
  • 使うライブラリの Vue 3 対応状況を先に洗う。対応版が無い・別物・根本非互換のものが工数の山
  • 「compat撤去」を独立フェーズとして最初から見積もる
  • 事前にコンテキスト化するのは公式・デファクトの一次情報まで。「世の中の苦労事例」の事前網羅は無理(踏む罠は事前に分からない)。それは踏んだ瞬間にピンポイントで引くもの

覚悟(情報を集めても残る)

  • ビルドが緑でも終わりではない。実行時に確かめる仕組み(E2E・実機・診断ビルド)が本体
  • UIライブラリの Vue 3 非対応が最大の山。全利用箇所を書き換えるより、旧APIを保った自前シム+CSS同梱で実体だけ Vue 3 化が安いことが多い。工数は「タグ数」でなく「どれだけ深く絡んでいるか」で決まる(<v-app> だけに見えて実は数百箇所+内部DOM依存、が起きる)
  • compat撤去で v-model が一斉に切れる。どの段階で撤去するかを設計する

AIの使い方(今回効いたもの)

  • 診断ビルドで炙り出す: 本番ビルドは console.error を落とすので、最小化と console 除去を一時的に止め、app.config.errorHandler でコンポーネント名込みのスタックを溜める。本番でしか出ない描画バグが見える
  • 複数レンズの並列レビュー: 「compat撤去の網羅」「静かに消える変更」「サードパーティの実API突合」など観点ごとに独立サブエージェントを並列で。compat と無関係の既存バグまで同時に出た
  • 機械変換は全数照合で担保: ::v-deep:deep() の大量変換は、変換後のコンパイル済みCSSを差分照合して挙動不変を確認
  • プレイブック化して横展開: grep パターンと before/after をまとめ、2件目はそれを渡して着手。手戻りが減った

まとめ

  • @vue/compat は「入れて動かす」より「外す」が本番。コンパイルが緑でも動作は別物で、破壊は実行時に潜む
  • 苦労の正体は2軸+α。①型システム(Javaはコンパイル時/Vueは実行時に破壊が潜む=本質的)②依存の先送り可否(今回はSpring 5.3.xに据え置いて回避/Vue 3 は作り直しで不可)。加えて Java=BE/Vue=FE の差も(FEはUTに起こしにくく確認がE2E頼り=言語差ではない)
  • 「Javaが常に楽」ではない。javax→jakarta を踏めば同型の痛み。痛みの大半は「依存が破壊を生き延びるか」で決まる
  • 公式 Migration Guide の◎は通読でケチらず潰す。工数の山は公式外の個別ライブラリ非互換で、読んでも防げない——だから次回もリスク・工数に見込む
  • 手動より遥かに楽にはなったが、いちばん効いたのは「緑にする」ことより、着手前の机上検証と移行計画だった。公式ガイドとその上の依存関係(各ライブラリのVue3対応)を先に検証し、compat撤去まで工程に織り込む。実行時の確認(E2E・実機)は、そこから漏れる分の受け皿

最後に、GMOコネクトではサービス開発支援や技術支援をはじめ、幅広い支援を行っておりますので、何かありましたらお気軽にお問合せください。

お問合せ:https://gmo-connect.jp/contactus/

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?