@4399ywkf/core 5.0.10 → 5.0.12

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.
@@ -1,6 +1,19 @@
1
1
  // src/router/generator.ts
2
- import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync } from "fs";
2
+ import {
3
+ existsSync,
4
+ readdirSync,
5
+ statSync,
6
+ mkdirSync,
7
+ writeFileSync
8
+ } from "fs";
3
9
  import { join, relative } from "path";
10
+ function winPath(path) {
11
+ const isExtendedLengthPath = /^\\\\\?\\/.test(path);
12
+ if (isExtendedLengthPath) {
13
+ return path;
14
+ }
15
+ return path.replace(/\\/g, "/");
16
+ }
4
17
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
5
18
  "components",
6
19
  "hooks",
@@ -57,14 +70,17 @@ var ConventionalRouteGenerator = class {
57
70
  );
58
71
  if (pathlessLayout) {
59
72
  const pathlessChildren = this.scanDirectory(subDirPath, routePath);
73
+ const pathlessLayoutRel = winPath(
74
+ relative(this.options.pagesDir, join(subDirPath, pathlessLayout))
75
+ );
60
76
  childRoutes.push({
61
77
  path: routePath,
62
78
  name: this.generateRouteName(subDir.slice(2)),
63
- layoutFile: relative(this.options.pagesDir, join(subDirPath, pathlessLayout)),
79
+ layoutFile: pathlessLayoutRel,
64
80
  isLayout: true,
65
81
  pathless: true,
66
82
  children: pathlessChildren.filter(
67
- (r) => r.layoutFile !== relative(this.options.pagesDir, join(subDirPath, pathlessLayout))
83
+ (r) => r.layoutFile !== pathlessLayoutRel
68
84
  )
69
85
  });
70
86
  } else {
@@ -76,7 +92,7 @@ var ConventionalRouteGenerator = class {
76
92
  childRoutes.push(...this.scanDirectory(subDirPath, subRoutePath));
77
93
  }
78
94
  const routeName = this.generateRouteName(routePath);
79
- const relPath = (file) => relative(this.options.pagesDir, join(dir, file));
95
+ const relPath = (file) => winPath(relative(this.options.pagesDir, join(dir, file)));
80
96
  if (layoutFile) {
81
97
  const layoutChildren = [];
82
98
  if (pageFile) {
@@ -222,26 +238,39 @@ export default routes;
222
238
  const toImportPath = (f) => f.replace(/\.(tsx?|jsx?)$/, "");
223
239
  if (route.isLayout && route.layoutFile) {
224
240
  lazyImports.push(
225
- `const ${name}Layout = lazy(() => import("@/pages/${toImportPath(route.layoutFile)}"));`
241
+ `const ${name}Layout = lazy(() => import("@/pages/${toImportPath(
242
+ route.layoutFile
243
+ )}"));`
226
244
  );
227
245
  }
228
246
  if (route.file) {
229
247
  lazyImports.push(
230
- `const ${name}Page = lazy(() => import("@/pages/${toImportPath(route.file)}"));`
248
+ `const ${name}Page = lazy(() => import("@/pages/${toImportPath(
249
+ route.file
250
+ )}"));`
231
251
  );
232
252
  }
233
253
  if (route.errorFile) {
234
254
  errorImports.push(
235
- `const ${name}Error = lazy(() => import("@/pages/${toImportPath(route.errorFile)}"));`
255
+ `const ${name}Error = lazy(() => import("@/pages/${toImportPath(
256
+ route.errorFile
257
+ )}"));`
236
258
  );
237
259
  }
238
260
  if (route.loadingFile) {
239
261
  loadingImports.push(
240
- `const ${name}Loading = lazy(() => import("@/pages/${toImportPath(route.loadingFile)}"));`
262
+ `const ${name}Loading = lazy(() => import("@/pages/${toImportPath(
263
+ route.loadingFile
264
+ )}"));`
241
265
  );
242
266
  }
243
267
  if (route.children) {
244
- this.collectImports(route.children, lazyImports, errorImports, loadingImports);
268
+ this.collectImports(
269
+ route.children,
270
+ lazyImports,
271
+ errorImports,
272
+ loadingImports
273
+ );
245
274
  }
246
275
  }
247
276
  }
@@ -267,7 +296,9 @@ export default routes;
267
296
  }
268
297
  if (route.isLayout && route.layoutFile) {
269
298
  const loadingProp = route.loadingFile ? ` Loading={${name}Loading}` : "";
270
- parts.push(`element: <LazyRoute Component={${name}Layout}${loadingProp} />`);
299
+ parts.push(
300
+ `element: <LazyRoute Component={${name}Layout}${loadingProp} />`
301
+ );
271
302
  } else if (route.file) {
272
303
  parts.push(`element: <LazyRoute Component={${name}Page} />`);
273
304
  }
@@ -276,7 +307,9 @@ export default routes;
276
307
  }
277
308
  if (route.children && route.children.length > 0) {
278
309
  const childParent = route.pathless ? parentPath : route.path;
279
- parts.push(`children: ${this.emitRouteArray(route.children, childParent)}`);
310
+ parts.push(
311
+ `children: ${this.emitRouteArray(route.children, childParent)}`
312
+ );
280
313
  }
