@zphhpzzph/vue-route-gen 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,8 @@ Vue 3 基于文件系统的路由生成器,为 Vue Router 提供完整的类
13
13
  - TypeScript 支持并生成完整类型
14
14
  - **类型安全的路由 Hooks**(`useRoute` 和 `useRouter` 提供完整类型推断)
15
15
  - **自动从动态路由提取参数类型**
16
+ - **`<route>` 自定义块支持** - 在 SFC 中定义路由元数据,零运行时开销
17
+ - **精确的字面量类型推断** - 为路由元数据提供编译时类型安全,详见 [字面量类型推断文档](./docs/LiteralTypes.md)
16
18
 
17
19
  ## 安装
18
20
 
@@ -63,7 +65,71 @@ src/pages/
63
65
 
64
66
  ## 配置选项
65
67
 
66
- ### 选项
68
+ ### Vite 配置
69
+
70
+ `@zphhpzzph/vue-route-gen` 提供了两个 Vite 插件:
71
+
72
+ #### 1. `routeBlockPlugin()` - 处理路由自定义块(必需)
73
+
74
+ 必须添加,用于移除 `<route>` 自定义块和 `defineRoute()` 宏:
75
+
76
+ ```typescript
77
+ // vite.config.ts
78
+ import { defineConfig } from 'vite';
79
+ import vue from '@vitejs/plugin-vue';
80
+ import { routeBlockPlugin } from '@zphhpzzph/vue-route-gen/vite';
81
+
82
+ export default defineConfig({
83
+ plugins: [
84
+ routeBlockPlugin(), // 处理 <route> 自定义块和 defineRoute()
85
+ vue(),
86
+ ],
87
+ });
88
+ ```
89
+
90
+ **注意**:`routeBlockPlugin` 会移除 `<route>` 自定义块,因为这些块已经在构建时被 `vue-route-gen` 提取并合并到路由配置中,不需要在运行时处理。
91
+
92
+ #### 2. `routeGenPlugin()` - 自动生成路由(推荐)
93
+
94
+ **开发体验优化**:自动监听文件变化并重新生成路由,无需手动运行命令。
95
+
96
+ ```typescript
97
+ // vite.config.ts
98
+ import { defineConfig } from 'vite';
99
+ import vue from '@vitejs/plugin-vue';
100
+ import { routeBlockPlugin, routeGenPlugin } from '@zphhpzzph/vue-route-gen/vite';
101
+
102
+ export default defineConfig({
103
+ plugins: [
104
+ routeBlockPlugin(), // 1. 最先执行
105
+ routeGenPlugin(), // 2. 自动生成路由
106
+ vue(), // 3. Vue 插件
107
+ ],
108
+ });
109
+ ```
110
+
111
+ **功能特性**:
112
+
113
+ - ✅ 开发服务器启动时自动生成路由
114
+ - ✅ 监听 `pages` 目录的文件变化
115
+ - ✅ 智能判断是否需要重新生成(避免不必要的重建)
116
+ - ✅ 自动触发 HMR 更新
117
+ - ✅ 支持自定义配置
118
+
119
+ **自定义配置**:
120
+
121
+ ```typescript
122
+ routeGenPlugin({
123
+ pagesDir: './src/pages', // 可选,默认 'src/pages'
124
+ outFile: './src/router/route.gen.ts', // 可选,默认 'src/router/route.gen.ts'
125
+ })
126
+ ```
127
+
128
+ **插件顺序**:`routeBlockPlugin()` → `routeGenPlugin()` → `vue()`
129
+
130
+ 详细文档:[Vite 插件使用指南](./docs/VitePlugin.md)
131
+
132
+ ### 生成器选项
67
133
 
68
134
  - `pagesDir`: 页面目录路径(默认:`src/pages`)
69
135
  - `outFile`: 输出文件路径(默认:`src/router/route.gen.ts`)
@@ -87,9 +153,306 @@ src/pages/
87
153
  3. `ROUTE_PATH_BY_NAME` - 按名称查找路径
88
154
  4. `RouteParams` - 每个路由的参数类型接口
89
155
  5. `RouteParamsByName<T>` - 根据路由名称获取参数类型的工具类型
