@ubean/routing 0.1.7 → 0.1.8

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.
@@ -18,6 +18,15 @@ import { ScanResult, ScannedLayout, ScannedPageRoute } from "../types.js";
18
18
  interface GeneratorOptions {
19
19
  /** Project root directory (absolute). */
20
20
  cwd: string;
21
+ /**
22
+ * Source directory (absolute, defaults to `<cwd>/src`).
23
+ *
24
+ * Used by the default `getImportPath` / `getLayoutImportPath` to compute
25
+ * import specifiers relative to `srcDir` (the conventional target of the
26
+ * `@/` path alias). When the alias differs, supply custom `getImportPath` /
27
+ * `getLayoutImportPath` callbacks.
28
+ */
29
+ srcDir?: string;
21
30
  /** Output directory for `routes.ts` / `imports.ts` (absolute or relative to `cwd`). */
22
31
  outDir: string;
23
32
  /** Path for `typed-router.d.ts` (absolute or relative to `cwd`). Default: `<outDir>/typed-router.d.ts`. */
@@ -41,7 +50,8 @@ interface GeneratorOptions {
41
50
  /**
42
51
  * Compute the import specifier for a page file. Receives the absolute
43
52
  * `fullPath` of the page; should return a module specifier usable from
44
- * `imports.ts`. Defaults to a project-relative path prefixed with `@/`.
53
+ * `imports.ts`. Defaults to a `srcDir`-relative path prefixed with `@/`
54
+ * (e.g. `@/pages/about.vue`), matching the common `@/` → `src/` alias.
45
55
  */
46
56
  getImportPath?: (page: ScannedPageRoute) => string;
47
57
  /** Same as `getImportPath` but for layouts. */
@@ -9,6 +9,7 @@ var RouteFileGenerator = class {
9
9
  const dtsPath = options.dtsPath ?? joinPosix(options.outDir, "typed-router.d.ts");
10
10
  this.opts = {
11
11
  cwd: options.cwd,
12
+ srcDir: options.srcDir ?? joinPosix(options.cwd, "src"),
12
13
  outDir: options.outDir,
13
14
  dtsPath,
14
15
  generateRoutes: options.generateRoutes ?? true,
@@ -52,23 +53,23 @@ ${scanResult.pages.map((p) => this.renderRouteRecord(p)).join(",\n")}
52
53
  }
53
54
  renderRouteRecord(page) {
54
55
  const parts = [];
55
- parts.push(` name: ${JSON.stringify(page.name)}`);
56
- parts.push(` path: ${JSON.stringify(page.route)}`);
57
- const component = this.opts.getImportPath?.(page) ?? defaultPageImportPath(page, this.opts.cwd);
58
- parts.push(` component: ${JSON.stringify(component)}`);
56
+ parts.push(` name: ${JSON.stringify(page.name)}`);
57
+ parts.push(` path: ${JSON.stringify(page.route)}`);
58
+ const componentKey = page.isReuse && page.reuseTarget ? page.reuseTarget : page.name;
59
+ parts.push(` component: ${JSON.stringify(componentKey)}`);
59
60
  if (page.layout !== void 0) {
60
61
  const layoutVal = page.layout === false ? "false" : JSON.stringify(page.layout);
61
- parts.push(` layout: ${layoutVal}`);
62
+ parts.push(` layout: ${layoutVal}`);
62
63
  }
63
- if (page.isReuse) parts.push(` reuse: true`);
64
+ if (page.isReuse) parts.push(` reuse: true`);
64
65
  const meta = this.computeMeta(page);
65
- if (meta && Object.keys(meta).length > 0) parts.push(` meta: ${JSON.stringify(meta)}`);
66
- if (page.pageMeta?.cache === true) parts.push(` cache: true`);
67
- if (page.pageMeta?.requiresAuth === true) parts.push(` requiresAuth: true`);
66
+ if (meta && Object.keys(meta).length > 0) parts.push(` meta: ${JSON.stringify(meta)}`);
67
+ if (page.pageMeta?.cache === true) parts.push(` cache: true`);
68
+ if (page.pageMeta?.requiresAuth === true) parts.push(` requiresAuth: true`);
68
69
  if (page.pageMeta?.middleware) {
69
70
  const mw = page.pageMeta.middleware;
70
71
  const mwVal = Array.isArray(mw) ? JSON.stringify(mw) : JSON.stringify(mw);
71
- parts.push(` middleware: ${mwVal}`);
72
+ parts.push(` middleware: ${mwVal}`);
72
73
  }
73
74
  return ` {\n${parts.join(",\n")}\n }`;
74
75
  }
@@ -84,10 +85,39 @@ ${scanResult.pages.map((p) => this.renderRouteRecord(p)).join(",\n")}
84
85
  const header = this.opts.headerComment;
85
86
  const layouts = scanResult.layouts.map((l) => this.renderLayoutImport(l)).join(",\n");
86
87
  const views = scanResult.pages.filter((p) => !p.isReuse).map((p) => this.renderViewImport(p)).join(",\n");
88
+ const layoutType = scanResult.layouts.length > 0 ? `'${scanResult.layouts.map((l) => l.name).join("' | '")}'` : "string";
89
+ const filePages = scanResult.pages.filter((p) => !p.isReuse);
90
+ const reusePages = scanResult.pages.filter((p) => p.isReuse);
87
91
  return `${header}
88
92
 
89
- export type LayoutKey = ${scanResult.layouts.length > 0 ? `'${scanResult.layouts.map((l) => l.name).join("' | '")}'` : "string"};
90
- export type RouteKey = ${scanResult.pages.length > 0 ? `'${scanResult.pages.map((p) => p.name).join("' | '")}'` : "string"};
93
+ export type LayoutKey = ${layoutType};
94
+
95
+ /**
96
+ * Route keys that have a corresponding source file (\`.vue\` / \`.md\`).
97
+ * Excludes reuse routes and builtin routes.
98
+ *
99
+ * The \`views\` map is keyed by this type — \`views[RouteFileKey]\` resolves
100
+ * to the actual component loader.
101
+ */
102
+ export type RouteFileKey = ${filePages.length > 0 ? `'${filePages.map((p) => p.name).join("' | '")}'` : "never"};
103
+
104
+ /**
105
+ * Route keys declared via \`.reuse.ts\` / \`.reuse.vue\`.
106
+ * They reference another route's component via \`reuseTarget\`.
107
+ */
108
+ export type RouteReuseKey = ${reusePages.length > 0 ? `'${reusePages.map((p) => p.name).join("' | '")}'` : "never"};
109
+
110
+ /**
111
+ * Builtin route keys (framework-provided, e.g. \`root\` / \`not_found\`).
112
+ * Currently unused — reserved for future builtin pages.
113
+ */
114
+ export type BuiltinRouteKey = never;
115
+
116
+ /**
117
+ * All page route names. Union of {@link RouteFileKey}, {@link RouteReuseKey}
118
+ * and {@link BuiltinRouteKey}.
119
+ */
120
+ export type RouteKey = ${scanResult.pages.length > 0 ? `'${scanResult.pages.map((p) => p.name).join("' | '")}'` : "never"};
91
121
 
92
122
  export type Lazy<T> = () => Promise<T>;
93
123
  export type RawRouteComponent = import('vue').Component | Lazy<import('vue').Component>;
@@ -95,7 +125,14 @@ export type RawRouteComponent = import('vue').Component | Lazy<import('vue').Com
95
125
  export interface RouteRecord {
96
126
  name: RouteKey;
97
127
  path: string;
98
- component: string;
128
+ /**
129
+ * Key into the \`views\` map — resolve the actual component via
130
+ * \`views[route.component]\`.
131
+ *
132
+ * For reuse routes, this is the \`reuseTarget\`'s \`RouteFileKey\`
133
+ * (not the reuse route's own name), so the same component is reused.
134
+ */
135
+ component: RouteFileKey;
99
136
  layout?: LayoutKey | false;
100
137
  reuse?: boolean;
101
138
  meta?: Record<string, unknown>;
@@ -108,19 +145,19 @@ export const layouts: Record<LayoutKey, RawRouteComponent> = {
108
145
  ${layouts}
109
146
  };
110
147
 
111
- export const views: Record<RouteKey, RawRouteComponent> = {
148
+ export const views: Record<RouteFileKey, RawRouteComponent> = {
112
149
  ${views}
113
150
  };
114
151
  `;
115
152
  }
116
153
  renderLayoutImport(layout) {
117
- const importPath = this.opts.getLayoutImportPath?.(layout) ?? defaultLayoutImportPath(layout, this.opts.cwd);
154
+ const importPath = this.opts.getLayoutImportPath?.(layout) ?? defaultLayoutImportPath(layout, this.opts.srcDir);
118
155
  if (typeof this.opts.layoutLazy === "function" ? this.opts.layoutLazy(layout) : this.opts.layoutLazy) return ` ${layout.name}: () => import(${JSON.stringify(importPath)})`;
119
156
  const importName = `__layout_${layout.name}`;
120
157
  return ` ${layout.name}: ${importName}, // import ${importName} from ${JSON.stringify(importPath)}`;
121
158
  }
122
159
  renderViewImport(page) {
123
- const importPath = this.opts.getImportPath?.(page) ?? defaultPageImportPath(page, this.opts.cwd);
160
+ const importPath = this.opts.getImportPath?.(page) ?? defaultPageImportPath(page, this.opts.srcDir);
124
161
  if (typeof this.opts.routeLazy === "function" ? this.opts.routeLazy(page) : this.opts.routeLazy) return ` ${page.name}: () => import(${JSON.stringify(importPath)})`;
125
162
  return ` ${page.name}: ${page.name}, // import ${page.name} from ${JSON.stringify(importPath)}`;
126
163
  }
@@ -158,6 +195,20 @@ ${pathMapEntries}
158
195
  * Reuse route keys (pages using \`.reuse.vue\` suffix).
159
196
  */
160
197
  export type ReuseRouteKey = ${reuseKeys};
198
+
199
+ /**
200
+ * Builtin route keys (framework-provided, e.g. \`root\` / \`not_found\`).
201
+ * Currently unused — reserved for future builtin pages.
202
+ */
203
+ export type BuiltinRouteKey = never;
204
+
205
+ /**
206
+ * Route keys that have a corresponding source file (\`.vue\` / \`.md\`).
207
+ * Excludes reuse routes and builtin routes.
208
+ *
209
+ * Use this to index the \`views\` map for component lookup.
210
+ */
211
+ export type RouteFileKey = Exclude<RouteKey, ReuseRouteKey | BuiltinRouteKey>;
161
212
  }
162
213
 
163
214
  declare module 'vue-router/auto-routes' {
@@ -184,7 +235,7 @@ declare module 'vue-router' {
184
235
  }
185
236
  }
186
237
 
187
- export type { RouteLayoutKey, RoutePathMap, RouteKey, RoutePath, ReuseRouteKey } from '@ubean/routing';
238
+ export type { RouteLayoutKey, RoutePathMap, RouteKey, RoutePath, ReuseRouteKey, BuiltinRouteKey, RouteFileKey } from '@ubean/routing';
188
239
  `;
189
240
  }
190
241
  /**
@@ -205,11 +256,31 @@ export type { RouteLayoutKey, RoutePathMap, RouteKey, RoutePath, ReuseRouteKey }
205
256
  return `RouteRecordInfo<${JSON.stringify(page.name)}, ${JSON.stringify(page.route)}, ${paramsRaw}, ${paramsResolved}>`;
206
257
  }
207
258
  };
208
- function defaultPageImportPath(page, cwd) {
209
- return `@/${relativePosix(cwd, page.fullPath).replace(/\.(vue|tsx?|jsx?|mdx?)$/, "")}`;
259
+ /**
260
+ * Extensions stripped when computing the default import specifier.
261
+ *
262
+ * `.vue` / `.md` / `.mdx` are KEPT — Vite dispatches them to dedicated
263
+ * transforms (vue-loader / markdown) based on the extension, so the
264
+ * explicit suffix is required for correct resolution at runtime.
265
+ *
266
+ * `.ts` / `.tsx` / `.js` / `.jsx` are STRIPPED — TypeScript/Vite convention
267
+ * is to import script modules without their extension.
268
+ */
269
+ const STRIPPABLE_IMPORT_EXT = /\.(tsx?|jsx?)$/;
270
+ /**
271
+ * Compute the default import specifier for a page.
272
+ *
273
+ * Uses `srcDir` (not `cwd`) as the relative base so that the resulting
274
+ * `@/<rel>` path matches the common `@/` → `src/` path alias. For example,
275
+ * a page at `<cwd>/src/pages/about.vue` becomes `@/pages/about.vue`
276
+ * (NOT `@/src/pages/about.vue`, which would double the `src/` prefix and
277
+ * fail to resolve under the standard alias).
278
+ */
279
+ function defaultPageImportPath(page, srcDir) {
280
+ return `@/${relativePosix(srcDir, page.fullPath).replace(STRIPPABLE_IMPORT_EXT, "")}`;
210
281
  }
211
- function defaultLayoutImportPath(layout, cwd) {
212
- return `@/${relativePosix(cwd, layout.fullPath).replace(/\.(vue|tsx?|jsx?)$/, "")}`;
282
+ function defaultLayoutImportPath(layout, srcDir) {
283
+ return `@/${relativePosix(srcDir, layout.fullPath).replace(STRIPPABLE_IMPORT_EXT, "")}`;
213
284
  }
214
285
  /**
215
286
  * 从 vue-router 风格路径中提取参数信息。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/routing",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "File-based routing core for ubean (scanner, rou3 matcher, route name generator, define-page AST extractor, file generator)",
5
5
  "files": [
6
6
  "dist"
@@ -30,20 +30,20 @@
30
30
  "scule": "^1.3.0",
31
31
  "tinyglobby": "^0.2.17",
32
32
  "ufo": "1.6.4",
33
- "@ubean/types": "0.1.7",
34
- "@ubean/utils": "0.1.7"
33
+ "@ubean/types": "0.1.8",
34
+ "@ubean/utils": "0.1.8"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/node": "^26.1.1",
38
- "typescript": "6.0.3",
37
+ "@types/node": "^26.1.2",
38
+ "typescript": "7.0.2",
39
39
  "vite-plus": "0.2.6",
40
40
  "vue": "^3.5.40",
41
41
  "vue-router": "^5.2.0",
42
- "@ubean/markdown": "0.1.7"
42
+ "@ubean/markdown": "0.1.8"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "vue-router": "^4.0.0",
46
- "@ubean/markdown": "0.1.7"
46
+ "@ubean/markdown": "0.1.8"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
49
  "@ubean/markdown": {