281
314
  return `{
282
315
  ${parts.join(",\n ")}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/router/generator.ts","../../src/router/plugin.ts"],"sourcesContent":["import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync } from \"fs\";\nimport { join, relative, basename } from \"path\";\n\n/**\n * 路由节点配置\n */\nexport interface RouteConfig {\n /** 路由路径 */\n path: string;\n /** 页面文件(page.tsx)相对路径 */\n file?: string;\n /** 布局文件(layout.tsx)相对路径 */\n layoutFile?: string;\n /** 错误边界文件(error.tsx)相对路径 */\n errorFile?: string;\n /** 加载状态文件(loading.tsx)相对路径 */\n loadingFile?: string;\n /** 通配路由文件($.tsx)相对路径 */\n catchAllFile?: string;\n /** 组件名称 */\n name: string;\n /** 子路由 */\n children?: RouteConfig[];\n /** 是否为 index 路由 */\n index?: boolean;\n /** 是否为布局路由 */\n isLayout?: boolean;\n /** 是否为无路径布局(__prefix) */\n pathless?: boolean;\n}\n\nexport interface GeneratorOptions {\n /** 页面目录 */\n pagesDir: string;\n /** 输出目录 */\n outputDir: string;\n /** 路由 basename */\n basename?: string;\n}\n\n/**\n * 不参与路由扫描的目录名\n */\nconst EXCLUDED_DIRS = new Set([\n \"components\",\n \"hooks\",\n \"utils\",\n \"services\",\n \"models\",\n \"assets\",\n \"types\",\n \"constants\",\n \"styles\",\n]);\n\n/**\n * 约定文件名匹配\n */\nconst CONVENTION_FILES = {\n page: /^page\\.(tsx?|jsx?)$/,\n layout: /^layout\\.(tsx?|jsx?)$/,\n error: /^error\\.(tsx?|jsx?)$/,\n loading: /^loading\\.(tsx?|jsx?)$/,\n catchAll: /^\\$\\.(tsx?|jsx?)$/,\n};\n\n/**\n * Modern.js 风格约定式路由生成器\n *\n * 文件约定:\n * - page.tsx — 页面内容(叶子组件)\n * - layout.tsx — 布局组件(使用 <Outlet> 渲染子路由)\n * - error.tsx — 错误边界组件\n * - loading.tsx — 加载状态组件\n * - $.tsx — 通配/404 路由\n *\n * 目录约定:\n * - [id]/ — 动态路由 → /:id\n * - [id$]/ — 可选动态路由 → /:id?\n * - [...slug]/ — 全匹配路由 → /*\n * - __auth/ — 无路径布局(不生成 URL 片段)\n * - user.profile/ — 扁平路由(. 分隔 → /user/profile)\n */\nexport class ConventionalRouteGenerator {\n private options: GeneratorOptions;\n\n constructor(options: GeneratorOptions) {\n this.options = options;\n }\n\n generate(): RouteConfig[] {\n const { pagesDir } = this.options;\n if (!existsSync(pagesDir)) {\n console.warn(`[ywkf] 页面目录不存在: ${pagesDir}`);\n return [];\n }\n return this.scanDirectory(pagesDir, \"/\");\n }\n\n /**\n * 扫描目录,生成路由树\n */\n private scanDirectory(dir: string, routePath: string): RouteConfig[] {\n const entries = readdirSync(dir);\n\n // 查找约定文件\n const layoutFile = entries.find((e) => CONVENTION_FILES.layout.test(e));\n const pageFile = entries.find((e) => CONVENTION_FILES.page.test(e));\n const errorFile = entries.find((e) => CONVENTION_FILES.error.test(e));\n const loadingFile = entries.find((e) => CONVENTION_FILES.loading.test(e));\n const catchAllFile = entries.find((e) => CONVENTION_FILES.catchAll.test(e));\n\n // 收集子目录\n const subDirs = entries.filter((e) => {\n if (e.startsWith(\".\")) return false;\n if (EXCLUDED_DIRS.has(e)) return false;\n return statSync(join(dir, e)).isDirectory();\n });\n\n // 递归子目录\n const childRoutes: RouteConfig[] = [];\n for (const subDir of subDirs) {\n const subDirPath = join(dir, subDir);\n\n // __prefix:无路径布局\n if (subDir.startsWith(\"__\")) {\n const pathlessRoutes = this.scanDirectory(subDirPath, routePath);\n // 如果该无路径目录有 layout,将子路由包装在其中\n const pathlessLayout = readdirSync(subDirPath).find((e) =>\n CONVENTION_FILES.layout.test(e)\n );\n if (pathlessLayout) {\n const pathlessChildren = this.scanDirectory(subDirPath, routePath);\n childRoutes.push({\n path: routePath,\n name: this.generateRouteName(subDir.slice(2)),\n layoutFile: relative(this.options.pagesDir, join(subDirPath, pathlessLayout)),\n isLayout: true,\n pathless: true,\n children: pathlessChildren.filter(\n (r) => r.layoutFile !== relative(this.options.pagesDir, join(subDirPath, pathlessLayout))\n ),\n });\n } else {\n childRoutes.push(...pathlessRoutes);\n }\n continue;\n }\n\n // 计算路由路径\n const subRoutePath = this.resolveRoutePath(subDir, routePath);\n childRoutes.push(...this.scanDirectory(subDirPath, subRoutePath));\n }\n\n // 构建当前目录的路由节点\n const routeName = this.generateRouteName(routePath);\n const relPath = (file: string) => relative(this.options.pagesDir, join(dir, file));\n\n // 有 layout.tsx → 创建布局路由节点,children 为子路由\n if (layoutFile) {\n const layoutChildren: RouteConfig[] = [];\n\n // page.tsx 作为 index 子路由\n if (pageFile) {\n layoutChildren.push({\n path: routePath,\n file: relPath(pageFile),\n name: routeName + \"Index\",\n index: true,\n });\n }\n\n // 通配路由\n if (catchAllFile) {\n layoutChildren.push({\n path: \"*\",\n file: relPath(catchAllFile),\n name: routeName + \"CatchAll\",\n });\n }\n\n layoutChildren.push(...childRoutes);\n\n return [\n {\n path: routePath,\n layoutFile: relPath(layoutFile),\n errorFile: errorFile ? relPath(errorFile) : undefined,\n loadingFile: loadingFile ? relPath(loadingFile) : undefined,\n name: routeName,\n isLayout: true,\n children: layoutChildren,\n },\n ];\n }\n\n // 无 layout → page.tsx 是叶子页面\n const result: RouteConfig[] = [];\n\n if (pageFile) {\n result.push({\n path: routePath,\n file: relPath(pageFile),\n errorFile: errorFile ? relPath(errorFile) : undefined,\n name: routeName,\n });\n }\n\n if (catchAllFile) {\n result.push({\n path: \"*\",\n file: relPath(catchAllFile),\n name: routeName + \"CatchAll\",\n });\n }\n\n result.push(...childRoutes);\n return result;\n }\n\n /**\n * 解析目录名到路由路径\n *\n * - [id] → :id\n * - [id$] → :id?\n * - [...slug] → *\n * - user.profile → user/profile\n */\n private resolveRoutePath(dirName: string, parentPath: string): string {\n let segment: string;\n\n // [...slug] → *\n if (dirName.match(/^\\[\\.\\.\\.(.+)\\]$/)) {\n segment = \"*\";\n }\n // [id$] → :id? (可选动态)\n else if (dirName.match(/^\\[(.+)\\$\\]$/)) {\n const param = dirName.slice(1, -2);\n segment = `:${param}?`;\n }\n // [id] → :id\n else if (dirName.match(/^\\[(.+)\\]$/)) {\n const param = dirName.slice(1, -1);\n segment = `:${param}`;\n }\n // user.profile → user/profile (扁平路由)\n else if (dirName.includes(\".\")) {\n segment = dirName.replace(/\\./g, \"/\");\n } else {\n segment = dirName;\n }\n\n return parentPath === \"/\"\n ? `/${segment}`\n : `${parentPath}/${segment}`;\n }\n\n /**\n * 生成有效的 JS 标识符名称\n */\n private generateRouteName(path: string): string {\n const name = path\n .split(\"/\")\n .filter(Boolean)\n .map((s) =>\n s\n .replace(/^:/, \"Param\")\n .replace(/\\?$/, \"Optional\")\n .replace(/^\\*$/, \"CatchAll\")\n )\n .map((s) => {\n const cleaned = s.replace(/[^a-zA-Z0-9]/g, \"\");\n if (!cleaned) return \"\";\n return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n })\n .join(\"\");\n\n if (!name || /^\\d/.test(name)) {\n return \"Page\" + (name || \"Root\");\n }\n return name;\n }\n\n // ===== 代码生成 =====\n\n generateCode(): string {\n const routes = this.generate();\n const lazyImports: string[] = [];\n const errorImports: string[] = [];\n const loadingImports: string[] = [];\n\n this.collectImports(routes, lazyImports, errorImports, loadingImports);\n\n return `// 此文件由 @4399ywkf/core 自动生成,请勿手动修改\n// Generated at: ${new Date().toISOString()}\n\nimport React, { lazy, Suspense } from \"react\";\nimport { createBrowserRouter, type RouteObject } from \"react-router\";\n\n// 懒加载页面组件\n${lazyImports.join(\"\\n\")}\n${errorImports.length > 0 ? \"\\n// 错误边界组件\\n\" + errorImports.join(\"\\n\") : \"\"}\n${loadingImports.length > 0 ? \"\\n// 加载状态组件\\n\" + loadingImports.join(\"\\n\") : \"\"}\n\n// 默认加载状态\nconst DefaultLoading = () => <div style={{ padding: 24, textAlign: \"center\" }}>加载中...</div>;\n\n// 懒加载包装\nfunction LazyRoute({\n Component,\n Loading = DefaultLoading,\n}: {\n Component: React.LazyExoticComponent<React.ComponentType<unknown>>;\n Loading?: React.ComponentType;\n}) {\n return (\n <Suspense fallback={<Loading />}>\n <Component />\n </Suspense>\n );\n}\n\n// 路由配置\nexport const routes: RouteObject[] = ${this.emitRouteArray(routes)};\n\n/**\n * 创建路由实例\n */\nexport function createRouter(basename?: string) {\n return createBrowserRouter(routes, { basename: basename || \"/\" });\n}\n\nexport default routes;\n`;\n }\n\n private collectImports(\n routes: RouteConfig[],\n lazyImports: string[],\n errorImports: string[],\n loadingImports: string[]\n ): void {\n for (const route of routes) {\n const name = route.name;\n const toImportPath = (f: string) => f.replace(/\\.(tsx?|jsx?)$/, \"\");\n\n if (route.isLayout && route.layoutFile) {\n lazyImports.push(\n `const ${name}Layout = lazy(() => import(\"@/pages/${toImportPath(route.layoutFile)}\"));`\n );\n }\n if (route.file) {\n lazyImports.push(\n `const ${name}Page = lazy(() => import(\"@/pages/${toImportPath(route.file)}\"));`\n );\n }\n if (route.errorFile) {\n errorImports.push(\n `const ${name}Error = lazy(() => import(\"@/pages/${toImportPath(route.errorFile)}\"));`\n );\n }\n if (route.loadingFile) {\n loadingImports.push(\n `const ${name}Loading = lazy(() => import(\"@/pages/${toImportPath(route.loadingFile)}\"));`\n );\n }\n\n if (route.children) {\n this.collectImports(route.children, lazyImports, errorImports, loadingImports);\n }\n }\n }\n\n private emitRouteArray(routes: RouteConfig[], parentPath?: string): string {\n const items = routes.map((r) => this.emitRouteObject(r, parentPath));\n return `[\\n ${items.join(\",\\n \")}\\n]`;\n }\n\n private emitRouteObject(route: RouteConfig, parentPath?: string): string {\n const parts: string[] = [];\n const name = route.name;\n\n // path\n if (route.index) {\n parts.push(\"index: true\");\n } else if (route.pathless) {\n // 无路径布局不输出 path\n } else if (route.path === \"*\") {\n parts.push(`path: \"*\"`);\n } else if (parentPath && route.path.startsWith(parentPath) && parentPath !== \"/\") {\n const rel = route.path.slice(parentPath.length + 1);\n parts.push(`path: \"${rel || \"\"}\"`);\n } else {\n parts.push(`path: \"${route.path}\"`);\n }\n\n // element\n if (route.isLayout && route.layoutFile) {\n const loadingProp = route.loadingFile ? ` Loading={${name}Loading}` : \"\";\n parts.push(`element: <LazyRoute Component={${name}Layout}${loadingProp} />`);\n } else if (route.file) {\n parts.push(`element: <LazyRoute Component={${name}Page} />`);\n }\n\n // errorElement\n if (route.errorFile) {\n parts.push(`errorElement: <LazyRoute Component={${name}Error} />`);\n }\n\n // children\n if (route.children && route.children.length > 0) {\n const childParent = route.pathless ? parentPath : route.path;\n parts.push(`children: ${this.emitRouteArray(route.children, childParent)}`);\n }\n\n return `{\\n ${parts.join(\",\\n \")}\\n }`;\n }\n\n /**\n * 写入路由文件\n */\n write(): void {\n const { outputDir } = this.options;\n const code = this.generateCode();\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n writeFileSync(join(outputDir, \"routes.tsx\"), code, \"utf-8\");\n if (process.env.DEBUG) console.log(`[ywkf] 约定式路由已生成`);\n }\n}\n\n/**\n * 生成约定式路由\n */\nexport function generateConventionalRoutes(options: GeneratorOptions): void {\n new ConventionalRouteGenerator(options).write();\n}\n","import type { Compiler } from \"@rspack/core\";\nimport { ConventionalRouteGenerator } from \"./generator.js\";\nimport { join } from \"path\";\nimport { watch, existsSync, readFileSync } from \"fs\";\n\nexport interface ConventionalRoutePluginOptions {\n /** 页面目录 */\n pagesDir: string;\n /** 输出目录 */\n outputDir: string;\n /** 路由 basename */\n basename?: string;\n /** 是否监听文件变化(开发模式) */\n watch?: boolean;\n}\n\n/**\n * 约定式路由 Rspack 插件\n *\n * 在编译开始前扫描 src/pages 目录,生成 .ywkf/routes.tsx\n */\nexport class ConventionalRoutePlugin {\n private options: ConventionalRoutePluginOptions;\n private isWatching = false;\n private hasGenerated = false;\n private lastGeneratedContent = \"\";\n\n constructor(options: ConventionalRoutePluginOptions) {\n this.options = options;\n }\n\n apply(compiler: Compiler): void {\n const pluginName = \"ConventionalRoutePlugin\";\n\n // 只在首次编译时生成路由\n compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {\n if (!this.hasGenerated) {\n this.generateRoutes();\n this.hasGenerated = true;\n }\n callback();\n });\n\n // 开发模式下监听文件变化\n if (this.options.watch && !this.isWatching) {\n this.watchPages();\n }\n }\n\n /**\n * 生成路由文件(带内容比对,避免不必要的重复生成)\n */\n private generateRoutes(): void {\n try {\n const generator = new ConventionalRouteGenerator({\n pagesDir: this.options.pagesDir,\n outputDir: this.options.outputDir,\n basename: this.options.basename,\n });\n\n const newContent = generator.generateCode();\n const outputPath = join(this.options.outputDir, \"routes.tsx\");\n\n // 比对内容,如果相同则跳过(去掉时间戳后比较)\n const normalizedNew = this.normalizeContent(newContent);\n const normalizedOld = this.normalizeContent(this.lastGeneratedContent);\n\n if (normalizedNew === normalizedOld) {\n return; // 内容相同,跳过生成\n }\n\n generator.write();\n this.lastGeneratedContent = newContent;\n } catch (error) {\n console.error(\"[ywkf] 生成路由失败:\", error);\n }\n }\n\n /**\n * 标准化内容(去掉时间戳等动态部分)\n */\n private normalizeContent(content: string): string {\n return content.replace(/\\/\\/ Generated at: .+/g, \"\");\n }\n\n /**\n * 监听页面目录变化\n */\n private watchPages(): void {\n if (!existsSync(this.options.pagesDir)) {\n return;\n }\n\n this.isWatching = true;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const watcher = watch(\n this.options.pagesDir,\n { recursive: true },\n (eventType, filename) => {\n // 只处理 tsx/jsx 文件\n if (!filename?.match(/\\.(tsx?|jsx?)$/)) {\n return;\n }\n\n // 防抖处理\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n debounceTimer = setTimeout(() => {\n if (process.env.DEBUG) console.log(`[ywkf] 检测到页面变化: ${filename}`);\n this.generateRoutes();\n }, 500);\n }\n );\n\n // 进程退出时关闭监听\n process.on(\"exit\", () => {\n watcher.close();\n });\n }\n}\n"],"mappings":"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","names":["join","existsSync"]}
