Nuxt2のアップデートに苦しむ備忘録
業務でNuxt2をNuxt3に移行している途中ですが、今までに出くわした苦しみをここに書き残しておきます。苦しみが増えると追記する可能性があります。
基本的な移行の話
基本的な話は探せば出てくるんですが、これで全てが上手くいったら苦労はしない。
前提
構成
歴史的経緯からこのような構成になっている。何故そうなっているかは私は知らない。
- Nuxt2 + TypeScript
- Vue Class Component(property-decorator)
- Vue Styled Components
- Vuex
本来はこれらをぶち壊してClass ComponentはComposition APIに、Vuexはpiniaに書き換えるべきなのですが、それをやりだすと時間がいくらあっても足りないということで何とか誤魔化していきます。
方針
- Vue Class Component
vue-facing-decoratorを使う 併せてvuex-facing-decoratorも使う
- Vue Styled Components
vue-styled-componentsがVue3に対応していないためvue3-styled-componentsを使う
- Vuex
こういうPluginを作って対応。
import type { StoreOptions } from 'vuex' const storeOption: StoreOptions<any> = { modules: { // 省略 }, } export default storeOption
import { defineNuxtPlugin } from 'nuxt/app' import { createStore } from 'vuex' import rootStore from '~/store' export default defineNuxtPlugin((nuxtApp) => { const store = createStore(rootStore) nuxtApp.vueApp.use(store) return { provide: { store } } })
遭遇した問題
Vue SFC内でclassの export defaultを消すとWebstormのテンプレートのハイライトが効かなくなる
WebstormはVueのClass Componentを認識してテンプレートに埋め込まれたコンポーネントや変数などのハイライトを行ってくれるが、そのために対象となるclassがdefault exportされていなければいけない。
一方で、vue-facing-decoratorはこのような使い方を想定している。
<script lang="ts"> import { Component, Vue, toNative } from 'vue-facing-decorator' @Component class MyComponent extends Vue {} export default toNative(MyComponent) </script>
そのため、かなり強引だがviteのプラグインでexport default class
をclass
に置換することで解決した。
export default function () { return { name: 'vue-class-replace-plugin', enforce: 'pre' as const, transform(code: string, id: string) { if (id.includes('.vue')) { const modifiedCode = code.replace('export default class', 'class') return { code: modifiedCode, map: null, // ソースマップが不要な場合 } } return null // 他のファイルは変更しない }, } }
<!--suppress JSDuplicatedDeclaration --> <script lang="ts"> /* eslint-disable import/export */ import { Component, Vue, toNative } from 'vue-facing-decorator' @Component // @ts-ignore export default class MyComponent extends Vue {} // @ts-ignore export default toNative(MyComponent) </script>
decoratorがnuxt build時にエラーになる
vue-facing-decoratorのissueに挙がっていたが、何も手掛かりがなかった。
色々試した結果解決できたのでissueに報告しておいた。
https://github.com/facing-dev/vue-facing-decorator/issues/93#issuecomment-1832410439
ページ内のリンクを踏んだ場合にエラーが発生する
ページ内のリンクから画面遷移しようとすると、アドレスだけが変わりこのようなエラーが出ていた。
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '__asyncLoader')
非同期コンポーネントを全てdefineAsyncComponent
関数でラップすることでエラーが起きなくなった。
ブラウザのコンソールにvue3-styled-components由来のVue warnが大量に出る
ページを表示した際にこのようなwarnが大量に出る。
[Vue warn]: injection "theme" not found.
traceによるとvue3-styled-components由来のようだが、色々試してみてもこれが消えることはなかったので何かしらのライブラリのバグの可能性がある。
特にレイアウトが崩れているようでもなかったのでこの警告は表示されないようにした。
warnHandlerの設定をnuxt.config.tsから弄る項目が見つけられなかったので適当なmiddleware内で処理した。
useNuxtApp().vueApp.config.warnHandler = (msg: string, _, trace: string) => { if (msg.includes('injection "theme" not found')) { return } console.warn(`[Vue warn]: ${msg}\n${trace}`) }
Titleという名前のコンポーネントがテンプレート内にあるとPropsの処理でエラーが起きる
type
というプロパティを渡しているコンポーネントでこのようなエラーが出てページが表示されなくなった。
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'type')
色々試した結果、テンプレート内にTitle
という名前のコンポーネントがあるとこのエラーが発生することがわかった。因果関係は不明。
ViteのHMR設定
nginxが前段にある環境でこのあたりの設定に合わせても上手くいかなかったが、最終的にdevServerのportとHMRのportを合わせることで上手く動いているように見える。
未解決の問題
this.$nuxt.$on/this.$nuxt.$emitの代替となる機能
Vue3のProvide/Injectを使えば解決できるか、もしくはこういったものを導入するか。
@gmap-vue/v3の表示のスタイル上の問題
そのままでは何も表示されず、スタイルを弄って表示しているが、表示領域からはみ出すという問題が更に発生しているので使うライブラリを変えたほうがいいかもしれない。
:deep(.gmv-map) { position: unset !important; }
おまけ
100以上あるコンポーネントに一つ一つvue-facing-decoratorを適用していくのは正気の沙汰ではないので以下のような正規表現を作成した。
(export default class )(\w+)( extends Vue \{?:|[\s\S]*\}\n)(\n// @ts-ignore\nexport default toNative\(\w+\)\n)?(</script>)
$1$2$3\n// @ts-ignore\nexport default toNative($2)\n$5
おわりに
Nuxt3のマイグレーションガイドがどうにも親切じゃないように思うのと、Nuxt3もVue3もメジャーバージョンアップで破壊的な変更が多くて辛いなって思ってます。