版博士V2.0程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. # unplugin-vue-router
  2. [![NPM version](https://img.shields.io/npm/v/unplugin-vue-router?color=black&label=)](https://www.npmjs.com/package/unplugin-vue-router) [![ci status](https://github.com/posva/unplugin-vue-router/actions/workflows/ci.yml/badge.svg)](https://github.com/posva/unplugin-vue-router/actions/workflows/ci.yml)
  3. > Automatic file based Routing in Vue with TS support ✨
  4. <!-- https://user-images.githubusercontent.com/664177/176622756-3d10acc6-caac-40ff-a41f-9bdccadf7f1d.mp4 -->
  5. <p align="center">
  6. <img src="https://user-images.githubusercontent.com/664177/176623167-0153f9fb-79cd-49a7-8575-429ce323dd11.gif" >
  7. </p>
  8. This build-time plugin simplifies your routing setup **and** makes it safer and easier to use thanks to TypeScript. Requires Vue Router at least 4.1.0.
  9. ⚠️ This package is still experimental. If you found any issue, design flaw, or have ideas to improve it, please, open an [issue](https://github.com/posva/unplugin-vue-router/issues/new/choose) or a [Discussion](https://github.com/posva/unplugin-vue-router/discussions).
  10. ## Install
  11. ```bash
  12. npm i -D unplugin-vue-router
  13. ```
  14. Add VueRouter plugin **before** Vue plugin:
  15. <details>
  16. <summary>Vite</summary><br>
  17. ```ts
  18. // vite.config.ts
  19. import VueRouter from 'unplugin-vue-router/vite'
  20. export default defineConfig({
  21. plugins: [
  22. VueRouter({
  23. /* options */
  24. }),
  25. // ⚠️ Vue must be placed after VueRouter()
  26. Vue(),
  27. ],
  28. })
  29. ```
  30. Example: [`playground/`](./playground/)
  31. <br></details>
  32. <details>
  33. <summary>Rollup</summary><br>
  34. ```ts
  35. // rollup.config.js
  36. import VueRouter from 'unplugin-vue-router/rollup'
  37. export default {
  38. plugins: [
  39. VueRouter({
  40. /* options */
  41. }),
  42. // ⚠️ Vue must be placed after VueRouter()
  43. Vue(),
  44. ],
  45. }
  46. ```
  47. <br></details>
  48. <details>
  49. <summary>Webpack</summary><br>
  50. ```ts
  51. // webpack.config.js
  52. module.exports = {
  53. /* ... */
  54. plugins: [
  55. require('unplugin-vue-router/webpack')({
  56. /* options */
  57. }),
  58. ],
  59. }
  60. ```
  61. <br></details>
  62. <details>
  63. <summary>Vue CLI</summary><br>
  64. ```ts
  65. // vue.config.js
  66. module.exports = {
  67. configureWebpack: {
  68. plugins: [
  69. require('unplugin-vue-router/webpack')({
  70. /* options */
  71. }),
  72. ],
  73. },
  74. }
  75. ```
  76. <br></details>
  77. <details>
  78. <summary>esbuild</summary><br>
  79. ```ts
  80. // esbuild.config.js
  81. import { build } from 'esbuild'
  82. import VueRouter from 'unplugin-vue-router/esbuild'
  83. build({
  84. plugins: [VueRouter()],
  85. })
  86. ```
  87. <br></details>
  88. ## Setup
  89. After installing, **you should run your dev server** (usually `npm run dev`) **to generate the first version of the types**. Then, you should replace your imports from `vue-router` to `vue-router/auto`:
  90. ```diff
  91. -import { createRouter, createWebHistory } from 'vue-router'
  92. +import { createRouter, createWebHistory } from 'vue-router/auto'
  93. createRouter({
  94. history: createWebHistory(),
  95. // You don't need to pass the routes anymore,
  96. // the plugin writes it for you 🤖
  97. })
  98. ```
  99. > **Note**
  100. > You can exclude `vue-router` from VSCode import suggestions by adding this setting to your `.vscode/settings.json`:
  101. ```json
  102. {
  103. "typescript.preferences.autoImportFileExcludePatterns": [
  104. "vue-router"
  105. ]
  106. }
  107. ```
  108. This will ensure VSCode only suggests `vue-router/auto` for imports. Alternatively, you can also configure [auto imports](#auto-imports).
  109. Alternatively, **you can also import the `routes` array** and create the router manually or pass it to some plugin. Here is an example with [Vitesse starter](https://github.com/antfu/vitesse/blob/main/src/main.ts):
  110. <!-- TODO: add notes for data fetching guards -->
  111. ```diff
  112. import { ViteSSG } from 'vite-ssg'
  113. import { setupLayouts } from 'virtual:generated-layouts'
  114. import App from './App.vue'
  115. import type { UserModule } from './types'
  116. -import generatedRoutes from '~pages'
  117. +import { routes } from 'vue-router/auto/routes'
  118. import '@unocss/reset/tailwind.css'
  119. import './styles/main.css'
  120. import 'uno.css'
  121. -const routes = setupLayouts(generatedRoutes)
  122. // https://github.com/antfu/vite-ssg
  123. export const createApp = ViteSSG(
  124. App,
  125. {
  126. - routes,
  127. + routes: setupLayouts(routes),
  128. base: import.meta.env.BASE_URL
  129. },
  130. (ctx) => {
  131. // install all modules under `modules/`
  132. Object.values(import.meta.glob<{ install: UserModule }>('./modules/*.ts', { eager: true }))
  133. .forEach(i => i.install?.(ctx))
  134. },
  135. )
  136. ```
  137. ### Auto Imports
  138. If you are using [unplugin-auto-import](https://github.com/antfu/unplugin-auto-import), make sure to remove the `vue-router` preset and use the one exported by `unplugin-vue-router`:
  139. ```diff
  140. import { defineConfig } from 'vite'
  141. import AutoImport from 'unplugin-auto-import/vite'
  142. +import { VueRouterAutoImports } from 'unplugin-vue-router'
  143. export default defineConfig({
  144. plugins: [
  145. // other plugins
  146. AutoImport({
  147. imports: [
  148. - 'vue-router',
  149. + VueRouterAutoImports,
  150. ],
  151. }),
  152. ],
  153. })
  154. ```
  155. Note that the `vue-router` preset might export less things than the one exported by `unplugin-vue-router` so you might need to add any other imports you were relying on manually:
  156. ```diff
  157. AutoImport({
  158. imports: [
  159. - 'vue-router',
  160. + VueRouterAutoImports,
  161. + {
  162. + // add any other imports you were relying on
  163. + 'vue-router/auto': ['useLink']
  164. + },
  165. ],
  166. }),
  167. ```
  168. Make sure to also check and follow [the TypeScript section](#typescript) below **if you are using TypeScript or have a `jsconfig.json` file**.
  169. ## Configuration
  170. Have a glimpse of all the existing configuration options with their corresponding **default values**:
  171. ```ts
  172. VueRouter({
  173. // Folder(s) to scan for vue components and generate routes. Can be a string, or
  174. // an object, or an array of those.
  175. routesFolder: 'src/pages',
  176. // allowed extensions to be considered as routes
  177. extensions: ['.vue'],
  178. // list of glob files to exclude from the routes generation
  179. // e.g. ['**/__*'] will exclude all files and folders starting with `__`
  180. // e.g. ['**/__*/**/*'] will exclude all files within folders starting with `__`
  181. // e.g. ['*.component.vue'] will exclude components ending with `.component.vue`
  182. // note you can exclude patterns with a leading `!`:
  183. // '!__not-ignored', -> __not-ignored will still be used as a page
  184. exclude: [],
  185. // Path for the generated types. Defaults to `./typed-router.d.ts` if typescript
  186. // is installed. Can be disabled by passing `false`.
  187. dts: './typed-router.d.ts',
  188. // Override the name generation of routes. unplugin-vue-router exports two versions:
  189. // `getFileBasedRouteName()` (the default) and `getPascalCaseRouteName()`. Import any
  190. // of them within your `vite.config.ts` file.
  191. getRouteName: (routeNode) => myOwnGenerateRouteName(routeNode),
  192. // Customizes the default langage for `<route>` blocks
  193. // json5 is just a more permissive version of json
  194. routeBlockLang: 'json5',
  195. // Change the import mode of page components. Can be 'async', 'sync', or a function with the following signature:
  196. // (filepath: string) => 'async' | 'sync'
  197. importMode: 'async',
  198. })
  199. ```
  200. ## Routes folder structure
  201. 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!**
  202. Let's take a look at a simple example:
  203. ```text
  204. src/pages/
  205. ├── index.vue
  206. ├── about.vue
  207. └── users/
  208. ├── index.vue
  209. └── [id].vue
  210. ```
  211. This will generate the following routes:
  212. - `/`: -> renders the `index.vue` component
  213. - `/about`: -> renders the `about.vue` component
  214. - `/users`: -> renders the `users/index.vue` component
  215. - `/users/:id`: -> renders the `users/[id].vue` component. `id` becomes a route param.
  216. ### Index Routes
  217. Any `index.vue` file will generate an empty path (similar to `index.html` files):
  218. - `src/pages/index.vue`: generates a `/` route
  219. - `src/pages/users/index.vue`: generates a `/users` route
  220. ### Nested Routes
  221. Nested routes are automatically defined by defining a `.vue` file alongside a folder **with the same name**. If you create both a `src/pages/users/index.vue` and a `src/pages/users.vue` components, the `src/pages/users/index.vue` will be rendered within the `src/pages/users.vue`'s `<RouterView>`.
  222. In other words, given this folder structure:
  223. ```text
  224. src/pages/
  225. ├── users/
  226. │ └── index.vue
  227. └── users.vue
  228. ```
  229. You will get this `routes` array:
  230. ```js
  231. const routes = [
  232. {
  233. path: '/users',
  234. component: () => import('src/pages/users.vue'),
  235. children: [
  236. { path: '', component: () => import('src/pages/users/index.vue') },
  237. ],
  238. },
  239. ]
  240. ```
  241. While omitting the `src/pages/users.vue` component will generate the following routes:
  242. ```js
  243. const routes = [
  244. {
  245. path: '/users',
  246. // notice how there is no component here
  247. children: [
  248. { path: '', component: () => import('src/pages/users/index.vue') },
  249. ],
  250. },
  251. ]
  252. ```
  253. Note the folder and file's name `users/` could be any valid naming like `my-[id]-param/`.
  254. #### Nested routes without nesting layouts
  255. Sometimes you might want to add _nesting to the URL_ in the form of slashes but you don't want it to impact your UI hierarchy. Consider the following folder structure:
  256. ```text
  257. src/pages/
  258. ├── users/
  259. │ ├── [id].vue
  260. │ └── index.vue
  261. └── users.vue
  262. ```
  263. If you want to add a new route `/users/create` you could add a new file `src/pages/users/create.vue` but that would nest the `create.vue` component within the `users.vue` component. To avoid this you can instead create a file `src/pages/users.create.vue`. The `.` will become a `/` when generating the routes:
  264. ```js
  265. const routes = [
  266. {
  267. path: '/users',
  268. component: () => import('src/pages/users.vue'),
  269. children: [
  270. { path: '', component: () => import('src/pages/users/index.vue') },
  271. { path: ':id', component: () => import('src/pages/users/[id].vue') },
  272. ],
  273. },
  274. {
  275. path: '/users/create',
  276. component: () => import('src/pages/users.create.vue'),
  277. },
  278. ]
  279. ```
  280. ### Named routes
  281. All generated routes that have a `component` property will have a `name` property. This avoid accidentally directing your users to a parent route. By default, names are generated using the file path, but you can override this behavior by passing a custom `getRouteName()` function. You will get TypeScript validation almost everywhere, so changing this should be easy.
  282. ### Dynamic Routes
  283. You can add [route params](https://router.vuejs.org/guide/essentials/dynamic-matching.html) by wrapping the _param name_ with brackets, e.g. `src/pages/users/[id].vue` will create a route with the following path: `/users/:id`.
  284. You can create [**optional params**](https://router.vuejs.org/guide/essentials/route-matching-syntax.html#optional-parameters) by wrapping the _param name_ with an extra pair of brackets, e.g. `src/pages/users/[[id]].vue` will create a route with the following path: `/users/:id?`.
  285. You can create [**repeatable params**](https://router.vuejs.org/guide/essentials/route-matching-syntax.html#repeatable-params) by adding a plus character (`+`) after the closing bracket, e.g. `src/pages/articles/[slugs]+.vue` will create a route with the following path: `/articles/:slugs+`.
  286. And you can combine both to create optional repeatable params, e.g. `src/pages/articles/[[slugs]]+.vue` will create a route with the following path: `/articles/:slugs*`.
  287. ### Catch all / 404 Not found route
  288. To create a catch all route prepend 3 dots (`...`) to the param name, e.g. `src/pages/[...path].vue` will create a route with the following path: `/:path(.*)`. This will match any route. Note this can be done inside a folder too, e.g. `src/pages/articles/[...path].vue` will create a route with the following path: `/articles/:path(.*)`.
  289. ### Multiple routes folders
  290. It's possible to provide multiple routes folders by passing an array to `routesFolder`:
  291. ```js
  292. VueRouter({
  293. routesFolder: ['src/pages', 'src/admin/routes'],
  294. })
  295. ```
  296. You can also provide a path prefix for each of these folders, it will be used _as is_, and **cannot start with a `/`** but can contain any params you want or even **not finish with a `/`**:
  297. ```js
  298. VueRouter({
  299. routesFolder: [
  300. 'src/pages',
  301. {
  302. src: 'src/admin/routes',
  303. // note there is always a trailing slash and never a leading one
  304. path: 'admin/',
  305. // src/admin/routes/dashboard.vue -> /admin/dashboard
  306. },
  307. {
  308. src: 'src/docs',
  309. // you can add parameters
  310. path: 'docs/:lang/',
  311. // src/docs/introduction.vue -> /docs/:lang/introduction
  312. },
  313. {
  314. src: 'src/promos',
  315. // you can omit the trailing slash
  316. path: 'promos-',
  317. // src/promos/black-friday.vue -> /promos-black-friday
  318. },
  319. ],
  320. })
  321. ```
  322. Note that the provided folders must be separate and one _route folder_ cannot contain another specified _route folder_. If you need further customization, give [definePage()](#definepage-in-script) a try.
  323. ## TypeScript
  324. This plugin generates a `d.ts` file with all the typing overrides when the dev or build server is ran. Make sure to include it in your `tsconfig.json`'s (or `jsconfig.json`'s) `include` or `files` property:
  325. ```js
  326. {
  327. // ...
  328. "include": [/* ... */ "typed-router.d.ts"]
  329. // ...
  330. }
  331. ```
  332. Then, you will be able to import from `vue-router/auto` (instead of `vue-router`) to get access to the typed APIs. You can commit the `typed-router.d.ts` file to your repository to make your life easier.
  333. ### Extra types
  334. You can always take a look at the generated `typed-router.d.ts` file to inspect what are the generated types. `unplugin-vue-router` improves upon many of the existing types in `vue-router` and adds a few ones as well:
  335. #### `RouteNamedMap`
  336. The `RouteNamedMap` interface gives you access to all the metadata associated with a route. It can also be extended to enable types for **dynamic routes** that are added during runtime.
  337. ```ts
  338. import type { RouteNamedMap } from 'vue-router/auto/routes'
  339. ```
  340. Extending types with dynamically added routes:
  341. ```ts
  342. declare module 'vue-router/auto/routes' {
  343. import type {
  344. RouteRecordInfo,
  345. ParamValue,
  346. // these are other param helper types
  347. ParamValueOneOrMore,
  348. ParamValueZeroOrMore,
  349. ParamValueZeroOrOne,
  350. } from 'unplugin-vue-router'
  351. export interface RouteNamedMap {
  352. // the key is the name and should match the first generic of RouteRecordInfo
  353. 'custom-dynamic-name': RouteRecordInfo<
  354. 'custom-dynamic-name',
  355. '/added-during-runtime/[...path]',
  356. // these are the raw param types (accept numbers, strings, booleans, etc)
  357. { path: ParamValue<true> },
  358. // these are the normalized params as found in useRoute().params
  359. { path: ParamValue<false> }
  360. >
  361. }
  362. }
  363. ```
  364. #### `RouterTyped`
  365. The `RouterTyped` type gives you access to the typed version of the router instance. It's also the _ReturnType_ of the `useRouter()` function.
  366. ```ts
  367. import type { RouterTyped } from 'vue-router/auto'
  368. ```
  369. #### `RouteLocationResolved`
  370. The `RouteLocationResolved` type exposed by `vue-router/auto` allows passing a generic (which autocomplete) to type a route **whenever checking the name doesn't makes sense because you know the type**. This is useful for cases like `<RouterLink v-slot="{ route }">`:
  371. ```vue
  372. <RouterLink v-slot="{ route }">
  373. User {{ (route as RouteLocationResolved<'/users/[id]'>).params.id }}
  374. </RouterLink>
  375. ```
  376. This type is also the return type of `router.resolve()`.
  377. You have the same equivalents for `RouteLocation`, `RouteLocationNormalized`, and `RouteLocationNormalizedLoaded`. All of them exist in `vue-router` but `vue-router/auto` override them to provide a type safe version of them. In addition to that, you can pass the name of the route as a generic:
  378. ```ts
  379. // these are all valid
  380. let userWithId: RouteLocationNormalizedLoaded<'/users/[id]'> = useRoute()
  381. userWithId = useRoute<'/users/[id]'>()
  382. // 👇 this one is the easiest to write because it autocomplete
  383. userWithId = useRoute('/users/[id]')
  384. ```
  385. ## Named views
  386. It is possible to define [named views](https://router.vuejs.org/guide/essentials/named-views.html#named-views) by appending an `@` + a name to their filename, e.g. a file named `src/pages/index@aux.vue` will generate a route of:
  387. ```js
  388. {
  389. path: '/',
  390. component: {
  391. aux: () => import('src/pages/index@aux.vue')
  392. }
  393. }
  394. ```
  395. Note that by default a non named route is named `default` and that you don't need to name your file `index@default.vue` even if there are other named views (e.g. having `index@aux.vue` and `index.vue` is the same as having `index@aux.vue` and `index@default.vue`).
  396. ## Extending existing routes
  397. ### `definePage()` in `<script>`
  398. The macro `definePage()` allows you to define any extra properties related to the route. It is useful when you need to customize the `path`, the `name`, `meta`, etc
  399. ```vue
  400. <script setup>
  401. definePage({
  402. name: 'my-own-name',
  403. path: '/absolute-with-:param',
  404. alias: ['/a/:param'],
  405. meta: {
  406. custom: 'data',
  407. },
  408. })
  409. </script>
  410. ```
  411. Note you cannot use variables in `definePage()` as its passed parameter gets extracted at build time and is removed from `<script setup>`. You can also use [the `<route>` block](#sfc-route-custom-block) which allows other formats like yaml.
  412. ### SFC `<route>` custom block
  413. The `<route>` custom block is a way to extend existing routes. It can be used to add new `meta` fields, override the `path`, the `name`, or anything else in a route. **It has to be added to a `.vue` component inside of the [routes folder](#routes-folder-structure)**. It is similar to [the same feature in vite-plugin-pages](https://github.com/hannoeru/vite-plugin-pages#sfc-custom-block-for-route-data) to facilitate migration.
  414. ```vue
  415. <route lang="json">
  416. {
  417. "name": "name-override",
  418. "meta": {
  419. "requiresAuth": false
  420. }
  421. }
  422. </route>
  423. ```
  424. Note you can specify the language to use with `<route lang="yaml">`. By default, the language is JSON5 (more flexible version of JSON) but yaml and JSON are also supported. **This will also add Syntax Highlighting**.
  425. ### `extendRoutes()`
  426. You can extend existing routes by passing an `extendRoutes` function to `createRouter()`. **This should be used as a last resort** (or until a feature is natively available here):
  427. ```js
  428. import { createWebHistory, createRouter } from 'vue-router/auto'
  429. const router = createRouter({
  430. extendRoutes: (routes) => {
  431. const adminRoute = routes.find((r) => r.name === '/admin')
  432. if (adminRoute) {
  433. adminRoute.meta ??= {}
  434. adminRoute.meta.requiresAuth = true
  435. }
  436. // completely optional since we are modifying the routes in place
  437. return routes
  438. },
  439. history: createWebHistory(),
  440. })
  441. ```
  442. As this plugin evolves, this function should be used less and less and only become necessary in unique edge cases.
  443. One example of this is using [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) which can only be used alongside `extendRoutes()`:
  444. ```ts
  445. import { createRouter } from 'vue-router/auto'
  446. import { setupLayouts } from 'virtual:generated-layouts'
  447. const router = createRouter({
  448. // ...
  449. extendRoutes: (routes) => setupLayouts(routes),
  450. })
  451. ```
  452. ## Rationale
  453. This project idea came from trying [to type the router directly using Typescript](https://github.com/vuejs/router/pull/1397/commits/a7c591b6fd5d8478ba3f87e833514bc0e30f93a9), finding out it's not fast enough to be pleasant to use and, ending up using build-based tools, taking some inspiration from other projects like:
  454. - [Nuxt](https://nuxtjs.org/) - The Vue.js Framework
  455. - [vite-plugin-pages](https://github.com/hannoeru/vite-plugin-pages) - Framework agnostic file based routing
  456. - [Typed Router for Nuxt](https://github.com/victorgarciaesgi/nuxt-typed-router) - A module to add typed routing to Nuxt
  457. ## License
  458. [MIT](http://opensource.org/licenses/MIT)