1
+ {"version":3,"sources":["../../src/router/generator.ts","../../src/router/plugin.ts"],"sourcesContent":["import {\n existsSync,\n readdirSync,\n statSync,\n mkdirSync,\n writeFileSync,\n} from \"fs\";\nimport { join, relative } from \"path\";\n\n/** 将 Windows 路径统一为 POSIX 正斜杠(import 路径必须用 /) */\nfunction winPath(path: string): string {\n const isExtendedLengthPath = /^\\\\\\\\\\?\\\\/.test(path);\n if (isExtendedLengthPath) {\n return path;\n }\n return path.replace(/\\\\/g, \"/\");\n}\n\n/**\n * 路由节点配置\n */\nexport interface RouteConfig {\n /** 路由路径 */\n path: string;\n /** 页面文件(page.tsx)相对路径 */\n file?: string;\n /** 布局文件(layout.tsx)相对路径 */\n layoutFile?: string;\n /** 错误边界文件(error.tsx)相对路径 */\n errorFile?: string;\n /** 加载状态文件(loading.tsx)相对路径 */\n loadingFile?: string;\n /** 通配路由文件($.tsx)相对路径 */\n catchAllFile?: string;\n /** 组件名称 */\n name: string;\n /** 子路由 */\n children?: RouteConfig[];\n /** 是否为 index 路由 */\n index?: boolean;\n /** 是否为布局路由 */\n isLayout?: boolean;\n /** 是否为无路径布局(__prefix) */\n pathless?: boolean;\n}\n\nexport interface GeneratorOptions {\n /** 页面目录 */\n pagesDir: string;\n /** 输出目录 */\n outputDir: string;\n /** 路由 basename */\n basename?: string;\n}\n\n/**\n * 不参与路由扫描的目录名\n */\nconst EXCLUDED_DIRS = new Set([\n \"components\",\n \"hooks\",\n \"utils\",\n \"services\",\n \"models\",\n \"assets\",\n \"types\",\n \"constants\",\n \"styles\",\n]);\n\n/**\n * 约定文件名匹配\n */\nconst CONVENTION_FILES = {\n page: /^page\\.(tsx?|jsx?)$/,\n layout: /^layout\\.(tsx?|jsx?)$/,\n error: /^error\\.(tsx?|jsx?)$/,\n loading: /^loading\\.(tsx?|jsx?)$/,\n catchAll: /^\\$\\.(tsx?|jsx?)$/,\n};\n\n/**\n * Modern.js 风格约定式路由生成器\n *\n * 文件约定:\n * - page.tsx — 页面内容(叶子组件)\n * - layout.tsx — 布局组件(使用 <Outlet> 渲染子路由)\n * - error.tsx — 错误边界组件\n * - loading.tsx — 加载状态组件\n * - $.tsx — 通配/404 路由\n *\n * 目录约定:\n * - [id]/ — 动态路由 → /:id\n * - [id$]/ — 可选动态路由 → /:id?\n * - [...slug]/ — 全匹配路由 → /*\n * - __auth/ — 无路径布局(不生成 URL 片段)\n * - user.profile/ — 扁平路由(. 分隔 → /user/profile)\n */\nexport class ConventionalRouteGenerator {\n private options: GeneratorOptions;\n\n constructor(options: GeneratorOptions) {\n this.options = options;\n }\n\n generate(): RouteConfig[] {\n const { pagesDir } = this.options;\n if (!existsSync(pagesDir)) {\n console.warn(`[ywkf] 页面目录不存在: ${pagesDir}`);\n return [];\n }\n return this.scanDirectory(pagesDir, \"/\");\n }\n\n /**\n * 扫描目录,生成路由树\n */\n private scanDirectory(dir: string, routePath: string): RouteConfig[] {\n const entries = readdirSync(dir);\n\n // 查找约定文件\n const layoutFile = entries.find((e) => CONVENTION_FILES.layout.test(e));\n const pageFile = entries.find((e) => CONVENTION_FILES.page.test(e));\n const errorFile = entries.find((e) => CONVENTION_FILES.error.test(e));\n const loadingFile = entries.find((e) => CONVENTION_FILES.loading.test(e));\n const catchAllFile = entries.find((e) => CONVENTION_FILES.catchAll.test(e));\n\n // 收集子目录\n const subDirs = entries.filter((e) => {\n if (e.startsWith(\".\")) return false;\n if (EXCLUDED_DIRS.has(e)) return false;\n return statSync(join(dir, e)).isDirectory();\n });\n\n // 递归子目录\n const childRoutes: RouteConfig[] = [];\n for (const subDir of subDirs) {\n const subDirPath = join(dir, subDir);\n\n // __prefix:无路径布局\n if (subDir.startsWith(\"__\")) {\n const pathlessRoutes = this.scanDirectory(subDirPath, routePath);\n // 如果该无路径目录有 layout,将子路由包装在其中\n const pathlessLayout = readdirSync(subDirPath).find((e) =>\n CONVENTION_FILES.layout.test(e),\n );\n if (pathlessLayout) {\n const pathlessChildren = this.scanDirectory(subDirPath, routePath);\n const pathlessLayoutRel = winPath(\n relative(this.options.pagesDir, join(subDirPath, pathlessLayout)),\n );\n childRoutes.push({\n path: routePath,\n name: this.generateRouteName(subDir.slice(2)),\n layoutFile: pathlessLayoutRel,\n isLayout: true,\n pathless: true,\n children: pathlessChildren.filter(\n (r) => r.layoutFile !== pathlessLayoutRel,\n ),\n });\n } else {\n childRoutes.push(...pathlessRoutes);\n }\n continue;\n }\n\n // 计算路由路径\n const subRoutePath = this.resolveRoutePath(subDir, routePath);\n childRoutes.push(...this.scanDirectory(subDirPath, subRoutePath));\n }\n\n // 构建当前目录的路由节点\n const routeName = this.generateRouteName(routePath);\n const relPath = (file: string) =>\n winPath(relative(this.options.pagesDir, join(dir, file)));\n\n // 有 layout.tsx → 创建布局路由节点,children 为子路由\n if (layoutFile) {\n const layoutChildren: RouteConfig[] = [];\n\n // page.tsx 作为 index 子路由\n if (pageFile) {\n layoutChildren.push({\n path: routePath,\n file: relPath(pageFile),\n name: routeName + \"Index\",\n index: true,\n });\n }\n\n // 通配路由\n if (catchAllFile) {\n layoutChildren.push({\n path: \"*\",\n file: relPath(catchAllFile),\n name: routeName + \"CatchAll\",\n });\n }\n\n layoutChildren.push(...childRoutes);\n\n return [\n {\n path: routePath,\n layoutFile: relPath(layoutFile),\n errorFile: errorFile ? relPath(errorFile) : undefined,\n loadingFile: loadingFile ? relPath(loadingFile) : undefined,\n name: routeName,\n isLayout: true,\n children: layoutChildren,\n },\n ];\n }\n\n // 无 layout → page.tsx 是叶子页面\n const result: RouteConfig[] = [];\n\n if (pageFile) {\n result.push({\n path: routePath,\n file: relPath(pageFile),\n errorFile: errorFile ? relPath(errorFile) : undefined,\n name: routeName,\n });\n }\n\n if (catchAllFile) {\n result.push({\n path: \"*\",\n file: relPath(catchAllFile),\n name: routeName + \"CatchAll\",\n });\n }\n\n result.push(...childRoutes);\n return result;\n }\n\n /**\n * 解析目录名到路由路径\n *\n * - [id] → :id\n * - [id$] → :id?\n * - [...slug] → *\n * - user.profile → user/profile\n */\n private resolveRoutePath(dirName: string, parentPath: string): string {\n let segment: string;\n\n // [...slug] → *\n if (dirName.match(/^\\[\\.\\.\\.(.+)\\]$/)) {\n segment = \"*\";\n }\n // [id$] → :id? (可选动态)\n else if (dirName.match(/^\\[(.+)\\$\\]$/)) {\n const param = dirName.slice(1, -2);\n segment = `:${param}?`;\n }\n // [id] → :id\n else if (dirName.match(/^\\[(.+)\\]$/)) {\n const param = dirName.slice(1, -1);\n segment = `:${param}`;\n }\n // user.profile → user/profile (扁平路由)\n else if (dirName.includes(\".\")) {\n segment = dirName.replace(/\\./g, \"/\");\n } else {\n segment = dirName;\n }\n\n return parentPath === \"/\" ? `/${segment}` : `${parentPath}/${segment}`;\n }\n\n /**\n * 生成有效的 JS 标识符名称\n */\n private generateRouteName(path: string): string {\n const name = path\n .split(\"/\")\n .filter(Boolean)\n .map((s) =>\n s\n .replace(/^:/, \"Param\")\n .replace(/\\?$/, \"Optional\")\n .replace(/^\\*$/, \"CatchAll\"),\n )\n .map((s) => {\n const cleaned = s.replace(/[^a-zA-Z0-9]/g, \"\");\n if (!cleaned) return \"\";\n return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n })\n .join(\"\");\n\n if (!name || /^\\d/.test(name)) {\n return \"Page\" + (name || \"Root\");\n }\n return name;\n }\n\n // ===== 代码生成 =====\n\n generateCode(): string {\n const routes = this.generate();\n const lazyImports: string[] = [];\n const errorImports: string[] = [];\n const loadingImports: string[] = [];\n\n this.collectImports(routes, lazyImports, errorImports, loadingImports);\n\n return `// 此文件由 @4399ywkf/core 自动生成,请勿手动修改\n// Generated at: ${new Date().toISOString()}\n\nimport React, { lazy, Suspense } from \"react\";\nimport { createBrowserRouter, type RouteObject } from \"react-router\";\n\n// 懒加载页面组件\n${lazyImports.join(\"\\n\")}\n${\n errorImports.length > 0 ? \"\\n// 错误边界组件\\n\" + errorImports.join(\"\\n\") : \"\"\n}\n${\n loadingImports.length > 0\n ? \"\\n// 加载状态组件\\n\" + loadingImports.join(\"\\n\")\n : \"\"\n}\n\n// 默认加载状态\nconst DefaultLoading = () => <div style={{ padding: 24, textAlign: \"center\" }}>加载中...</div>;\n\n// 懒加载包装\nfunction LazyRoute({\n Component,\n Loading = DefaultLoading,\n}: {\n Component: React.LazyExoticComponent<React.ComponentType<unknown>>;\n Loading?: React.ComponentType;\n}) {\n return (\n <Suspense fallback={<Loading />}>\n <Component />\n </Suspense>\n );\n}\n\n// 路由配置\nexport const routes: RouteObject[] = ${this.emitRouteArray(routes)};\n\n/**\n * 创建路由实例\n */\nexport function createRouter(basename?: string) {\n return createBrowserRouter(routes, { basename: basename || \"/\" });\n}\n\nexport default routes;\n`;\n }\n\n private collectImports(\n routes: RouteConfig[],\n lazyImports: string[],\n errorImports: string[],\n loadingImports: string[],\n ): void {\n for (const route of routes) {\n const name = route.name;\n const toImportPath = (f: string) => f.replace(/\\.(tsx?|jsx?)$/, \"\");\n\n if (route.isLayout && route.layoutFile) {\n lazyImports.push(\n `const ${name}Layout = lazy(() => import(\"@/pages/${toImportPath(\n route.layoutFile,\n )}\"));`,\n );\n }\n if (route.file) {\n lazyImports.push(\n `const ${name}Page = lazy(() => import(\"@/pages/${toImportPath(\n route.file,\n )}\"));`,\n );\n }\n if (route.errorFile) {\n errorImports.push(\n `const ${name}Error = lazy(() => import(\"@/pages/${toImportPath(\n route.errorFile,\n )}\"));`,\n );\n }\n if (route.loadingFile) {\n loadingImports.push(\n `const ${name}Loading = lazy(() => import(\"@/pages/${toImportPath(\n route.loadingFile,\n )}\"));`,\n );\n }\n\n if (route.children) {\n this.collectImports(\n route.children,\n lazyImports,\n errorImports,\n loadingImports,\n );\n }\n }\n }\n\n private emitRouteArray(routes: RouteConfig[], parentPath?: string): string {\n const items = routes.map((r) => this.emitRouteObject(r, parentPath));\n return `[\\n ${items.join(\",\\n \")}\\n]`;\n }\n\n private emitRouteObject(route: RouteConfig, parentPath?: string): string {\n const parts: string[] = [];\n const name = route.name;\n\n // path\n if (route.index) {\n parts.push(\"index: true\");\n } else if (route.pathless) {\n // 无路径布局不输出 path\n } else if (route.path === \"*\") {\n parts.push(`path: \"*\"`);\n } else if (\n parentPath &&\n route.path.startsWith(parentPath) &&\n parentPath !== \"/\"\n ) {\n const rel = route.path.slice(parentPath.length + 1);\n parts.push(`path: \"${rel || \"\"}\"`);\n } else {\n parts.push(`path: \"${route.path}\"`);\n }\n\n // element\n if (route.isLayout && route.layoutFile) {\n const loadingProp = route.loadingFile ? ` Loading={${name}Loading}` : \"\";\n parts.push(\n `element: <LazyRoute Component={${name}Layout}${loadingProp} />`,\n );\n } else if (route.file) {\n parts.push(`element: <LazyRoute Component={${name}Page} />`);\n }\n\n // errorElement\n if (route.errorFile) {\n parts.push(`errorElement: <LazyRoute Component={${name}Error} />`);\n }\n\n // children\n if (route.children && route.children.length > 0) {\n const childParent = route.pathless ? parentPath : route.path;\n parts.push(\n `children: ${this.emitRouteArray(route.children, childParent)}`,\n );\n }\n\n return `{\\n ${parts.join(\",\\n \")}\\n }`;\n }\n\n /**\n * 写入路由文件\n */\n write(): void {\n const { outputDir } = this.options;\n const code = this.generateCode();\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n writeFileSync(join(outputDir, \"routes.tsx\"), code, \"utf-8\");\n if (process.env.DEBUG) console.log(`[ywkf] 约定式路由已生成`);\n }\n}\n\n/**\n * 生成约定式路由\n */\nexport function generateConventionalRoutes(options: GeneratorOptions): void {\n new ConventionalRouteGenerator(options).write();\n}\n","import type { Compiler } from \"@rspack/core\";\nimport { ConventionalRouteGenerator } from \"./generator.js\";\nimport { join } from \"path\";\nimport { watch, existsSync, readFileSync } from \"fs\";\n\nexport interface ConventionalRoutePluginOptions {\n /** 页面目录 */\n pagesDir: string;\n /** 输出目录 */\n outputDir: string;\n /** 路由 basename */\n basename?: string;\n /** 是否监听文件变化(开发模式) */\n watch?: boolean;\n}\n\n/**\n * 约定式路由 Rspack 插件\n *\n * 在编译开始前扫描 src/pages 目录,生成 .ywkf/routes.tsx\n */\nexport class ConventionalRoutePlugin {\n private options: ConventionalRoutePluginOptions;\n private isWatching = false;\n private hasGenerated = false;\n private lastGeneratedContent = \"\";\n\n constructor(options: ConventionalRoutePluginOptions) {\n this.options = options;\n }\n\n apply(compiler: Compiler): void {\n const pluginName = \"ConventionalRoutePlugin\";\n\n // 只在首次编译时生成路由\n compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {\n if (!this.hasGenerated) {\n this.generateRoutes();\n this.hasGenerated = true;\n }\n callback();\n });\n\n // 开发模式下监听文件变化\n if (this.options.watch && !this.isWatching) {\n this.watchPages();\n }\n }\n\n /**\n * 生成路由文件(带内容比对,避免不必要的重复生成)\n */\n private generateRoutes(): void {\n try {\n const generator = new ConventionalRouteGenerator({\n pagesDir: this.options.pagesDir,\n outputDir: this.options.outputDir,\n basename: this.options.basename,\n });\n\n const newContent = generator.generateCode();\n const outputPath = join(this.options.outputDir, \"routes.tsx\");\n\n // 比对内容,如果相同则跳过(去掉时间戳后比较)\n const normalizedNew = this.normalizeContent(newContent);\n const normalizedOld = this.normalizeContent(this.lastGeneratedContent);\n\n if (normalizedNew === normalizedOld) {\n return; // 内容相同,跳过生成\n }\n\n generator.write();\n this.lastGeneratedContent = newContent;\n } catch (error) {\n console.error(\"[ywkf] 生成路由失败:\", error);\n }\n }\n\n /**\n * 标准化内容(去掉时间戳等动态部分)\n */\n private normalizeContent(content: string): string {\n return content.replace(/\\/\\/ Generated at: .+/g, \"\");\n }\n\n /**\n * 监听页面目录变化\n */\n private watchPages(): void {\n if (!existsSync(this.options.pagesDir)) {\n return;\n }\n\n this.isWatching = true;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const watcher = watch(\n this.options.pagesDir,\n { recursive: true },\n (eventType, filename) => {\n // 只处理 tsx/jsx 文件\n if (!filename?.match(/\\.(tsx?|jsx?)$/)) {\n return;\n }\n\n // 防抖处理\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n debounceTimer = setTimeout(() => {\n if (process.env.DEBUG) console.log(`[ywkf] 检测到页面变化: ${filename}`);\n this.generateRoutes();\n }, 500);\n }\n );\n\n // 进程退出时关闭监听\n process.on(\"exit\", () => {\n watcher.close();\n });\n }\n}\n"],"mappings":"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","names":["join","existsSync"]}
@@ -134,8 +134,21 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as wr
134
134
  import { join as join3 } from "path";