90
- 6. `routes` - Vue Router 路由记录数组
91
- 7. `useRoute()` - 类型安全的路由访问 Hook,提供参数类型推断
92
- 8. `useRouter()` - 类型安全的路由导航 Hook,提供参数验证
156
+ 6. `RouteMetaMap` - 每个路由的元数据类型接口(从 `<route>` 块提取)
157
+ 7. `RouteMetaByName<T>` - 根据路由名称获取元数据类型的工具类型
158
+ 8. `routes` - Vue Router 路由记录数组
159
+ 9. `useRoute()` - 类型安全的路由访问 Hook,提供参数和元数据类型推断
160
+ 10. `useRouter()` - 类型安全的路由导航 Hook,提供参数验证
161
+
162
+ ## 使用 `<route>` 自定义块
163
+
164
+ `@zphhpzzph/vue-route-gen` 支持在 Vue SFC 文件中使用 `<route>` 自定义块来定义路由元数据。这些元数据会在**构建时**被提取并合并到生成的路由配置中,**零运行时开销**。
165
+
166
+ ### 基础用法
167
+
168
+ 在 Vue 组件中添加 `<route>` 自定义块:
169
+
170
+ ```vue
171
+ <template>
172
+ <div>
173
+ <h1>用户列表</h1>
174
+ </div>
175
+ </template>
176
+
177
+ <script setup lang="ts">
178
+ // 组件逻辑
179
+ </script>
180
+
181
+ <route>
182
+ {
183
+ "title": "用户列表",
184
+ "layout": "admin",
185
+ "requiresAuth": true,
186
+ "roles": ["admin", "moderator"]
187
+ }
188
+ </route>
189
+ ```
190
+
191
+ ### 支持的元数据属性
192
+
193
+ | 属性 | 类型 | 说明 |
194
+ |------|------|------|
195
+ | `title` | `string` | 页面标题,用于 document.title 和面包屑 |
196
+ | `layout` | `string \| false` | 布局组件名称,或 `false` 禁用布局 |
197
+ | `keepAlive` | `boolean` | 是否缓存页面组件 |
198
+ | `requiresAuth` | `boolean` | 是否需要认证 |
199
+ | `roles` | `string[]` | 允许访问的用户角色 |
200
+ | `redirect` | `string \| { name: string }` | 重定向配置 |
201
+ | `icon` | `string` | 菜单图标(自定义属性) |
202
+ | `hidden` | `boolean` | 是否隐藏菜单(自定义属性) |
203
+ | `*` | `any` | 支持任何自定义属性 |
204
+
205
+ ### JSON 和 JavaScript 对象语法
206
+
207
+ `<route>` 块支持两种语法:
208
+
209
+ #### 1. JSON 语法(推荐)
210
+
211
+ ```vue
212
+ <route>
213
+ {
214
+ "title": "用户详情",
215
+ "layout": "admin",
216
+ "requiresAuth": true,
217
+ "roles": ["admin"]
218
+ }
219
+ </route>
220
+ ```
221
+
222
+ #### 2. JavaScript 对象语法
223
+
224
+ ```vue
225
+ <route>
226
+ {
227
+ title: '用户详情',
228
+ layout: 'admin',
229
+ requiresAuth: true,
230
+ roles: ['admin', 'moderator']
231
+ }
232
+ </route>
233
+ ```
234
+
235
+ ### 完整示例
236
+
237
+ #### 用户列表页面
238
+
239
+ ```vue
240
+ <!-- src/pages/users/index.vue -->
241
+ <template>
242
+ <div class="users-page">
243
+ <h1>用户列表</h1>
244
+ <UserList />
245
+ </div>
246
+ </template>
247
+
248
+ <script setup lang="ts">
249
+ import UserList from './components/UserList.vue';
250
+ </script>
251
+
252
+ <route>
253
+ {
254
+ "title": "用户列表",
255
+ "layout": "admin",
256
+ "requiresAuth": true,
257
+ "roles": ["admin"],
258
+ "icon": "User",
259
+ "keepAlive": true
260
+ }
261
+ </route>
262
+ ```
263
+
264
+ #### 用户详情页面
265
+
266
+ ```vue
267
+ <!-- src/pages/users/[id].vue -->
268
+ <template>
269
+ <div class="user-detail">
270
+ <h1>用户详情</h1>
271
+ <UserInfo :user-id="userId" />
272
+ </div>
273
+ </template>
274
+
275
+ <script setup lang="ts">
276
+ import { computed } from 'vue';
277
+ import { useRoute } from '@/router/route.gen';
278
+ import UserInfo from './components/UserInfo.vue';
279
+
280
+ const route = useRoute<'users-[id]>();
281
+ const userId = computed(() => route.params.id);
282
+ </script>
283
+
284
+ <route>
285
+ {
286
+ "title": "用户详情",
287
+ "layout": "admin",
288
+ "requiresAuth": true,
289
+ "roles": ["admin", "moderator"],
290
+ "icon": "UserProfile"
291
+ }
292
+ </route>
293
+ ```
294
+
295
+ #### 公开页面(无需认证)
296
+
297
+ ```vue
298
+ <!-- src/pages/about.vue -->
299
+ <template>
300
+ <div class="about-page">
301
+ <h1>关于我们</h1>
302
+ <p>这是关于页面</p>
303
+ </div>
304
+ </template>
305
+
306
+ <script setup lang="ts">
307
+ // 组件逻辑
308
+ </script>
309
+
310
+ <route>
311
+ {
312
+ "title": "关于我们",
313
+ "layout": "default",
314
+ "requiresAuth": false,
315
+ "keepAlive": false
316
+ }
317
+ </route>
318
+ ```
319
+
320
+ ### 自定义元数据属性
321
+
322
+ 你可以在 `<route>` 块中添加任何自定义属性,并通过 TypeScript 模块扩展来获得类型支持:
323
+
324
+ ```typescript
325
+ // types/route-meta.d.ts
326
+ declare module '@zphhpzzph/vue-route-gen/runtime' {
327
+ interface RouteMeta {
328
+ icon?: string;
329
+ hidden?: boolean;
330
+ order?: number;
331
+ badge?: string | number;
332
+ }
333
+ }
334
+ ```
335
+
336
+ 然后在组件中使用:
337
+
338
+ ```vue
339
+ <route>
340
+ {
341
+ "title": "仪表盘",
342
+ "icon": "Dashboard",
343
+ "hidden": false,
344
+ "order": 1,
345
+ "badge": "New"
346
+ }
347
+ </route>
348
+ ```
349
+
350
+ ### 构建时提取
351
+
352
+ 路由元数据在构建时被提取,零运行时开销:
353
+
354
+ ```typescript
355
+ // 生成的 route.gen.ts
356
+ export const routes = [
357
+ {
358
+ path: "/users/:id",
359
+ name: "users-[id]",
360
+ component: () => import("../pages/users/[id].vue"),
361
+ meta: {
362
+ title: "用户详情",
363
+ layout: "admin",
364
+ requiresAuth: true,
365
+ roles: ["admin", "moderator"],
366
+ icon: "UserProfile"
367
+ },
368
+ children: [],
369
+ }
370
+ ] satisfies RouteRecordRaw[];
371
+ ```
372
+
373
+ ### 在路由守卫中使用元数据
374
+
375
+ ```typescript
376
+ // router/guards.ts
377
+ import { router } from './router';
378
+
379
+ router.beforeEach((to, from, next) => {
380
+ const meta = to.meta;
381
+
382
+ // 检查认证
383
+ if (meta.requiresAuth && !isAuthenticated()) {
384
+ return next({ name: 'login' });
385
+ }
386
+
387
+ // 检查角色权限
388
+ if (meta.roles && !hasRole(meta.roles)) {
389
+ return next({ name: 'forbidden' });
390
+ }
391
+
392
+ // 设置页面标题
393
+ if (meta.title) {
394
+ document.title = `${meta.title} - My App`;
395
+ }
396
+
397
+ next();
398
+ });
399
+ ```
400
+
401
+ ### 与导航菜单结合
402
+
403
+ ```vue
404
+ <!-- components/Sidebar.vue -->
405
+ <script setup lang="ts">
406
+ import { routes } from '@/router/route.gen';
407
+
408
+ const menuItems = routes
409
+ .filter(route => route.meta && !route.meta.hidden)
410
+ .map(route => ({
411
+ title: route.meta?.title,
412
+ icon: route.meta?.icon,
413
+ path: route.path,
414
+ order: route.meta?.order ?? 999,
415
+ }))
416
+ .sort((a, b) => a.order - b.order);
417
+ </script>
418
+
419
+ <template>
420
+ <nav>
421
+ <router-link
422
+ v-for="item in menuItems"
423
+ :key="item.path"
424
+ :to="item.path"
425
+ >
426
+ <Icon :name="item.icon" />
427
+ {{ item.title }}
428
+ </router-link>
429
+ </nav>
430
+ </template>
431
+ ```
432
+
433
+ ### 类型安全扩展
434
+
435
+ 通过 TypeScript 模块扩展,让你的自定义元数据属性类型安全:
436
+
437
+ ```typescript
438
+ // types/route-meta.d.ts
439
+ import '@zphhpzzph/vue-route-gen/runtime';
440
+
441
+ declare module '@zphhpzzph/vue-route-gen/runtime' {
442
+ interface RouteMeta {
443
+ // 页面权限
444
+ permissions?: string[];
445
+ // 页面描述
446
+ description?: string;
447
+ // SEO 关键词
448
+ keywords?: string[];
449
+ // 是否在标签页中打开
450
+ openInTab?: boolean;
451
+ // 自定义中间件
452
+ middleware?: string[];
453
+ }
454
+ }
455
+ ```
93
456
 
