Closed7

Nuxt 2 でも middlewareの型をチェックしたい

wattanxwattanx

はじめに

Nuxt 2 では、各ページで middlewareの設定ができる。
middlewareディレクトリに作成したmiddlewareのファイルを指定することで設定可能である。

middleware/auth.js
export default function (context) {
  // Add the userAgent property to the context
  context.userAgent = process.server
    ? context.req.headers['user-agent']
    : navigator.userAgent
}
pages/index.vue
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api';

export default defineComponent({
  middleware: 'auth'
})
</script>
wattanxwattanx

課題

middlewareは文字列で指定可能だが、このとき型推論が効かない&型チェックが効かない。
そのため、存在しないmiddlewareファイルを設定してもエラーにならない。
実行時にはエラーになるため実際に動かしてみて初めて気づく。

存在しないファイルを設定した場合、ビルドエラーなどで開発時に気付きたい。

pages/index.vue
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api';

export default defineComponent({
  middleware: 'test' // エラーにならない
})
</script>
wattanxwattanx

元々のmiddlewareの型はどうなってるか

node_modules/@nuxt/types/app/vue.d.tsを確認すると、下記のような型になっている。

node_modules/@nuxt/types/app/vue.d.ts
declare module 'vue/types/options' {
  // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars
  interface ComponentOptions<V extends Vue> {
    // eslint-disable-next-line @typescript-eslint/ban-types
    asyncData?(ctx: Context): Promise<object | void> | object | void
    fetch?(ctx: Context): Promise<void> | void
    fetchKey?: string | ((getKey: (id: string) => number) => string)
    fetchDelay?: number
    fetchOnServer?: boolean | (() => boolean)
    head?: MetaInfo | (() => MetaInfo)
    key?: string | ((to: Route) => string)
    layout?: string | ((ctx: Context) => string)
    loading?: boolean
    middleware?: Middleware | Middleware[]
    scrollToTop?: boolean
    transition?: string | Transition | ((to: Route, from: Route | undefined) => string | Transition)
    validate?(ctx: Context): Promise<boolean> | boolean
    watchQuery?: boolean | string[] | ((newQuery: Route['query'], oldQuery: Route['query']) => boolean)
    meta?: { [key: string]: any }
  }
}

Middlewareの型は

export type Middleware = string | ((ctx: Context, cb: Function) => Promise<void> | void)
wattanxwattanx

Nuxt 3 の middlewareはどうなっているか

Nuxt 3 ではmiddlewareの設定で型が効くようになっている。

middleware/redirect.ts
export default defineNuxtRouteMiddleware((to, from) => {
  if (to.params.id === "1") {
    return abortNavigation();
  }
  return navigateTo("/");
});
pages/index.vue
<script setup lang="ts">
definePageMeta({
  middleware: ["test"], // エラーになる
});
</script>

なぜ型が効くのか

ビルド時やnpx nuxi prepareなどで、middlewareの型が自動生成されるため

.nuxt/types/middleware.d.ts
import type { NavigationGuard } from 'vue-router'
export type MiddlewareKey = "redirect"
declare module "/Users/wattanx/repo/nuxt3-example/node_modules/nuxt/dist/pages/runtime/composables" {
  interface PageMeta {
    middleware?: MiddlewareKey | NavigationGuard | Array<MiddlewareKey | NavigationGuard>
  }
}
wattanxwattanx

Nuxt 2 でも型が効くようにがんばる

node_modules/@nuxt/types/app/vue.d.tsのmiddlewareの型を上書きすれば実現できそうな気がするので下記のような型を作ってみる

@types/middleware.d.ts
import Vue from 'vue';
import { Context } from '@nuxt/types/app';

type MiddlewareKey = 'test';

declare module 'vue/types/options' {
  export type Middleware =
    | ((ctx: Context, cb: Function) => Promise<void> | void)
    | MiddlewareKey;
  interface ComponentOptions<V extends Vue> {
    middleware?: Middleware | Middleware[];
  }
}

上記の型をつくったことで、middlewareの型推論が効くようになった。

wattanxwattanx

ファイル名が自動生成されるようにする

下記のようなスクリプトを用意する。
(Nuxt 3 のように .nuxtディレクトリに型を置いてもいいし、別ディレクトリに保存してもよさそう)

import { fs, glob } from 'zx';

async function main() {
  const files = await glob(['middleware/**/*.{js,ts}']);
  const resources = files
    // 拡張子を消す
    .map((x) => `'${x.replace(/^(.+)\..+$/, '$1')}'`)
    .join(' | ')
    .replaceAll('middleware/', '');

  const type = `import Vue from 'vue';
import { Context } from '@nuxt/types/app';

type MiddlewareKey = ${resources};

declare module 'vue/types/options' {
  export type Middleware =
    | ((ctx: Context, cb: Function) => Promise<void> | void)
    | MiddlewareKey;
  interface ComponentOptions<V extends Vue> {
    middleware?: Middleware | Middleware[];
  }
}
`;
  fs.writeFileSync('@types/middleware.d.ts', type);
}

main()
このスクラップは2023/08/07にクローズされました