Vueのバグを発見しました。
PR: https://github.com/vuejs/vue/pull/11107
事象
async componentでv-if="false"を使うと、vueのvdom処理でエラーになる。
詳細はPRのテストコード参照。
原因
vdomでは、async componentはasyncPlaceholder(コメントノード + 補助データ)として扱われる。
v-if="false"はコメントノードとして扱われる (参考: https://github.com/vuejs/vue/issues/5117 )。
patch.jsのsameVNodeでは、async componentとコメントノードが同じと判定されてしまう(これが誤り)。
sameVNodeで同じと判定されると、patchVNode処理に入り、その中のasyncFactory.resolvedで、asyncFactoryがundefinedでエラーになる。
修正
sameVNodeでasync componentとコメントノードが同じと判定されないようにする。
メモ
data-server-renderedはルートにのみつくらしい。
hydrationするときに取り除くので、hydrationは一回のみ実行される。
hydrationのときにロードできないasync componentはasyncPlaceholderにされる(つまり、async component内部はhydrationの対象外になる)。
コードを見た感じ、hydrationのときにすでにresolveされているasync componentは通常のコンポーネントとほぼ同様に処理されるみたい。
感想
nuxtでパフォチューをやっているときに遭遇した。
セキュリティ懸念(誤ってキャッシュされるとか、リクエスト間で情報が漏れるとか)から、SSRでは認証が必要なデータは取得しないようにしている。
非ログインユーザーはSSRで完結するのでクライアント側でデータ取得しなくて良い。
ログインユーザーはクライアント側でデータ取得するが、今まではmount後にリクエストを開始していて遅かったので、mount前にリクエストを開始するようにした。
それでこのバグが発生。
最初、hydrationが行われる前にリクエストが返ってきて、SSRとクライアントのdom不整合かなと思ったが、データ更新を強制的にmount後や、mount後のnextTick後にしても発生する。
vue内部のresolvedでエラーになっていることがわかったので、vueのコードを検索したらpatch.jsを発見。
async componentがresolveされる前に、v-if="false"でvdom更新されるとエラーになることを発見。
結局、パフォチューで速くなったせいで、async componentのロードとデータ更新のタイミングが逆転してバグが発生したみたい。
自分のコードが原因だと思って1日くらいかかった。
これでvueのコントリビューターの肩書を得た。