94
457
  ## 类型安全的路由
95
458
 
@@ -100,7 +463,7 @@ src/pages/
100
463
  ```typescript
101
464
  // 在 route.gen.ts 中生成
102
465
  export interface RouteParams {
103
- 'users-id': {
466
+ 'users-[id]': {
104
467
  id: string;
105
468
  };
106
469
  // ... 其他路由
@@ -109,19 +472,49 @@ export interface RouteParams {
109
472
 
110
473
  ### 使用 useRoute
111
474
 
112
- 生成的 `useRoute` Hook 为路由参数提供完整的类型推断:
475
+ 生成的 `useRoute` Hook 为路由参数和元数据提供完整的类型推断:
113
476
 
114
477
  ```typescript
115
478
  import { useRoute, ROUTE_NAME } from '@/router/route.gen';
116
479
 
117
- const route = useRoute<'users-id'>();
118
- // route.params.id 的类型为 `string`
480
+ const route = useRoute();
119
481
 
482
+ // 类型安全的参数访问
120
483
  if (route.name === ROUTE_NAME.USERS_ID) {
121
- console.log(route.params.id); // 完整类型支持!
484
+ console.log(route.params.id); // 类型为 string ✅
485
+
486
+ // 类型安全的元数据访问
487
+ console.log(route.meta.title); // 类型为 string ✅
488
+ console.log(route.meta.layout); // 类型为 string ✅
489
+ console.log(route.meta.requiresAuth); // 类型为 boolean ✅
490
+ console.log(route.meta.roles); // 类型为 string[] ✅
491
+
492
+ // ❌ TypeScript 报错:属性不存在
493
+ // console.log(route.meta.wrongProp);
122
494
  }