135
135
 
136
136
  // src/router/generator.ts
137
- import { existsSync as existsSync2, readdirSync, statSync, mkdirSync, writeFileSync } from "fs";
137
+ import {
138
+ existsSync as existsSync2,
139
+ readdirSync,
140
+ statSync,
141
+ mkdirSync,
142
+ writeFileSync
143
+ } from "fs";
138
144
  import { join, relative } from "path";
145
+ function winPath(path) {
146
+ const isExtendedLengthPath = /^\\\\\?\\/.test(path);
147
+ if (isExtendedLengthPath) {
148
+ return path;
149
+ }
150
+ return path.replace(/\\/g, "/");
151
+ }
139
152
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
140
153
  "components",
141
154
  "hooks",
@@ -192,14 +205,17 @@ var ConventionalRouteGenerator = class {
192
205
  );
193
206
  if (pathlessLayout) {
194
207
  const pathlessChildren = this.scanDirectory(subDirPath, routePath);
208
+ const pathlessLayoutRel = winPath(
209
+ relative(this.options.pagesDir, join(subDirPath, pathlessLayout))
210
+ );
195
211
  childRoutes.push({
196
212
  path: routePath,
197
213
  name: this.generateRouteName(subDir.slice(2)),
198
- layoutFile: relative(this.options.pagesDir, join(subDirPath, pathlessLayout)),
214
+ layoutFile: pathlessLayoutRel,
199
215
  isLayout: true,
200
216
  pathless: true,
201
217
  children: pathlessChildren.filter(
202
- (r) => r.layoutFile !== relative(this.options.pagesDir, join(subDirPath, pathlessLayout))
218
+ (r) => r.layoutFile !== pathlessLayoutRel
203
219
  )
204
220
  });
205
221
  } else {
@@ -211,7 +227,7 @@ var ConventionalRouteGenerator = class {
211
227
  childRoutes.push(...this.scanDirectory(subDirPath, subRoutePath));
212
228
  }
213
229
  const routeName = this.generateRouteName(routePath);
214
- const relPath = (file) => relative(this.options.pagesDir, join(dir, file));
230
+ const relPath = (file) => winPath(relative(this.options.pagesDir, join(dir, file)));
215
231
  if (layoutFile) {
216
232
  const layoutChildren = [];
217
233
  if (pageFile) {
@@ -357,26 +373,39 @@ export default routes;
357
373
  const toImportPath = (f) => f.replace(/\.(tsx?|jsx?)$/, "");
358
374
  if (route.isLayout && route.layoutFile) {
359
375
  lazyImports.push(
360
- `const ${name}Layout = lazy(() => import("@/pages/${toImportPath(route.layoutFile)}"));`
376
+ `const ${name}Layout = lazy(() => import("@/pages/${toImportPath(
377
+ route.layoutFile
378
+ )}"));`
361
379
  );
362
380
  }
363
381
  if (route.file) {
364
382
  lazyImports.push(
365
- `const ${name}Page = lazy(() => import("@/pages/${toImportPath(route.file)}"));`
383
+ `const ${name}Page = lazy(() => import("@/pages/${toImportPath(
384
+ route.file
385
+ )}"));`
366
386
  );
367
387
  }
368
388
  if (route.errorFile) {
369
389
  errorImports.push(
370
- `const ${name}Error = lazy(() => import("@/pages/${toImportPath(route.errorFile)}"));`
390
+ `const ${name}Error = lazy(() => import("@/pages/${toImportPath(
391
+ route.errorFile
392
+ )}"));`
371
393
  );
372
394
  }
373
395
  if (route.loadingFile) {
374
396
  loadingImports.push(
375
- `const ${name}Loading = lazy(() => import("@/pages/${toImportPath(route.loadingFile)}"));`
397
+ `const ${name}Loading = lazy(() => import("@/pages/${toImportPath(
398
+ route.loadingFile
399
+ )}"));`
376
400
  );
377
401
  }
378
402
  if (route.children) {
379
- this.collectImports(route.children, lazyImports, errorImports, loadingImports);
403
+ this.collectImports(
404
+ route.children,
405
+ lazyImports,
406
+ errorImports,
407
+ loadingImports
408
+ );
380
409
  }
381
410
  }
382
411
  }
@@ -402,7 +431,9 @@ export default routes;
402
431
  }
403
432
  if (route.isLayout && route.layoutFile) {
404
433
  const loadingProp = route.loadingFile ? ` Loading={${name}Loading}` : "";
405
- parts.push(`element: <LazyRoute Component={${name}Layout}${loadingProp} />`);
434
+ parts.push(
435
+ `element: <LazyRoute Component={${name}Layout}${loadingProp} />`
436
+ );
406
437
  } else if (route.file) {
407
438
  parts.push(`element: <LazyRoute Component={${name}Page} />`);
408
439
  }
@@ -411,7 +442,9 @@ export default routes;
411
442
  }
412
443
  if (route.children && route.children.length > 0) {
413
444
  const childParent = route.pathless ? parentPath : route.path;
414
- parts.push(`children: ${this.emitRouteArray(route.children, childParent)}`);
445
+ parts.push(
446
+ `children: ${this.emitRouteArray(route.children, childParent)}`
447
+ );
415
448
  }
416
449
  return `{
417
450
  ${parts.join(",\n ")}