bobe-router 0.0.60 → 0.0.61

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/dist/index.umd.js CHANGED
@@ -56,6 +56,13 @@
56
56
  return null;
57
57
  }
58
58
 
59
+ let GlobalKey = function (GlobalKey) {
60
+ GlobalKey["Routes"] = "__BOBE_INIT_ROUTES__";
61
+ GlobalKey["Menus"] = "__BOBE_INIT_MENUS__";
62
+ GlobalKey["Path"] = "__BOBE_INIT_PATH__";
63
+ return GlobalKey;
64
+ }({});
65
+
59
66
  function createRouteRecord(opts = {}) {
60
67
  return {
61
68
  import: opts.import,
@@ -74,40 +81,55 @@
74
81
  idleSet = new Set();
75
82
  maxPreload = 3;
76
83
  loadingCount = 0;
77
- constructor(routes, initialPath) {
84
+ ready(cb) {
85
+ if (cb) {
86
+ if (this.#inited) {
87
+ cb();
88
+ } else {
89
+ this.#readyQueue.push(cb);
90
+ }
91
+ return;
92
+ }
93
+ return new Promise(resolve => this.ready(resolve));
94
+ }
95
+ #inited = false;
96
+ #readyQueue = [];
97
+ constructor(opt) {
78
98
  super();
79
- if (routes) this.routes = routes;
80
- this.ready = this.#init(initialPath ?? (typeof location !== 'undefined' ? location.pathname : '/'));
99
+ const routes = opt?.routes;
100
+ const initialPath = opt?.initialPath;
101
+ this.routes = routes || globalThis[GlobalKey.Routes] || {};
102
+ const injectedMenus = globalThis[GlobalKey.Menus];
103
+ if (injectedMenus) this.menus = injectedMenus;
104
+ const path = initialPath || globalThis[GlobalKey.Path] || (typeof location !== 'undefined' ? location.pathname : '/');
105
+ this.#init(path);
81
106
  }
82
107
  async #init(path) {
83
108
  this.#initIdleSet();
84
109
  const result = match(path, this.routes);
85
110
  if (result) {
86
- await this.#loadComponent(result.path);
87
111
  const route = this.routes[result.path];
112
+ if (route?.component) {
113
+ route.status = 'loaded';
114
+ } else {
115
+ await this.#loadComponent(result.path);
116
+ }
88
117
  this.active = {
89
118
  path,
90
119
  params: result.params,
91
- component: route.component
120
+ component: route?.component
92
121
  };
93
122
  }
94
- if (typeof window !== 'undefined') {
95
- const preloaded = window.__INIT_ROUTE__?.[path];
96
- if (preloaded) {
97
- const route = this.routes[path];
98
- if (route) {
99
- route.component = preloaded;
100
- route.status = 'loaded';
101
- if (this.active) this.active.component = preloaded;
102
- }
103
- }
104
- }
105
123
  this.stack = [{
106
124
  path,
107
125
  params: this.active?.params ?? {}
108
126
  }];
109
127
  this.stackIndex = 0;
110
128
  this.#initBrowser();
129
+ this.#inited = true;
130
+ const q = this.#readyQueue;
131
+ this.#readyQueue = [];
132
+ for (const cb of q) cb();
111
133
  }
112
134
  #initBrowser() {
113
135
  if (typeof window === 'undefined') return;
@@ -189,6 +211,13 @@
189
211
  if (id !== this.navId) return;
190
212
  target.component = this.routes[target.path]?.component;
191
213
  this.active = target;
214
+ const hash = new URL(target.path, location.origin).hash;
215
+ if (hash) {
216
+ const el = document.querySelector(decodeURIComponent(hash));
217
+ if (el) el.scrollIntoView({
218
+ behavior: 'smooth'
219
+ });
220
+ }
192
221
  this.#preloadNext();
193
222
  }
194
223
  async #checkGuard(entry, type) {
@@ -247,6 +276,7 @@
247
276
  if (!link) return;
248
277
  const href = link.getAttribute('href');
249
278
  if (!href) return;
279
+ if (href.startsWith('#')) return;
250
280
  try {
251
281
  const url = new URL(href, location.origin);
252
282
  if (url.origin !== location.origin) return;
@@ -261,6 +291,7 @@
261
291
  const current = location.pathname + location.search;
262
292
  const idx = this.stack.findLastIndex(r => r.path === current);
263
293
  if (idx !== -1) {
294
+ if (idx === this.stackIndex) return;
264
295
  this.stackIndex = idx;
265
296
  const entry = this.stack[idx];
266
297
  this.#navigate(entry, {
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","sources":["../src/match.ts","../src/router.ts"],"sourcesContent":["import type { RouteMap, MatchResult } from './type';\n\n/** 编译路径模式为正则:/post/:id → ^\\/post\\/([^/]+)$,捕获名为 id */\nfunction compilePattern(pattern: string): { regex: RegExp; params: string[] } {\n const paramNames: string[] = [];\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // 转义正则特殊字符\n .replace(/:([a-zA-Z_]\\w*)/g, (_, name) => {\n paramNames.push(name);\n return '([^/]+)';\n });\n return {\n regex: new RegExp(`^${regexStr}$`),\n params: paramNames,\n };\n}\n\n/** 预编译所有路由的正则 */\ntype CompiledRoute = {\n pattern: string;\n regex: RegExp;\n paramNames: string[];\n};\n\nlet compiledCache: CompiledRoute[] | null = null;\nlet cacheKey: RouteMap | null = null;\n\nfunction compileRoutes(map: RouteMap): CompiledRoute[] {\n if (cacheKey === map && compiledCache) return compiledCache;\n compiledCache = Object.keys(map)\n .filter((k) => k !== '*') // 通配符最后匹配\n .map((pattern) => {\n const { regex, params: paramNames } = compilePattern(pattern);\n return { pattern, regex, paramNames };\n });\n // 通配符路由放最后\n if (map['*']) {\n compiledCache.push({ pattern: '*', regex: /.*/, paramNames: [] });\n }\n cacheKey = map;\n return compiledCache;\n}\n\n/**\n * 根据路径匹配路由表\n * @param path - URL 路径,如 /post/42\n * @param map - 路由表\n * @returns 匹配结果,含路径参数;null 表示 404\n */\nexport function match(path: string, map: RouteMap): MatchResult | null {\n // 确保以 / 开头\n const normalized = path.startsWith('/') ? path : `/${path}`;\n\n for (const compiled of compileRoutes(map)) {\n const m = normalized.match(compiled.regex);\n if (!m) continue;\n\n const params: Record<string, string> = {};\n for (let i = 0; i < compiled.paramNames.length; i++) {\n params[compiled.paramNames[i]] = m[i + 1];\n }\n return { path: compiled.pattern, params };\n }\n return null;\n}\n","import { Store, StoreIgnoreKeys } from 'aoye';\nimport type { RouteMap, RouteEntry, RouteRecord, GuardResult, Menu } from './type';\nimport { match } from './match';\n\n/** 创建带初始数据的 RouteRecord */\nexport function createRouteRecord(\n opts: Partial<Pick<RouteRecord, 'import' | 'component' | 'params'>> = {}\n): RouteRecord {\n return {\n import: opts.import,\n component: opts.component,\n status: opts.component ? 'loaded' : 'idle',\n params: opts.params,\n };\n}\n\nexport class Router extends Store {\n static [StoreIgnoreKeys] = ['routes', 'menus', 'stack', 'ready'] as string[];\n\n /** 当前激活的路由,模板用 {active.component} 渲染 */\n active: RouteEntry | null = null;\n\n /** 路由表 */\n routes: RouteMap = {};\n\n /** 目录嵌套菜单 */\n menus: Menu[] = [];\n\n /** 进入守卫 */\n enterGuard?: (to: RouteEntry) => GuardResult | Promise<GuardResult>;\n\n /** 离开守卫 */\n leaveGuard?: (from: RouteEntry) => GuardResult | Promise<GuardResult>;\n\n /** 历史栈(不响应式) */\n private stack: RouteEntry[] = [];\n private stackIndex = 0;\n\n /** 待预加载的路径集合 */\n private idleSet = new Set<string>();\n\n /** 最大并行预加载数 */\n maxPreload = 3;\n\n /** 当前正在加载的组件数 */\n private loadingCount = 0;\n\n /** 首屏初始化就绪后可继续操作 */\n ready: Promise<void>;\n\n constructor(routes?: RouteMap, initialPath?: string) {\n super();\n if (routes) this.routes = routes;\n this.ready = this.#init(initialPath ?? (typeof location !== 'undefined' ? location.pathname : '/'));\n }\n\n // ====== 初始化 ======\n async #init(path: string): Promise<void> {\n\n // 1. 初始化 idleSet(在加载首屏前,让预加载尽早启动)\n this.#initIdleSet();\n\n // 2. 预加载首屏组件\n const result = match(path, this.routes);\n if (result) {\n await this.#loadComponent(result.path);\n const route = this.routes[result.path];\n\n this.active = {\n path,\n params: result.params,\n component: route.component,\n };\n }\n\n // 2. 浏览器端从 SSR 注入获取首屏组件\n if (typeof window !== 'undefined') {\n const preloaded = (window as any).__INIT_ROUTE__?.[path];\n if (preloaded) {\n const route = this.routes[path];\n if (route) {\n route.component = preloaded;\n route.status = 'loaded';\n if (this.active) this.active.component = preloaded;\n }\n }\n }\n\n this.stack = [{ path, params: this.active?.params ?? {} }];\n this.stackIndex = 0;\n this.#initBrowser();\n }\n\n // ====== 浏览器初始化 ======\n\n #initBrowser(): void {\n if (typeof window === 'undefined') return;\n\n // 劫持容器内链接点击\n document.addEventListener('click', this.#onClick);\n\n // 浏览器前进/后退\n window.addEventListener('popstate', this.#onPopstate);\n }\n\n #initIdleSet(): void {\n for (const path of Object.keys(this.routes)) {\n if (this.routes[path].status === 'idle') {\n this.idleSet.add(path);\n }\n }\n // 移除首屏路径\n if (this.active) {\n this.idleSet.delete(this.active.path);\n }\n }\n\n // ====== 五个公开方法 ======\n\n /** 导航到新页面(追加历史记录) */\n async pushState(url: string): Promise<void> {\n const result = match(url, this.routes);\n if (!result) return;\n\n const target: RouteEntry = { path: url, params: result.params };\n // 截断当前位置之后的历史,再追加\n this.stack.length = this.stackIndex + 1;\n this.stack.push(target);\n this.stackIndex = this.stack.length - 1;\n await this.#navigate(target);\n }\n\n /** 替换当前页面(不追加历史记录) */\n async replaceState(url: string): Promise<void> {\n const result = match(url, this.routes);\n if (!result) return;\n\n const target: RouteEntry = { path: url, params: result.params };\n this.stack[this.stackIndex] = target;\n await this.#navigate(target, { replace: true });\n }\n\n /** 后退 */\n async back(): Promise<void> {\n if (this.stackIndex <= 0) return;\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n\n const target = this.stack[this.stackIndex - 1];\n if (!(await this.#checkGuard(target, 'enter'))) return;\n\n history.back(); // popstate 触发 Index 同步\n }\n\n /** 前进 */\n async forward(): Promise<void> {\n if (this.stackIndex >= this.stack.length - 1) return;\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n\n const target = this.stack[this.stackIndex + 1];\n if (!(await this.#checkGuard(target, 'enter'))) return;\n\n history.forward(); // popstate 触发 Index 同步\n }\n\n /** 跳转多步 */\n async go(delta: number): Promise<void> {\n if (delta === 0) return;\n const newIdx = this.stackIndex + delta;\n if (newIdx < 0 || newIdx >= this.stack.length) return;\n\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n if (!(await this.#checkGuard(this.stack[newIdx], 'enter'))) return;\n\n history.go(delta);\n }\n\n // ====== 内部实现 ======\n\n private navId = 0;\n\n async #navigate(target: RouteEntry, opts: { replace?: boolean } = {}): Promise<void> {\n const id = ++this.navId;\n\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n if (id !== this.navId) return; // 守卫期间有新导航,丢弃本次\n if (!(await this.#checkGuard(target, 'enter'))) return;\n if (id !== this.navId) return;\n\n // 保存当前页滚动位置\n if (this.active && this.stack[this.stackIndex]) {\n this.stack[this.stackIndex].scroll = window.scrollY;\n }\n\n if (!opts.replace) {\n history.pushState(null, '', target.path);\n } else {\n history.replaceState(null, '', target.path);\n }\n\n // 加载组件\n await this.#loadComponent(target.path);\n if (id !== this.navId) return; // 加载期间有新导航,丢弃本次\n\n target.component = this.routes[target.path]?.component;\n this.active = target;\n\n this.#preloadNext();\n }\n\n async #checkGuard(\n entry: RouteEntry,\n type: 'enter' | 'leave'\n ): Promise<boolean> {\n if (type === 'enter' && this.enterGuard) {\n const result = await this.enterGuard(entry);\n if (result === false) return false;\n if (typeof result === 'object' && !result.ok) return false;\n }\n if (type === 'leave' && this.leaveGuard) {\n const result = await this.leaveGuard(entry);\n if (result === false) return false;\n if (typeof result === 'object' && !result.ok) return false;\n }\n return true;\n }\n\n // ====== 组件异步加载 ======\n\n async #loadComponent(path: string): Promise<any> {\n const route = this.routes[path];\n if (!route) return undefined;\n\n switch (route.status) {\n case 'loaded':\n return route.component;\n case 'loading':\n return route.promise;\n case 'error':\n break; // 可重试\n case 'idle':\n break;\n }\n\n if (!route.import) {\n throw new Error(`Route \"${path}\" has no import function`);\n }\n\n this.idleSet.delete(path);\n route.status = 'loading';\n route.promise = route\n .import()\n .then((mod: any) => {\n const Comp = mod.default || mod;\n route.status = 'loaded';\n route.component = Comp;\n return route.component;\n })\n .catch((err) => {\n route.status = 'error';\n throw err;\n })\n .finally(() => {\n this.loadingCount--;\n this.#preloadNext();\n });\n\n return route.promise;\n }\n\n // ====== 空闲预加载 ======\n\n #preloadNext(): void {\n while (this.loadingCount < this.maxPreload && this.idleSet.size > 0) {\n const path = this.idleSet.values().next().value!;\n this.loadingCount++;\n const scheduler =\n typeof requestIdleCallback !== 'undefined'\n ? requestIdleCallback\n : (fn: () => void) => setTimeout(fn, 0);\n scheduler(() => {\n this.#loadComponent(path);\n });\n }\n }\n\n // ====== 链接劫持 ======\n\n #onClick = (e: MouseEvent): void => {\n const link = (e.target as Element).closest('a');\n if (!link) return;\n\n const href = link.getAttribute('href');\n if (!href) return;\n\n try {\n const url = new URL(href, location.origin);\n // 外部链接不拦截\n if (url.origin !== location.origin) return;\n // 新窗口、下载、快捷键不拦截\n if (link.target === '_blank') return;\n if (link.hasAttribute('download')) return;\n if (e.ctrlKey || e.metaKey || e.shiftKey) return;\n\n e.preventDefault();\n this.pushState(url.pathname + url.search + url.hash);\n } catch {\n // 无效 URL,不拦截\n }\n };\n\n // ====== popstate 回调 ======\n\n #onPopstate = (): void => {\n const current = location.pathname + location.search;\n const idx = this.stack.findLastIndex((r) => r.path === current);\n\n if (idx !== -1) {\n // 在栈中 → Router 产生的历史,移动指针\n this.stackIndex = idx;\n const entry = this.stack[idx];\n this.#navigate(entry, { replace: true });\n // 恢复滚动位置\n if (entry.scroll != null) {\n window.scrollTo(0, entry.scroll);\n }\n } else {\n // 不在栈中 → 外部跳转,重置栈\n const result = match(current, this.routes);\n const entry: RouteEntry = {\n path: current,\n params: result?.params ?? {},\n };\n this.stack = [entry];\n this.stackIndex = 0;\n this.active = entry;\n }\n };\n}\n"],"names":["compilePattern","pattern","paramNames","regexStr","replace","_","name","push","regex","RegExp","params","compiledCache","cacheKey","compileRoutes","map","Object","keys","filter","k","_compilePattern","match","path","normalized","startsWith","compiled","m","i","length","createRouteRecord","opts","import","component","status","Router","Store","StoreIgnoreKeys","active","routes","menus","stack","stackIndex","idleSet","Set","maxPreload","loadingCount","constructor","initialPath","ready","location","pathname","#init","result","route","window","preloaded","__INIT_ROUTE__","#initBrowser","document","addEventListener","#initIdleSet","add","delete","pushState","url","target","replaceState","back","history","forward","go","delta","newIdx","navId","#navigate","id","scroll","scrollY","#checkGuard","entry","type","enterGuard","ok","leaveGuard","#loadComponent","undefined","promise","Error","then","mod","Comp","default","catch","err","finally","#preloadNext","size","values","next","value","scheduler","requestIdleCallback","fn","setTimeout","e","link","closest","href","getAttribute","URL","origin","hasAttribute","ctrlKey","metaKey","shiftKey","preventDefault","search","hash","#onPopstate","current","idx","findLastIndex","r","scrollTo"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.js","sources":["../src/match.ts","../src/global.ts","../src/router.ts"],"sourcesContent":["import type { RouteMap, MatchResult } from './type';\n\n/** 编译路径模式为正则:/post/:id → ^\\/post\\/([^/]+)$,捕获名为 id */\nfunction compilePattern(pattern: string): { regex: RegExp; params: string[] } {\n const paramNames: string[] = [];\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // 转义正则特殊字符\n .replace(/:([a-zA-Z_]\\w*)/g, (_, name) => {\n paramNames.push(name);\n return '([^/]+)';\n });\n return {\n regex: new RegExp(`^${regexStr}$`),\n params: paramNames,\n };\n}\n\n/** 预编译所有路由的正则 */\ntype CompiledRoute = {\n pattern: string;\n regex: RegExp;\n paramNames: string[];\n};\n\nlet compiledCache: CompiledRoute[] | null = null;\nlet cacheKey: RouteMap | null = null;\n\nfunction compileRoutes(map: RouteMap): CompiledRoute[] {\n if (cacheKey === map && compiledCache) return compiledCache;\n compiledCache = Object.keys(map)\n .filter((k) => k !== '*') // 通配符最后匹配\n .map((pattern) => {\n const { regex, params: paramNames } = compilePattern(pattern);\n return { pattern, regex, paramNames };\n });\n // 通配符路由放最后\n if (map['*']) {\n compiledCache.push({ pattern: '*', regex: /.*/, paramNames: [] });\n }\n cacheKey = map;\n return compiledCache;\n}\n\n/**\n * 根据路径匹配路由表\n * @param path - URL 路径,如 /post/42\n * @param map - 路由表\n * @returns 匹配结果,含路径参数;null 表示 404\n */\nexport function match(path: string, map: RouteMap): MatchResult | null {\n // 确保以 / 开头\n const normalized = path.startsWith('/') ? path : `/${path}`;\n\n for (const compiled of compileRoutes(map)) {\n const m = normalized.match(compiled.regex);\n if (!m) continue;\n\n const params: Record<string, string> = {};\n for (let i = 0; i < compiled.paramNames.length; i++) {\n params[compiled.paramNames[i]] = m[i + 1];\n }\n return { path: compiled.pattern, params };\n }\n return null;\n}\n","/** 通过 globalThis 传递的路由全局变量名 */\nexport enum GlobalKey {\n /** 路由表:{ [url]: { import/component } } */\n Routes = '__BOBE_INIT_ROUTES__',\n /** 菜单树:Menu[] */\n Menus = '__BOBE_INIT_MENUS__',\n /** 初始路径 */\n Path = '__BOBE_INIT_PATH__',\n}\n","import { Store, StoreIgnoreKeys } from 'aoye';\nimport type { RouteMap, RouteEntry, RouteRecord, GuardResult, Menu, RouterOptions } from './type';\nimport { match } from './match';\nimport { GlobalKey } from './global';\n\n/** 创建带初始数据的 RouteRecord */\nexport function createRouteRecord(\n opts: Partial<Pick<RouteRecord, 'import' | 'component' | 'params'>> = {}\n): RouteRecord {\n return {\n import: opts.import,\n component: opts.component,\n status: opts.component ? 'loaded' : 'idle',\n params: opts.params,\n };\n}\n\nexport class Router extends Store {\n static [StoreIgnoreKeys] = ['routes', 'menus', 'stack', 'ready'] as string[];\n\n /** 当前激活的路由,模板用 {active.component} 渲染 */\n active: RouteEntry | null = null;\n\n /** 路由表 */\n routes: RouteMap = {};\n\n /** 目录嵌套菜单 */\n menus: Menu[] = [];\n\n /** 进入守卫 */\n enterGuard?: (to: RouteEntry) => GuardResult | Promise<GuardResult>;\n\n /** 离开守卫 */\n leaveGuard?: (from: RouteEntry) => GuardResult | Promise<GuardResult>;\n\n /** 历史栈(不响应式) */\n private stack: RouteEntry[] = [];\n private stackIndex = 0;\n\n /** 待预加载的路径集合 */\n private idleSet = new Set<string>();\n\n /** 最大并行预加载数 */\n maxPreload = 3;\n\n /** 当前正在加载的组件数 */\n private loadingCount = 0;\n\n /**\n * 注册首屏就绪回调。\n * - 已初始化 → 同步执行 cb\n * - 未初始化 → 入队,首屏加载完成后执行\n * 支持多次调用。\n * 无参数时返回 Promise。\n */\n ready(): Promise<void>;\n ready(cb: () => void): void;\n ready(cb?: () => void): Promise<void> | void {\n if (cb) {\n if (this.#inited) { cb(); }\n else { this.#readyQueue.push(cb); }\n return;\n }\n return new Promise<void>(resolve => this.ready(resolve));\n }\n\n #inited = false;\n #readyQueue: (() => void)[] = [];\n\n constructor(opt?: RouterOptions) {\n super();\n\n const routes = opt?.routes;\n const initialPath = opt?.initialPath;\n\n // 1. routes 优先级:用户传入 > SSR 注入 > 空\n this.routes = routes\n || (globalThis as any)[GlobalKey.Routes]\n || {};\n\n // 2. menus 优先级:SSR 注入 > 空\n const injectedMenus = (globalThis as any)[GlobalKey.Menus];\n if (injectedMenus) this.menus = injectedMenus;\n\n // 3. path 优先级:用户传入 > SSR 注入 > location > '/'\n const path = initialPath\n || (globalThis as any)[GlobalKey.Path]\n || (typeof location !== 'undefined' ? location.pathname : '/');\n\n this.#init(path);\n }\n\n // ====== 初始化 ======\n async #init(path: string): Promise<void> {\n\n // 1. 初始化 idleSet(在加载首屏前,让预加载尽早启动)\n this.#initIdleSet();\n\n // 2. 首屏:匹配路由,已有 component 则跳过 load\n const result = match(path, this.routes);\n if (result) {\n const route = this.routes[result.path];\n\n if (route?.component) {\n // SSR 注入或构造函数传入的 component,直接复用\n route.status = 'loaded';\n } else {\n await this.#loadComponent(result.path);\n }\n\n this.active = {\n path,\n params: result.params,\n component: route?.component,\n };\n }\n\n this.stack = [{ path, params: this.active?.params ?? {} }];\n this.stackIndex = 0;\n this.#initBrowser();\n\n // 就绪:执行所有排队回调\n this.#inited = true;\n const q = this.#readyQueue;\n this.#readyQueue = [];\n for (const cb of q) cb();\n }\n\n // ====== 浏览器初始化 ======\n\n #initBrowser(): void {\n if (typeof window === 'undefined') return;\n\n // 劫持容器内链接点击\n document.addEventListener('click', this.#onClick);\n\n // 浏览器前进/后退\n window.addEventListener('popstate', this.#onPopstate);\n }\n\n #initIdleSet(): void {\n for (const path of Object.keys(this.routes)) {\n if (this.routes[path].status === 'idle') {\n this.idleSet.add(path);\n }\n }\n // 移除首屏路径\n if (this.active) {\n this.idleSet.delete(this.active.path);\n }\n }\n\n // ====== 五个公开方法 ======\n\n /** 导航到新页面(追加历史记录) */\n async pushState(url: string): Promise<void> {\n const result = match(url, this.routes);\n if (!result) return;\n\n const target: RouteEntry = { path: url, params: result.params };\n // 截断当前位置之后的历史,再追加\n this.stack.length = this.stackIndex + 1;\n this.stack.push(target);\n this.stackIndex = this.stack.length - 1;\n await this.#navigate(target);\n }\n\n /** 替换当前页面(不追加历史记录) */\n async replaceState(url: string): Promise<void> {\n const result = match(url, this.routes);\n if (!result) return;\n\n const target: RouteEntry = { path: url, params: result.params };\n this.stack[this.stackIndex] = target;\n await this.#navigate(target, { replace: true });\n }\n\n /** 后退 */\n async back(): Promise<void> {\n if (this.stackIndex <= 0) return;\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n\n const target = this.stack[this.stackIndex - 1];\n if (!(await this.#checkGuard(target, 'enter'))) return;\n\n history.back(); // popstate 触发 Index 同步\n }\n\n /** 前进 */\n async forward(): Promise<void> {\n if (this.stackIndex >= this.stack.length - 1) return;\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n\n const target = this.stack[this.stackIndex + 1];\n if (!(await this.#checkGuard(target, 'enter'))) return;\n\n history.forward(); // popstate 触发 Index 同步\n }\n\n /** 跳转多步 */\n async go(delta: number): Promise<void> {\n if (delta === 0) return;\n const newIdx = this.stackIndex + delta;\n if (newIdx < 0 || newIdx >= this.stack.length) return;\n\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n if (!(await this.#checkGuard(this.stack[newIdx], 'enter'))) return;\n\n history.go(delta);\n }\n\n // ====== 内部实现 ======\n\n private navId = 0;\n\n async #navigate(target: RouteEntry, opts: { replace?: boolean } = {}): Promise<void> {\n const id = ++this.navId;\n\n if (!(await this.#checkGuard(this.active!, 'leave'))) return;\n if (id !== this.navId) return; // 守卫期间有新导航,丢弃本次\n if (!(await this.#checkGuard(target, 'enter'))) return;\n if (id !== this.navId) return;\n\n // 保存当前页滚动位置\n if (this.active && this.stack[this.stackIndex]) {\n this.stack[this.stackIndex].scroll = window.scrollY;\n }\n\n if (!opts.replace) {\n history.pushState(null, '', target.path);\n } else {\n history.replaceState(null, '', target.path);\n }\n\n // 加载组件\n await this.#loadComponent(target.path);\n if (id !== this.navId) return; // 加载期间有新导航,丢弃本次\n\n target.component = this.routes[target.path]?.component;\n this.active = target;\n\n // hash 滚动\n const hash = new URL(target.path, location.origin).hash;\n if (hash) {\n const el = document.querySelector(decodeURIComponent(hash));\n if (el) (el as HTMLElement).scrollIntoView({ behavior: 'smooth' });\n }\n\n this.#preloadNext();\n }\n\n async #checkGuard(\n entry: RouteEntry,\n type: 'enter' | 'leave'\n ): Promise<boolean> {\n if (type === 'enter' && this.enterGuard) {\n const result = await this.enterGuard(entry);\n if (result === false) return false;\n if (typeof result === 'object' && !result.ok) return false;\n }\n if (type === 'leave' && this.leaveGuard) {\n const result = await this.leaveGuard(entry);\n if (result === false) return false;\n if (typeof result === 'object' && !result.ok) return false;\n }\n return true;\n }\n\n // ====== 组件异步加载 ======\n\n async #loadComponent(path: string): Promise<any> {\n const route = this.routes[path];\n if (!route) return undefined;\n\n switch (route.status) {\n case 'loaded':\n return route.component;\n case 'loading':\n return route.promise;\n case 'error':\n break; // 可重试\n case 'idle':\n break;\n }\n\n if (!route.import) {\n throw new Error(`Route \"${path}\" has no import function`);\n }\n\n this.idleSet.delete(path);\n route.status = 'loading';\n route.promise = route\n .import()\n .then((mod: any) => {\n const Comp = mod.default || mod;\n route.status = 'loaded';\n route.component = Comp;\n return route.component;\n })\n .catch((err) => {\n route.status = 'error';\n throw err;\n })\n .finally(() => {\n this.loadingCount--;\n this.#preloadNext();\n });\n\n return route.promise;\n }\n\n // ====== 空闲预加载 ======\n\n #preloadNext(): void {\n while (this.loadingCount < this.maxPreload && this.idleSet.size > 0) {\n const path = this.idleSet.values().next().value!;\n this.loadingCount++;\n const scheduler =\n typeof requestIdleCallback !== 'undefined'\n ? requestIdleCallback\n : (fn: () => void) => setTimeout(fn, 0);\n scheduler(() => {\n this.#loadComponent(path);\n });\n }\n }\n\n // ====== 链接劫持 ======\n\n #onClick = (e: MouseEvent): void => {\n const link = (e.target as Element).closest('a');\n if (!link) return;\n\n const href = link.getAttribute('href');\n if (!href) return;\n\n // 纯 hash 链接放行,浏览器原生处理滚动\n if (href.startsWith('#')) return;\n\n try {\n const url = new URL(href, location.origin);\n // 外部链接不拦截\n if (url.origin !== location.origin) return;\n // 新窗口、下载、快捷键不拦截\n if (link.target === '_blank') return;\n if (link.hasAttribute('download')) return;\n if (e.ctrlKey || e.metaKey || e.shiftKey) return;\n\n e.preventDefault();\n this.pushState(url.pathname + url.search + url.hash);\n } catch {\n // 无效 URL,不拦截\n }\n };\n\n // ====== popstate 回调 ======\n\n #onPopstate = (): void => {\n const current = location.pathname + location.search;\n const idx = this.stack.findLastIndex((r) => r.path === current);\n\n if (idx !== -1) {\n // 仅 hash 变化,浏览器已更新 URL 并处理滚动,跳过 #navigate(避免 replaceState 抹掉 hash)\n if (idx === this.stackIndex) return;\n // 在栈中 → Router 产生的历史,移动指针\n this.stackIndex = idx;\n const entry = this.stack[idx];\n this.#navigate(entry, { replace: true });\n // 恢复滚动位置\n if (entry.scroll != null) {\n window.scrollTo(0, entry.scroll);\n }\n } else {\n // 不在栈中 → 外部跳转,重置栈\n const result = match(current, this.routes);\n const entry: RouteEntry = {\n path: current,\n params: result?.params ?? {},\n };\n this.stack = [entry];\n this.stackIndex = 0;\n this.active = entry;\n }\n };\n}\n"],"names":["compilePattern","pattern","paramNames","regexStr","replace","_","name","push","regex","RegExp","params","compiledCache","cacheKey","compileRoutes","map","Object","keys","filter","k","_compilePattern","match","path","normalized","startsWith","compiled","m","i","length","GlobalKey","createRouteRecord","opts","import","component","status","Router","Store","StoreIgnoreKeys","active","routes","menus","stack","stackIndex","idleSet","Set","maxPreload","loadingCount","ready","cb","Promise","resolve","constructor","opt","initialPath","globalThis","Routes","injectedMenus","Menus","Path","location","pathname","#init","result","route","q","#initBrowser","window","document","addEventListener","#initIdleSet","add","delete","pushState","url","target","replaceState","back","history","forward","go","delta","newIdx","navId","#navigate","id","scroll","scrollY","hash","URL","origin","el","querySelector","decodeURIComponent","scrollIntoView","behavior","#checkGuard","entry","type","enterGuard","ok","leaveGuard","#loadComponent","undefined","promise","Error","then","mod","Comp","default","catch","err","finally","#preloadNext","size","values","next","value","scheduler","requestIdleCallback","fn","setTimeout","e","link","closest","href","getAttribute","hasAttribute","ctrlKey","metaKey","shiftKey","preventDefault","search","#onPopstate","current","idx","findLastIndex","r","scrollTo"],"mappings":"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"}
@@ -0,0 +1,241 @@
1
+ 'use strict';
2
+
3
+ var path = require('path');
4
+ var fs = require('fs');
5
+
6
+ function _arrayLikeToArray(r, a) {
7
+ (null == a || a > r.length) && (a = r.length);
8
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
9
+ return n;
10
+ }
11
+ function _arrayWithHoles(r) {
12
+ if (Array.isArray(r)) return r;
13
+ }
14
+ function _iterableToArrayLimit(r, l) {
15
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
16
+ if (null != t) {
17
+ var e,
18
+ n,
19
+ i,
20
+ u,
21
+ a = [],
22
+ f = true,
23
+ o = false;
24
+ try {
25
+ if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
26
+ } catch (r) {
27
+ o = true, n = r;
28
+ } finally {
29
+ try {
30
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
31
+ } finally {
32
+ if (o) throw n;
33
+ }
34
+ }
35
+ return a;
36
+ }
37
+ }
38
+ function _nonIterableRest() {
39
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
40
+ }
41
+ function _slicedToArray(r, e) {
42
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
43
+ }
44
+ function _unsupportedIterableToArray(r, a) {
45
+ if (r) {
46
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
47
+ var t = {}.toString.call(r).slice(8, -1);
48
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
49
+ }
50
+ }
51
+
52
+ const RE_DIR = /^(?:(\d+)_)?([^_]+?)(?:_(.+?))?$/;
53
+ const DEFAULT_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx'];
54
+ function buildFileRegex(extensions) {
55
+ const exts = extensions.map(e => e.replace(/^\./, '')).join('|');
56
+ return new RegExp(`^(?:(\\d+)_)?([^_]+?)(?:_(.*))?\\.(${exts})$`);
57
+ }
58
+ function buildUrl(base, part) {
59
+ return (base === '/' ? '' : base) + '/' + part.replace(/\./g, '/');
60
+ }
61
+ function parseEntry(name, fileRe) {
62
+ const dirMatch = name.match(RE_DIR);
63
+ const isDir = !name.includes('.');
64
+ if (isDir && dirMatch) {
65
+ const _dirMatch = _slicedToArray(dirMatch, 4),
66
+ orderStr = _dirMatch[1],
67
+ pathPart = _dirMatch[2],
68
+ menuName = _dirMatch[3];
69
+ return {
70
+ order: orderStr ? parseInt(orderStr, 10) : Infinity,
71
+ pathPart,
72
+ menuName
73
+ };
74
+ }
75
+ const fileMatch = name.match(fileRe);
76
+ if (fileMatch) {
77
+ const _fileMatch = _slicedToArray(fileMatch, 4),
78
+ orderStr = _fileMatch[1],
79
+ pathPart = _fileMatch[2],
80
+ menuName = _fileMatch[3];
81
+ return {
82
+ order: orderStr ? parseInt(orderStr, 10) : Infinity,
83
+ pathPart,
84
+ menuName
85
+ };
86
+ }
87
+ return null;
88
+ }
89
+ function sortEntries(entries, fileRe) {
90
+ return entries.sort((a, b) => {
91
+ const pa = parseEntry(a.name, fileRe);
92
+ const pb = parseEntry(b.name, fileRe);
93
+ const oa = pa?.order ?? Infinity;
94
+ const ob = pb?.order ?? Infinity;
95
+ if (oa !== ob) return oa - ob;
96
+ return a.name.localeCompare(b.name);
97
+ });
98
+ }
99
+ function scanDir(absDir, basePath = absDir, parentPath = '', menuRef, extensions = DEFAULT_EXTENSIONS) {
100
+ const routes = [];
101
+ const menus = [];
102
+ let entries;
103
+ const fileRe = buildFileRegex(extensions);
104
+ try {
105
+ entries = fs.readdirSync(absDir, {
106
+ withFileTypes: true
107
+ });
108
+ } catch {
109
+ return {
110
+ routes,
111
+ menus
112
+ };
113
+ }
114
+ entries = sortEntries(entries, fileRe);
115
+ for (const ent of entries) {
116
+ const full = path.join(absDir, ent.name);
117
+ const parsed = parseEntry(ent.name, fileRe);
118
+ if (!parsed) continue;
119
+ const pathPart = parsed.pathPart,
120
+ menuName = parsed.menuName;
121
+ if (ent.isDirectory()) {
122
+ const urlPath = buildUrl(parentPath, pathPart);
123
+ const menu = {
124
+ name: menuName || pathPart,
125
+ hasComponent: false,
126
+ children: []
127
+ };
128
+ const child = scanDir(full, basePath, urlPath, menu, extensions);
129
+ routes.push(...child.routes);
130
+ menu.children = child.menus;
131
+ menus.push(menu);
132
+ } else if (ent.isFile()) {
133
+ const isIndex = pathPart === 'index';
134
+ const relFile = '/' + full.slice(basePath.length).replace(/^[/\\]/, '');
135
+ const urlPath = buildUrl(parentPath, pathPart);
136
+ if (isIndex) {
137
+ const url = parentPath || '/';
138
+ routes.push({
139
+ url,
140
+ file: relFile,
141
+ menuName
142
+ });
143
+ if (menuRef) {
144
+ if (menuName) menuRef.name = menuName;
145
+ menuRef.path = url;
146
+ menuRef.hasComponent = true;
147
+ } else if (menuName) {
148
+ menus.push({
149
+ name: menuName,
150
+ path: '/',
151
+ hasComponent: true,
152
+ children: []
153
+ });
154
+ }
155
+ } else {
156
+ routes.push({
157
+ url: urlPath,
158
+ file: relFile,
159
+ menuName
160
+ });
161
+ if (menuName) menus.push({
162
+ name: menuName,
163
+ path: urlPath,
164
+ hasComponent: true
165
+ });
166
+ }
167
+ }
168
+ }
169
+ return {
170
+ routes,
171
+ menus
172
+ };
173
+ }
174
+
175
+ let GlobalKey = function (GlobalKey) {
176
+ GlobalKey["Routes"] = "__BOBE_INIT_ROUTES__";
177
+ GlobalKey["Menus"] = "__BOBE_INIT_MENUS__";
178
+ GlobalKey["Path"] = "__BOBE_INIT_PATH__";
179
+ return GlobalKey;
180
+ }({});
181
+
182
+ const Routes = GlobalKey.Routes,
183
+ Menus = GlobalKey.Menus;
184
+ function generateCsrInit(items) {
185
+ const entries = items.map(({
186
+ url,
187
+ file
188
+ }) => ` '${url}': { import: () => import('${file}') }`);
189
+ return `globalThis['${Routes}'] = {\n` + entries.join(',\n') + '\n};';
190
+ }
191
+ function generateSsgInit(items) {
192
+ const imports = items.map(({
193
+ file
194
+ }, i) => `import __route_${i} from '${file}';`);
195
+ const names = items.map((_, i) => `__route_${i}`);
196
+ const entries = items.map(({
197
+ url
198
+ }, i) => ` '${url}': { component: __route_${i} }`);
199
+ return imports.join('\n') + `\n\nglobalThis['${Routes}'] = {\n` + entries.join(',\n') + '\n};' + `\nexport const __bobe_routes = [${names.join(', ')}];`;
200
+ }
201
+ function generateCsrMenus(menus) {
202
+ return `globalThis['${Menus}'] = ${JSON.stringify(menus)};`;
203
+ }
204
+
205
+ function bobeRouter(opt = {}) {
206
+ const dir = opt.dir || 'pages';
207
+ const extensions = opt.extensions || ['js', 'jsx', 'ts', 'tsx'];
208
+ let root;
209
+ return [{
210
+ name: 'bobe-router',
211
+ enforce: 'pre',
212
+ configResolved(config) {
213
+ root = config.root;
214
+ },
215
+ resolveId(id) {
216
+ if (id === 'bobe-router/csr-routes') return '\0bobe-router/csr-routes';
217
+ },
218
+ load(id) {
219
+ if (id === '\0bobe-router/csr-routes') {
220
+ const absDir = path.resolve(root, dir);
221
+ const _scanDir = scanDir(absDir, root, '', undefined, extensions),
222
+ routes = _scanDir.routes,
223
+ menus = _scanDir.menus;
224
+ return generateCsrInit(routes) + (menus.length ? '\n' + generateCsrMenus(menus) : '');
225
+ }
226
+ },
227
+ transform(code, id) {
228
+ if (id.includes('entry-server.ts')) {
229
+ const absDir = path.resolve(root, dir);
230
+ const _scanDir2 = scanDir(absDir, root, '', undefined, extensions),
231
+ routes = _scanDir2.routes,
232
+ menus = _scanDir2.menus;
233
+ const ssg = generateSsgInit(routes) + (menus.length ? '\n' + generateCsrMenus(menus) : '');
234
+ return ssg + '\n' + code;
235
+ }
236
+ }
237
+ }];
238
+ }
239
+
240
+ module.exports = bobeRouter;
241
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin/scan.ts","../src/global.ts","../src/plugin/generate.ts","../src/plugin/index.ts"],"sourcesContent":["import { Menu } from '#/type';\nimport { Dirent, readdirSync } from 'fs';\nimport { join } from 'path';\n\nexport interface ScanItem {\n url: string;\n file: string;\n menuName?: string;\n}\n\nconst RE_DIR = /^(?:(\\d+)_)?([^_]+?)(?:_(.+?))?$/;\n\nconst DEFAULT_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx'];\n\nfunction buildFileRegex(extensions: string[]): RegExp {\n const exts = extensions.map(e => e.replace(/^\\./, '')).join('|');\n return new RegExp(`^(?:(\\\\d+)_)?([^_]+?)(?:_(.*))?\\\\.(${exts})$`);\n}\n\nfunction buildUrl(base: string, part: string): string {\n return (base === '/' ? '' : base) + '/' + part.replace(/\\./g, '/');\n}\n\nfunction parseEntry(name: string, fileRe: RegExp): { order: number; pathPart: string; menuName?: string } | null {\n const dirMatch = name.match(RE_DIR);\n const isDir = !name.includes('.');\n\n if (isDir && dirMatch) {\n const [, orderStr, pathPart, menuName] = dirMatch;\n return { order: orderStr ? parseInt(orderStr, 10) : Infinity, pathPart, menuName };\n }\n\n const fileMatch = name.match(fileRe);\n if (fileMatch) {\n const [, orderStr, pathPart, menuName] = fileMatch;\n return { order: orderStr ? parseInt(orderStr, 10) : Infinity, pathPart, menuName };\n }\n\n return null;\n}\n\nfunction sortEntries(entries: Dirent[], fileRe: RegExp): Dirent[] {\n return entries.sort((a, b) => {\n const pa = parseEntry(a.name, fileRe);\n const pb = parseEntry(b.name, fileRe);\n const oa = pa?.order ?? Infinity;\n const ob = pb?.order ?? Infinity;\n if (oa !== ob) return oa - ob;\n return a.name.localeCompare(b.name);\n });\n}\n\nexport function scanDir(\n absDir: string,\n basePath = absDir,\n parentPath = '',\n menuRef?: Menu,\n extensions: string[] = DEFAULT_EXTENSIONS\n): { routes: ScanItem[]; menus: Menu[] } {\n const routes: ScanItem[] = [];\n const menus: Menu[] = [];\n let entries: Dirent[];\n const fileRe = buildFileRegex(extensions);\n\n try {\n entries = readdirSync(absDir, { withFileTypes: true });\n } catch {\n return { routes, menus };\n }\n\n entries = sortEntries(entries, fileRe);\n\n for (const ent of entries) {\n const full = join(absDir, ent.name);\n const parsed = parseEntry(ent.name, fileRe);\n if (!parsed) continue;\n\n const { pathPart, menuName } = parsed;\n\n if (ent.isDirectory()) {\n const urlPath = buildUrl(parentPath, pathPart);\n const menu: Menu = {\n name: menuName || pathPart,\n hasComponent: false,\n children: []\n };\n const child = scanDir(full, basePath, urlPath, menu, extensions);\n routes.push(...child.routes);\n menu.children = child.menus;\n menus.push(menu);\n } else if (ent.isFile()) {\n const isIndex = pathPart === 'index';\n const relFile = '/' + full.slice(basePath.length).replace(/^[/\\\\]/, '');\n const urlPath = buildUrl(parentPath, pathPart);\n\n if (isIndex) {\n const url = parentPath || '/';\n routes.push({ url, file: relFile, menuName });\n if (menuRef) {\n // 直接修改父菜单,不走 push\n if (menuName) menuRef.name = menuName;\n menuRef.path = url;\n menuRef.hasComponent = true;\n } else if (menuName) {\n menus.push({ name: menuName, path: '/', hasComponent: true, children: [] });\n }\n } else {\n routes.push({ url: urlPath, file: relFile, menuName });\n if (menuName) menus.push({ name: menuName, path: urlPath, hasComponent: true });\n }\n }\n }\n\n return { routes, menus };\n}\n","/** 通过 globalThis 传递的路由全局变量名 */\nexport enum GlobalKey {\n /** 路由表:{ [url]: { import/component } } */\n Routes = '__BOBE_INIT_ROUTES__',\n /** 菜单树:Menu[] */\n Menus = '__BOBE_INIT_MENUS__',\n /** 初始路径 */\n Path = '__BOBE_INIT_PATH__',\n}\n","import { GlobalKey } from '#/global';\nimport { Menu } from '#/type';\nimport type { ScanItem } from './scan';\n\nconst { Routes, Menus } = GlobalKey;\n\nexport function generateCsrInit(items: ScanItem[]): string {\n const entries = items.map(({ url, file }) =>\n ` '${url}': { import: () => import('${file}') }`\n );\n return `globalThis['${Routes}'] = {\\n` + entries.join(',\\n') + '\\n};';\n}\n\nexport function generateSsgInit(items: ScanItem[]): string {\n const imports = items.map(({ file }, i) =>\n `import __route_${i} from '${file}';`\n );\n const names = items.map((_, i) => `__route_${i}`);\n const entries = items.map(({ url }, i) =>\n ` '${url}': { component: __route_${i} }`\n );\n return imports.join('\\n')\n + `\\n\\nglobalThis['${Routes}'] = {\\n` + entries.join(',\\n') + '\\n};'\n + `\\nexport const __bobe_routes = [${names.join(', ')}];`;\n}\n\nexport function generateCsrMenus(menus: Menu[]): string {\n return `globalThis['${Menus}'] = ${JSON.stringify(menus)};`;\n}\n","import type { Plugin } from 'vite';\nimport { resolve } from 'path';\nimport { scanDir } from './scan';\nimport { generateCsrInit, generateSsgInit, generateCsrMenus } from './generate';\n\nexport interface BobeRouterPluginOptions {\n dir?: string;\n ssg?: boolean;\n /** 可识别的文件后缀,默认 ['js', 'jsx', 'ts', 'tsx'] */\n extensions?: string[];\n}\n\nexport default function bobeRouter(opt: BobeRouterPluginOptions = {}): Plugin[] {\n const dir = opt.dir || 'pages';\n const extensions = opt.extensions || ['js', 'jsx', 'ts', 'tsx'];\n\n let root: string;\n\n return [{\n name: 'bobe-router',\n enforce: 'pre',\n configResolved(config) {\n root = config.root;\n },\n\n resolveId(id) {\n if (id === 'bobe-router/csr-routes') return '\\0bobe-router/csr-routes';\n },\n\n load(id) {\n if (id === '\\0bobe-router/csr-routes') {\n const absDir = resolve(root, dir);\n const { routes, menus } = scanDir(absDir, root, '', undefined, extensions);\n return generateCsrInit(routes) + (menus.length ? '\\n' + generateCsrMenus(menus) : '');\n }\n },\n\n // SSR routes 直接注入到 entry-server.ts,不走虚拟模块(避免 Rollup tree-shake)\n transform(code, id) {\n if (id.includes('entry-server.ts')) {\n const absDir = resolve(root, dir);\n const { routes, menus } = scanDir(absDir, root, '', undefined, extensions);\n const ssg = generateSsgInit(routes) + (menus.length ? '\\n' + generateCsrMenus(menus) : '');\n return ssg + '\\n' + code;\n }\n }\n }];\n}\n"],"names":["RE_DIR","DEFAULT_EXTENSIONS","buildFileRegex","extensions","exts","map","e","replace","join","RegExp","buildUrl","base","part","parseEntry","name","fileRe","dirMatch","match","isDir","includes","_dirMatch","_slicedToArray","orderStr","pathPart","menuName","order","parseInt","Infinity","fileMatch","_fileMatch","sortEntries","entries","sort","a","b","pa","pb","oa","ob","localeCompare","scanDir","absDir","basePath","parentPath","menuRef","routes","menus","readdirSync","withFileTypes","ent","full","parsed","isDirectory","urlPath","menu","hasComponent","children","child","push","isFile","isIndex","relFile","slice","length","url","file","path","GlobalKey","Routes","Menus","generateCsrInit","items","generateSsgInit","imports","i","names","_","generateCsrMenus","JSON","stringify","bobeRouter","opt","dir","root","enforce","configResolved","config","resolveId","id","load","resolve","_scanDir","undefined","transform","code","_scanDir2","ssg"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface BobeRouterPluginOptions {
4
+ dir?: string;
5
+ ssg?: boolean;
6
+ /** 可识别的文件后缀,默认 ['js', 'jsx', 'ts', 'tsx'] */
7
+ extensions?: string[];
8
+ }
9
+ declare function bobeRouter(opt?: BobeRouterPluginOptions): Plugin[];
10
+
11
+ export { bobeRouter as default };
12
+ export type { BobeRouterPluginOptions };