Closed7
Nuxt 2 でも middlewareの型をチェックしたい
はじめに
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>
課題
middlewareは文字列で指定可能だが、このとき型推論が効かない&型チェックが効かない。
そのため、存在しないmiddlewareファイルを設定してもエラーにならない。
実行時にはエラーになるため実際に動かしてみて初めて気づく。
存在しないファイルを設定した場合、ビルドエラーなどで開発時に気付きたい。
pages/index.vue
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api';
export default defineComponent({
middleware: 'test' // エラーにならない
})
</script>
元々の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)
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>
}
}
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の型推論が効くようになった。
ファイル名が自動生成されるようにする
下記のようなスクリプトを用意する。
(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()
Nuxt Bridge に middleware の型を生成する機能を実装したので、自前で用意しなくても済むようになった
このスクラップは2023/08/07にクローズされました