123
495
  ```
124
496
 
497
+ **Meta 类型自动推导**:
498
+ - 从 `<route>` 块中提取的元数据会自动生成对应的 TypeScript 类型
499
+ - 不同路由有不同的 meta 类型
500
+ - 访问不存在的 meta 属性时 TypeScript 会报错
501
+
502
+ **获取特定路由的 Meta 类型**:
503
+
504
+ ```typescript
505
+ import type { RouteMetaByName } from '@/router/route.gen';
506
+
507
+ // 获取特定路由的 meta 类型
508
+ type UsersIdMeta = RouteMetaByName<typeof ROUTE_NAME.USERS_ID>;
509
+ // 类型为:
510
+ // {
511
+ // title: string;
512
+ // layout: string;
513
+ // requiresAuth: true;
514
+ // roles: string[];
515
+ // } & RouteMeta
516
+ ```
517
+
125
518
  ### 使用 useRouter
126
519
 
127
520
  生成的 `useRouter` Hook 提供类型安全的导航:
@@ -216,6 +609,54 @@ function navigateToUser(userId: string) {
216
609
 
217
610
  ## 高级用法
218
611
 
612
+ ### 类型工具(从 `@zphhpzzph/vue-route-gen` 导入)
613
+
614
+ 包中提供了一些高级类型工具用于自定义类型安全的路由 hooks:
615
+
616
+ ```typescript
617
+ import {
618
+ createTypedUseRoute,
619
+ createTypedUseRouter,
620
+ type TypedRoute,
621
+ type TypedRouter,
622
+ type TypedRouteLocation,
623
+ } from '@zphhpzzph/vue-route-gen';
624
+ ```
625
+
626
+ #### 创建自定义类型安全的 useRoute Hook
627
+
628
+ 如果需要为特定路由创建类型更精确的 hook:
629
+
630
+ ```typescript
631
+ import { createTypedUseRoute } from '@zphhpzzph/vue-route-gen';
632
+
633
+ // 创建针对特定路由的 hook
634
+ const useUserDetailRoute = createTypedUseRoute<'users-[id]', { id: string }>();
635
+
636
+ // 在组件中使用
637
+ const route = useUserDetailRoute();
638
+ console.log(route.params.id); // 类型为 string
639
+ ```
640
+
641
+ #### 创建类型安全的 Router
642
+
643
+ ```typescript
644
+ import { createTypedUseRouter } from '@zphhpzzph/vue-route-gen';
645
+
646
+ const useRouter = createTypedUseRouter();
647
+ const router = useRouter();
648
+
649
+ // 导航时提供类型检查
650
+ router.push({
651
+ name: 'users-[id]',
652
+ params: { id: '123' },
653
+ });
654
+ ```
655
+
656
+ **注意**:大多数情况下,你应该使用生成的 `useRoute()` 和 `useRouter()` hooks(从 `route.gen.ts` 导入),它们已经提供了完整的类型安全。这些底层类型工具主要用于高级自定义场景。
657
+
658
+ > 💡 **深入阅读**:更多高级用法和完整文档,请参阅[文档索引](./docs/README.md)
659
+
219
660
  ### 获取特定路由的参数类型
220
661
 
221
662
  ```typescript
