vite-plugin-generoutes 1.1.1 → 2.0.0-beta.2

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
@@ -14,6 +14,7 @@ A Vite plugin that automatically generates Vue router configuration based on fil
14
14
  - 🎨 Customizable route configuration
15
15
  - 🧩 Support for route metadata via `defineOptions`
16
16
  - 🚦 Route redirection support
17
+ - 🖼️ Layout-based route grouping
17
18
 
18
19
  ### 📦 Installation
19
20
 
@@ -52,9 +53,9 @@ export default defineConfig({
52
53
  | Option | Type | Default | Description |
53
54
  | --------------- | ---------- | ---------------- | ----------------------------------------------------------------------------------------------------------------- |
54
55
  | `pagesFolder` | `string` | `'src/pages'` | Path to pages folder |
56
+ | `layoutsFolder` | `string` | `'src/layouts'` | Path to layouts folder |
55
57
  | `ignoreFolders` | `string[]` | `['components']` | Folders to ignore when generating routes |
56
58
  | `routesPath` | `string` | Auto-detected | Path to generated routes file. Auto-detected based on `tsconfig.json` presence (`.ts` if exists, otherwise `.js`) |
57
- | `nested` | `boolean` | `false` | Whether to generate nested routes |
58
59
 
59
60
  ### 📘 TypeScript Support
60
61
 
@@ -105,7 +106,7 @@ defineOptions({
105
106
 
106
107
  ### 🌲 Nested Routes
107
108
 
108
- With the `nested: true` option enabled, you can set nested route relationships using the `parent` property:
109
+ Use the `parent` property to set nested route relationships (handled automatically):
109
110
 
110
111
  ```vue
111
112
  <script setup>
@@ -119,7 +120,70 @@ defineOptions({
119
120
  </script>
120
121
  ```
121
122
 
122
- ### 🚀 Complete Example
123
+ ### �️ Layout Routes
124
+
125
+ Routes are automatically grouped by their `meta.layout` property and wrapped with a parent layout route:
126
+
127
+ - Routes with `meta.layout: false` will **not** be wrapped with a layout
128
+ - Routes without `meta.layout` will use the `'default'` layout by default
129
+ - Routes with `meta.layout: 'xxx'` will use the corresponding layout component from `layoutsFolder`
130
+
131
+ ```vue
132
+ <!-- src/pages/login.vue - No layout wrapper -->
133
+ <script setup>
134
+ defineOptions({
135
+ name: 'Login',
136
+ meta: {
137
+ layout: false
138
+ }
139
+ })
140
+ </script>
141
+ ```
142
+
143
+ ```vue
144
+ <!-- src/pages/home.vue - Uses 'admin' layout -->
145
+ <script setup>
146
+ defineOptions({
147
+ name: 'Home',
148
+ meta: {
149
+ layout: 'admin'
150
+ }
151
+ })
152
+ </script>
153
+ ```
154
+
155
+ **Generated route structure example:**
156
+
157
+ ```javascript
158
+ [
159
+ // Routes with layout: false are not wrapped
160
+ {
161
+ name: 'Login',
162
+ path: '/login',
163
+ component: () => import('/src/pages/login.vue'),
164
+ meta: { layout: false }
165
+ },
166
+ // Routes are grouped by layout
167
+ {
168
+ name: 'LAYOUT_DEFAULT',
169
+ path: '/__layout_default__',
170
+ component: () => import('/src/layouts/default.vue'),
171
+ children: [
172
+ { name: 'Index', path: '/' }
173
+ ]
174
+ },
175
+ {
176
+ name: 'LAYOUT_ADMIN',
177
+ path: '/__layout_admin__',
178
+ component: () => import('/src/layouts/admin.vue'),
179
+ children: [
180
+ { name: 'Home', path: '/home' }
181
+ ]
182
+ }
183
+ ]
184
+ ```
185
+
186
+ ### �🚀 Complete Example
123
187
 
124
188
  ```javascript
125
189
  import vue from '@vitejs/plugin-vue'
@@ -132,8 +196,7 @@ export default defineConfig({
132
196
  generoutes({
133
197
  pagesFolder: 'src/views',
134
198
  ignoreFolders: ['components', 'assets'],
135
- routesPath: 'src/router/routes.js',
136
- nested: true
199
+ routesPath: 'src/router/routes.js'
137
200
  })
138
201
  ]
139
202
  })
package/README.zh_CN.md CHANGED
@@ -14,6 +14,7 @@
14
14
  - 🎨 可自定义路由配置
15
15
  - 🧩 支持通过`defineOptions`设置路由元数据
16
16
  - 🚦 支持路由重定向
17
+ - 🖼️ 支持按布局分组路由
17
18
 
18
19
  ### 📦 安装
19
20
 
@@ -52,9 +53,9 @@ export default defineConfig({
52
53
  | 选项 | 类型 | 默认值 | 描述 |
53
54
  | --------------- | ---------- | ---------------- | ----------------------------------------------------------------------------------------- |
54
55
  | `pagesFolder` | `string` | `'src/pages'` | 页面文件夹路径 |
56
+ | `layoutsFolder` | `string` | `'src/layouts'` | 布局组件文件夹路径 |
55
57
  | `ignoreFolders` | `string[]` | `['components']` | 生成路由时忽略的文件夹 |
56
58
  | `routesPath` | `string` | 自动检测 | 生成的路由文件路径,根据 `tsconfig.json` 是否存在自动检测(存在则为 `.ts`,否则为 `.js`) |
57
- | `nested` | `boolean` | `false` | 是否生成嵌套路由 |
58
59
 
59
60
  ### 📘 TypeScript 支持
60
61
 
@@ -105,7 +106,7 @@ defineOptions({
105
106
 
106
107
  ### 🌲 嵌套路由
107
108
 
108
- 启用`nested: true`选项后,可以通过`parent`属性设置嵌套路由关系:
109
+ 使用 `parent` 属性即可建立嵌套路由关系(无需额外开关,自动生效):
109
110
 
110
111
  ```vue
111
112
  <script setup>
@@ -119,7 +120,70 @@ defineOptions({
119
120
  </script>
120
121
  ```
121
122
 
122
- ### 🚀 完整示例
123
+ ### �️ 布局路由
124
+
125
+ 路由会根据 `meta.layout` 属性自动分组,并包裹在对应的布局父级路由中:
126
+
127
+ - 设置 `meta.layout: false` 的路由**不会**被布局包裹
128
+ - 未设置 `meta.layout` 的路由默认使用 `'default'` 布局
129
+ - 设置 `meta.layout: 'xxx'` 的路由会使用 `layoutsFolder` 中对应的布局组件
130
+
131
+ ```vue
132
+ <!-- src/pages/login.vue - 不使用布局包裹 -->
133
+ <script setup>
134
+ defineOptions({
135
+ name: 'Login',
136
+ meta: {
137
+ layout: false
138
+ }
139
+ })
140
+ </script>
141
+ ```
142
+
143
+ ```vue
144
+ <!-- src/pages/home.vue - 使用 'admin' 布局 -->
145
+ <script setup>
146
+ defineOptions({
147
+ name: 'Home',
148
+ meta: {
149
+ layout: 'admin'
150
+ }
151
+ })
152
+ </script>
153
+ ```
154
+
155
+ **生成的路由结构示例:**
156
+
157
+ ```javascript
158
+ [
159
+ // layout: false 的路由不会被包裹
160
+ {
161
+ name: 'Login',
162
+ path: '/login',
163
+ component: () => import('/src/pages/login.vue'),
164
+ meta: { layout: false }
165
+ },
166
+ // 路由按布局分组
167
+ {
168
+ name: 'LAYOUT_DEFAULT',
169
+ path: '/__layout_default__',
170
+ component: () => import('/src/layouts/default.vue'),
171
+ children: [
172
+ { name: 'Index', path: '/' }
173
+ ]
174
+ },
175
+ {
176
+ name: 'LAYOUT_ADMIN',
177
+ path: '/__layout_admin__',
178
+ component: () => import('/src/layouts/admin.vue'),
179
+ children: [
180
+ { name: 'Home', path: '/home' }
181
+ ]
182
+ }
183
+ ]
184
+ ```
185
+
186
+ ### �🚀 完整示例
123
187
 
124
188
  ```javascript
125
189
  import vue from '@vitejs/plugin-vue'
@@ -132,8 +196,7 @@ export default defineConfig({
132
196
  generoutes({
133
197
  pagesFolder: 'src/views',
134
198
  ignoreFolders: ['components', 'assets'],
135
- routesPath: 'src/router/routes.js',
136
- nested: true
199
+ routesPath: 'src/router/routes.js'
137
200
  })
138
201
  ],
139
202
  })
package/dist/index.d.ts CHANGED
@@ -1,39 +1,31 @@
1
1
  import { Plugin } from 'vite';
2
+ import { RouteRecordRaw } from 'vue-router';
2
3
 
3
4
  /**
4
5
  * Route meta information
5
6
  */
6
7
  interface RouteMeta {
7
- /** Route title */
8
+ /** Page title */
8
9
  title?: string;
9
- /** Route icon */
10
+ /** Page icon */
10
11
  icon?: string;
12
+ /** Page code */
13
+ code?: string;
14
+ /** Page layout */
15
+ layout?: string | false;
11
16
  /** Whether authentication is required */
12
- requiresAuth?: boolean;
17
+ requireAuth?: boolean;
18
+ /** Whether to keep alive */
19
+ keepAlive?: boolean;
13
20
  /** Whether the route is enabled */
14
21
  enabled?: boolean;
22
+ /** Whether it's the home page */
23
+ isHome?: boolean;
24
+ /** Whether it's a login page */
25
+ isLogin?: boolean;
15
26
  /** Custom properties */
16
27
  [key: string]: unknown;
17
28
  }
18
- /**
19
- * Internal route representation used during generation
20
- */
21
- interface InternalRoute {
22
- /** Route name */
23
- name: string;
24
- /** Route path */
25
- path: string;
26
- /** Redirect path */
27
- redirect?: string;
28
- /** Route component placeholder */
29
- component?: string;
30
- /** Route meta information */
31
- meta: RouteMeta;
32
- /** Parent route name (for nested routes) */
33
- parent?: string;
34
- /** Child routes */
35
- children?: InternalRoute[];
36
- }
37
29
 
38
30
  /**********************************
39
31
  * @Author: Ronnie Zhang
@@ -46,22 +38,10 @@ interface InternalRoute {
46
38
  /**
47
39
  * Generated route record with proper typing for vue-router
48
40
  */
49
- interface GeneratedRoute {
50
- /** Route name */
51
- name: string;
52
- /** Route path */
53
- path: string;
54
- /** Redirect path */
55
- redirect?: string;
56
- /** Route component */
57
- component?: () => Promise<any>;
58
- /** Route meta information */
59
- meta: RouteMeta;
60
- /** Parent route name (for nested routes) */
61
- parent?: string;
62
- /** Child routes */
41
+ type GeneratedRoute = RouteRecordRaw & {
42
+ meta?: RouteMeta;
63
43
  children?: GeneratedRoute[];
64
- }
44
+ };
65
45
  interface Options {
66
46
  /**
67
47
  * pages folder
@@ -69,6 +49,12 @@ interface Options {
69
49
  * @default src/pages
70
50
  */
71
51
  pagesFolder: string;
52
+ /**
53
+ * layouts folder
54
+ *
55
+ * @default src/layouts
56
+ */
57
+ layoutsFolder: string;
72
58
  /**
73
59
  * ignore folders, ignore these folders when generating routes
74
60
  *
@@ -82,13 +68,7 @@ interface Options {
82
68
  * @default src/router/routes.ts (if tsconfig.json exists) or src/router/routes.js
83
69
  */
84
70
  routesPath: string;
85
- /**
86
- * nested routes
87
- *
88
- * @default false
89
- */
90
- nested: boolean;
91
71
  }
92
72
  declare function VitePluginGeneroutes(options?: Partial<Options>): Plugin;
93
73
 
94
- export { type GeneratedRoute, type InternalRoute, type Options, type RouteMeta, VitePluginGeneroutes, VitePluginGeneroutes as default };
74
+ export { type GeneratedRoute, type Options, type RouteMeta, VitePluginGeneroutes, VitePluginGeneroutes as default };
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ function toPascalCase(str) {
13
13
  return p1.toUpperCase();
14
14
  });
15
15
  }
16
- function convertToTree(routes) {
16
+ function nestRoutes(routes) {
17
17
  const nodeMap = {};
18
18
  const result = [];
19
19
  routes.forEach((route) => {
@@ -66,80 +66,72 @@ function VitePluginGeneroutes(options = {}) {
66
66
  let routesPath;
67
67
  let isTypeScript;
68
68
  const pagesFolder = options.pagesFolder || "src/pages";
69
+ const layoutsFolder = options.layoutsFolder || "src/layouts";
69
70
  const ignoreFolders = options.ignoreFolders || ["components"];
70
- const nested = options.nested || false;
71
71
  const defineOptionsCache = /* @__PURE__ */ new Map();
72
72
  function detectTypeScriptProject() {
73
73
  const tsconfigPath = path.resolve(rootDir, "tsconfig.json");
74
74
  return fs.existsSync(tsconfigPath);
75
75
  }
76
- function generateMenusAndRoutes() {
76
+ function generateRoutes() {
77
77
  const pages = globSync(`${pagesFolder}/**/*.vue`, { ignore: ignoreFolders.map((folder) => `${pagesFolder}/**/${folder}/**`) });
78
- const routes = pages.map((filePath) => {
78
+ const routes = [];
79
+ for (let filePath of pages) {
79
80
  filePath = slash(filePath);
80
81
  const defineOptions = parseDefineOptions(filePath) || {};
81
82
  defineOptionsCache.set(filePath, JSON.stringify(defineOptions));
82
83
  const meta = defineOptions.meta || {};
83
84
  if (meta.enabled === false)
84
- return null;
85
+ continue;
85
86
  const pathSegments = filePath.replace(`${pagesFolder}`, "").replace(".vue", "").replace("index", "").split("/").filter((item) => !!item && !/^\(.*\)$/.test(item));
86
87
  const name = defineOptions.name || pathSegments.map((item) => toPascalCase(item)).join("_") || "Index";
87
88
  const component = `##/${filePath}@@${name}##`;
88
89
  const routePath = `/${pathSegments.map((item) => item.replace(/\[(.*?)\]/g, (_, p1) => p1 === "...all" ? ":pathMatch(.*)*" : p1.split(",").map((i) => `:${i}`).join("/"))).join("/")}`;
89
- return {
90
+ routes.push({
90
91
  name,
91
92
  path: routePath,
92
93
  redirect: defineOptions.redirect,
94
+ parent: defineOptions.parent,
93
95
  component: defineOptions.redirect ? void 0 : component,
94
- meta,
95
- parent: defineOptions.parent
96
- };
97
- }).filter((route) => route !== null);
96
+ meta
97
+ });
98
+ }
98
99
  const { duplicateNames, duplicatePaths } = findDuplicateRoutes(routes);
99
100
  if (duplicateNames.length)
100
101
  console.warn(`Warning: Duplicate names found in routes: ${duplicateNames.join(", ")}`);
101
102
  if (duplicatePaths.length)
102
103
  console.warn(`Warning: Duplicate paths found in routes: ${duplicatePaths.join(", ")}`);
103
- return {
104
- routes: nested ? convertToTree(routes) : routes
105
- };
104
+ return wrapRoutesWithLayout(nestRoutes(routes));
105
+ }
106
+ function wrapRoutesWithLayout(routes) {
107
+ const layoutGroups = /* @__PURE__ */ new Map();
108
+ const noLayoutRoutes = [];
109
+ for (const route of routes) {
110
+ const layout = route.meta?.layout;
111
+ if (layout === false) {
112
+ noLayoutRoutes.push(route);
113
+ } else {
114
+ const layoutName = layout || "default";
115
+ const group = layoutGroups.get(layoutName);
116
+ if (group) {
117
+ group.push(route);
118
+ } else {
119
+ layoutGroups.set(layoutName, [route]);
120
+ }
121
+ }
122
+ }
123
+ const layoutRoutes = Array.from(layoutGroups, ([layoutName, children]) => ({
124
+ name: `__LAYOUT_${layoutName.toUpperCase()}__`,
125
+ path: `/__layout_${layoutName}__`,
126
+ component: `@@layout@@/${layoutsFolder}/${layoutName}.vue@@`,
127
+ children
128
+ }));
129
+ return layoutRoutes.concat(noLayoutRoutes);
106
130
  }
107
131
  async function writerRoutesFile(isInit = false) {
108
- const { routes } = generateMenusAndRoutes();
132
+ const routes = generateRoutes();
109
133
  const typeDefinitions = isTypeScript ? `
110
- import type { RouteRecordRaw } from 'vue-router'
111
-
112
- /**
113
- * Route meta information
114
- */
115
- export interface RouteMeta {
116
- /** Page title */
117
- title?: string
118
- /** Page icon */
119
- icon?: string
120
- /** Page code */
121
- code?: string
122
- /** Page layout */
123
- layout?: string | false
124
- /** Whether authentication is required */
125
- requireAuth?: boolean
126
- /** Whether to keep alive */
127
- keepAlive?: boolean
128
- /** Whether it's the home page */
129
- isHome?: boolean
130
- /** Whether it's a login page */
131
- isLogin?: boolean
132
- /** Custom properties */
133
- [key: string]: unknown
134
- }
135
-
136
- /**
137
- * Generated route type with proper typing
138
- */
139
- export type GeneratedRoute = RouteRecordRaw & {
140
- meta?: RouteMeta
141
- children?: GeneratedRoute[]
142
- }
134
+ import type { GeneratedRoute } from 'vite-plugin-generoutes'
143
135
  ` : "";
144
136
  const routesType = isTypeScript ? ": GeneratedRoute[]" : "";
145
137
  let routesStr = `
@@ -148,6 +140,7 @@ export type GeneratedRoute = RouteRecordRaw & {
148
140
  export const routes${routesType} = ${JSON.stringify(routes, null, 2)}
149
141
  `;
150
142
  routesStr = routesStr.replace(/"##(.*)##"/g, (_, p1) => `async () => ({...(await import('${p1.split("@@")[0]}')).default, name: '${p1.split("@@")[1]}'})`);
143
+ routesStr = routesStr.replace(/"@@layout@@(.*)@@"/g, (_, p1) => `() => import('${p1}')`);
151
144
  const parser = isTypeScript ? "typescript" : "babel";
152
145
  routesStr = await prettier.format(routesStr, { parser, semi: false, singleQuote: true });
153
146
  const filePath = path.resolve(rootDir, routesPath);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vite-plugin-generoutes",
3
3
  "type": "module",
4
- "version": "1.1.1",
4
+ "version": "2.0.0-beta.2",
5
5
  "packageManager": "pnpm@10.27.0",
6
6
  "description": "A Vite plugin that generate routes based on the file structure, supports dynamic routes, and supports custom meta data for each route.",
7
7
  "author": "Ronnie Zhang <zclzone@outlook.com>",