unplugin-vue-router を使ってみる
以下 RFC を読んでて初めて知った、 unplugin-vue-router
を試してみる。
上記 RFC の実装はこのパッケージしかされてないみたいだから、動かして見るためにも先にこっちを試す必要がありそうなので。
Automatic file based Routing in Vue with TS support
の通り、VueRouter におけるファイルベースのルーティングと型サポートをビルドタイムで行ってくれるツールらしい。 Nuxt におけるルーティングとの違いはよくわからない。
一旦 Vite で単純な Vue + VueRouter アプリを用意しよう。
$ yarn create vite --template vue-ts
yarn create v1.22.19
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
success Installed "create-vite@3.0.2" with binaries:
- create-vite
- cva
✔ Project name: … unplugin-vue-router-sample
$ yarn add vue-router
$ yarn install
あとは適当にスキャフォルドされたコードを削除していって準備OK
インストール
$ yarn add -D unplugin-vue-router
devDependencies でいいってことは、やっぱランタイムプラグインじゃなくてビルドタイムでだけ使うのか。
Vite の設定も指示通りに
// vite.config.ts
import VueRouter from 'unplugin-vue-router/vite'
export default defineConfig({
plugins: [
VueRouter({
/* options */
}),
// ⚠️ Vue must be placed after VueRouter()
Vue(),
],
})
お、サーバー起動しただけで勝手に型定義が生成されたぞ。
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
/// <reference types="unplugin-vue-router/client" />
import type {
// type safe route locations
RouteLocationTypedList,
RouteLocationResolvedTypedList,
RouteLocationNormalizedTypedList,
RouteLocationNormalizedLoadedTypedList,
// helper types
// route definitions
RouteRecordInfo,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
// vue-router extensions
_RouterTyped,
RouterLinkTyped,
NavigationGuard,
UseLinkFnTyped,
// data fetching
_DataLoader,
_DefineLoaderOptions,
} from 'unplugin-vue-router'
declare module 'vue-router/auto/routes' {
export interface RouteNamedMap {
}
}
declare module 'vue-router/auto' {
import type { RouteNamedMap } from 'vue-router/auto/routes'
export type RouterTyped = _RouterTyped<RouteNamedMap>
/**
* Type safe version of `RouteLocationNormalized` (the type of `to` and `from` in navigation guards).
* Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocationNormalized<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationNormalizedTypedList<RouteNamedMap>[Name]
/**
* Type safe version of `RouteLocationNormalizedLoaded` (the return type of `useRoute()`).
* Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocationNormalizedLoaded<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[Name]
/**
* Type safe version of `RouteLocationResolved` (the returned route of `router.resolve()`).
* Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocationResolved<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationResolvedTypedList<RouteNamedMap>[Name]
/**
* Type safe version of `RouteLocation` . Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocation<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationTypedList<RouteNamedMap>[Name]
/**
* Generate a type safe params for a route location. Requires the name of the route to be passed as a generic.
*/
export type RouteParams<Name extends keyof RouteNamedMap> = RouteNamedMap[Name]['params']
/**
* Generate a type safe raw params for a route location. Requires the name of the route to be passed as a generic.
*/
export type RouteParamsRaw<Name extends keyof RouteNamedMap> = RouteNamedMap[Name]['paramsRaw']
export function useRouter(): RouterTyped
export function useRoute<Name extends keyof RouteNamedMap = keyof RouteNamedMap>(name?: Name): RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[Name]
export const useLink: UseLinkFnTyped<RouteNamedMap>
export function onBeforeRouteLeave(guard: NavigationGuard<RouteNamedMap>): void
export function onBeforeRouteUpdate(guard: NavigationGuard<RouteNamedMap>): void
// Experimental Data Fetching
export function defineLoader<
P extends Promise<any>,
Name extends keyof RouteNamedMap = keyof RouteNamedMap,
isLazy extends boolean = false,
>(
name: Name,
loader: (route: RouteLocationNormalizedLoaded<Name>) => P,
options?: _DefineLoaderOptions<isLazy>,
): _DataLoader<Awaited<P>, isLazy>
export function defineLoader<
P extends Promise<any>,
isLazy extends boolean = false,
>(
loader: (route: RouteLocationNormalizedLoaded) => P,
options?: _DefineLoaderOptions<isLazy>,
): _DataLoader<Awaited<P>, isLazy>
export {
_definePage as definePage,
_HasDataLoaderMeta as HasDataLoaderMeta,
_setupDataFetchingGuard as setupDataFetchingGuard,
_stopDataFetchingScope as stopDataFetchingScope,
} from 'unplugin-vue-router/runtime'
}
declare module 'vue-router' {
import type { RouteNamedMap } from 'vue-router/auto/routes'
export interface TypesConfig {
beforeRouteUpdate: NavigationGuard<RouteNamedMap>
beforeRouteLeave: NavigationGuard<RouteNamedMap>
$route: RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[keyof RouteNamedMap]
$router: _RouterTyped<RouteNamedMap>
RouterLink: RouterLinkTyped<RouteNamedMap>
}
}
まだ VueRouter を一切使ってないからここが空なんだな
declare module 'vue-router/auto/routes' {
export interface RouteNamedMap {
}
}
今更だけど unplugin
の由来ってこれか。
Vite, Rollup, Webpack, esbuild 向けのプラグインを同時に開発できるツールなんだ。
型ファイルが生成されてたので TypeScript でそれを参照するように設定する必要がある
{
// ...
"include": [/* ... */ "typed-router.d.ts"]
// ...
}
ルーターはこう定義すれば、ルートを自動挿入してくれるから手動定義する必要がないらしい
import { createRouter, createWebHistory } from "vue-router/auto";
const router = createRouter({
history: createWebHistory(),
});
export default router;
import { createRouter, createWebHistory } from "vue-router/auto";
const router = createRouter({
history: createWebHistory(),
});
export default router;
By default, this plugins checks the folder at src/pages for any .vue files and generates the corresponding routing structure basing itself in the file name. This way, you no longer need to maintain a routes array when adding routes to your application, instead just add the new .vue component to the routes folder and let this plugin do the rest!
とのことなので、src/pages
にページコンポーネントを作っていく。
適当にこんなファイルをおいてみた
src/pages/index.vue
src/pages/users/[id].vue
ビルドすると
$ yarn dev
型ファイルにルートが追加されてる!
declare module 'vue-router/auto/routes' {
export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/users/[id]': RouteRecordInfo<'/users/[id]', '/users/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
}
}
ルート生成のルールは Nuxt でお馴染みな感じがするので特に違和感もないかな。
ルートパラメータの型付けをどこまでできるのか。
補完がどのぐらい効くのか
router-link
の to
export default defineComponent({
setup() {
const route = useRoute();
if (route.name === "/users/[id]") {
route.params.id
}
},
});
こうすると route が確定して route.params.id
に型安全アクセスできるんだけど、これも自動でやってくれるものな気がするな。
あー、これでいいのか。
export default defineComponent({
setup() {
const route = useRoute('/users/[id]');
return { route }
},
});
流石にこれは手入力いるのかな。補完は効くから手軽ではあるけど。
playground のコードでもそうしてるからそういうもんか。
雰囲気把握できたのでヨシ