@@ -248,6 +689,15 @@ src/pages/
248
689
  1. **始终使用生成的常量**:使用 `ROUTE_NAME` 而不是硬编码字符串
249
690
  2. **利用类型推断**:让 TypeScript 为你检查路由参数
250
691
  3. **组合使用 Hooks**:`useRoute` 和 `useRouter` 提供完整的类型安全
692
+ 4. **使用 `<route>` 块定义元数据**:在组件中直接定义路由元数据,便于维护
693
+
694
+ ## 📚 文档
695
+
696
+ 查看完整文档:
697
+ - **[文档索引](./docs/README.md)** - 所有文档���导航目录
698
+ - **[更新日志](./CHANGELOG.md)** - 版本更新记录和迁移指南
699
+ - **[路由元数据字面量类型推断](./docs/LiteralTypes.md)** - 精确的类型推断系统详解
700
+ - **[Vite 插件使用指南](./docs/VitePlugin.md)** - 自动路由生成和智能更新
251
701
 
252
702
  ## 发布新版本(维护者)
253
703
 
package/dist/cli.js CHANGED
File without changes
@@ -1,17 +1,33 @@
1
- export interface RouteMeta {
2
- title?: string;
3
- layout?: string | false;
4
- keepAlive?: boolean;
5
- requiresAuth?: boolean;
6
- roles?: string[];
7
- redirect?: string | {
8
- name: string;
9
- path?: string;
10
- };
1
+ import type { RouteRecordRaw, RouteRecordRedirectOption, _RouteRecordProps } from 'vue-router';
2
+ type RouteRecordProps = _RouteRecordProps;
3
+ /**
4
+ * Route configuration override interface
5
+ * Supports all RouteRecordRaw fields for complete customization
6
+ */
7
+ export interface RouteConfigOverride {
8
+ path?: string;
9
+ name?: string;
10
+ alias?: string | string[];
11
+ redirect?: RouteRecordRedirectOption;
12
+ props?: RouteRecordProps;
13
+ meta?: Record<string, any>;
14
+ children?: RouteRecordRaw[];
15
+ beforeEnter?: any;
11
16
  [key: string]: any;
12
17
  }
18
+ /**
19
+ * Parse Vue SFC and extract complete route configuration
20
+ * Supports both <route> custom block and defineRoute() macro
21
+ *
22
+ * @param filePath - Path to the Vue SFC file
23
+ * @returns Route configuration override, or undefined if no custom config
24
+ * @throws Error if both <route> block and defineRoute() are present
25
+ */
26
+ export declare function extractRouteConfig(filePath: string): RouteConfigOverride | undefined;
13
27
  /**
14
28
  * Parse Vue SFC and extract metadata from <route> custom block
29
+ * @deprecated Use extractRouteConfig() instead for full configuration support
15
30
  */
16
- export declare function extractRouteMeta(filePath: string): RouteMeta;
31
+ export declare function extractRouteMeta(filePath: string): Record<string, any>;
32
+ export {};
17
33
  //# sourceMappingURL=extract-meta.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract-meta.d.ts","sourceRoot":"","sources":["../src/extract-meta.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAqB5D"}
1
+ {"version":3,"file":"extract-meta.d.ts","sourceRoot":"","sources":["../src/extract-meta.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG/F,KAAK,gBAAgB,GAAG,iBAAiB,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAiDpF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAStE"}