@fycoding/uni-router-enhance 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,571 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ createRouter: () => createRouter,
34
+ routeTypesPlugin: () => routeTypesPlugin
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/type.ts
39
+ var CloseTypes = /* @__PURE__ */ ((CloseTypes2) => {
40
+ CloseTypes2["default"] = "default";
41
+ CloseTypes2["current"] = "current";
42
+ CloseTypes2["all"] = "all";
43
+ return CloseTypes2;
44
+ })(CloseTypes || {});
45
+
46
+ // src/utils.ts
47
+ function extractSecondPathSegment(url) {
48
+ const segments = url.split("/").slice(1, -1);
49
+ return segments.length === 1 ? segments[0] : segments.join("_");
50
+ }
51
+ var resolveRouteName = (routePath, strategy = "default") => {
52
+ if (typeof strategy === "function") {
53
+ return strategy(routePath);
54
+ }
55
+ const normalized = routePath.replace(/^\/+/, "").replace(/\.vue$/i, "");
56
+ if (strategy === "package_page") {
57
+ const segments = normalized.split("/").filter(Boolean);
58
+ if (segments[segments.length - 1] === "index") {
59
+ segments.pop();
60
+ }
61
+ return segments.length > 0 ? segments.join("_") : void 0;
62
+ }
63
+ return extractSecondPathSegment(routePath);
64
+ };
65
+ var isEnumValue = (enumObject, value) => Object.values(enumObject).includes(value);
66
+ var ensureLeadingSlash = (url) => url.startsWith("/") ? url : `/${url}`;
67
+ var buildUrlWithQuery = (url, query) => {
68
+ const normalized = ensureLeadingSlash(url);
69
+ const queryEntries = Object.entries(query ?? {}).filter(([, value]) => value !== void 0);
70
+ if (queryEntries.length === 0) {
71
+ return normalized;
72
+ }
73
+ const queryString = queryEntries.map(([key, value]) => {
74
+ const serialized = typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
75
+ return `${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`;
76
+ }).join("&");
77
+ return `${normalized}?${queryString}`;
78
+ };
79
+ var resolveCloseType = (close) => {
80
+ if (!close) return "default" /* default */;
81
+ if (isEnumValue(CloseTypes, close)) return close;
82
+ if (typeof close === "string" && close in CloseTypes) return CloseTypes[close];
83
+ return "default" /* default */;
84
+ };
85
+ function parseRoutesFromPagesJson(routes, namingStrategy = "default") {
86
+ const routeMeta = /* @__PURE__ */ new Map();
87
+ const tabBarPaths = /* @__PURE__ */ new Set();
88
+ if (routes.tabBar?.list) {
89
+ routes.tabBar.list.forEach((item) => {
90
+ tabBarPaths.add(item.pagePath);
91
+ });
92
+ }
93
+ if (routes.pages) {
94
+ routes.pages.forEach((page) => {
95
+ const name = resolveRouteName(page.path, namingStrategy);
96
+ if (name) {
97
+ routeMeta.set(name, {
98
+ ...page,
99
+ name,
100
+ isTabBar: tabBarPaths.has(page.path) || page.type === "tabBar",
101
+ url: page.path
102
+ });
103
+ }
104
+ });
105
+ }
106
+ if (routes.subPackages) {
107
+ const subpackages = routes.subPackages;
108
+ subpackages.forEach((subpackage) => {
109
+ const root = subpackage.root;
110
+ subpackage.pages.forEach((page) => {
111
+ const fullPath = `${root}/${page.path}`;
112
+ const name = resolveRouteName(fullPath, namingStrategy);
113
+ ;
114
+ if (name) {
115
+ routeMeta.set(name, {
116
+ ...page,
117
+ name,
118
+ isTabBar: tabBarPaths.has(fullPath) || page.type === "tabBar",
119
+ url: fullPath
120
+ });
121
+ }
122
+ });
123
+ });
124
+ }
125
+ return routeMeta;
126
+ }
127
+
128
+ // src/useRouter.ts
129
+ var performNavigation = async (url, close) => {
130
+ const navigationMethods = {
131
+ ["default" /* default */]: (options) => new Promise((resolve, reject) => {
132
+ uni.navigateTo({
133
+ ...options,
134
+ success: () => resolve(),
135
+ fail: (err) => reject(err)
136
+ });
137
+ }),
138
+ ["current" /* current */]: (options) => new Promise((resolve, reject) => {
139
+ uni.redirectTo({
140
+ ...options,
141
+ success: () => resolve(),
142
+ fail: (err) => reject(err)
143
+ });
144
+ }),
145
+ ["all" /* all */]: (options) => new Promise((resolve, reject) => {
146
+ uni.reLaunch({
147
+ ...options,
148
+ success: () => resolve(),
149
+ fail: (err) => reject(err)
150
+ });
151
+ })
152
+ };
153
+ await navigationMethods[close]({ url });
154
+ };
155
+ function createRouterHook(router) {
156
+ const getCurrentRouteName = () => {
157
+ const currentPage = getCurrentPages().at(-1);
158
+ if (!currentPage?.route) {
159
+ return "";
160
+ }
161
+ return router.resolveNameByUrl(currentPage.route) || "";
162
+ };
163
+ const basicPush = async (input) => {
164
+ const routeData = typeof input === "string" ? { path: input } : input;
165
+ const path2 = routeData.path;
166
+ if (!path2) {
167
+ const error = new Error("\u8DEF\u7531\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
168
+ routeData.fail?.(error);
169
+ if (!routeData.fail) throw error;
170
+ return;
171
+ }
172
+ const meta = router.getRouteMeta(path2);
173
+ if (!meta) {
174
+ const error = new Error(`\u627E\u4E0D\u5230\u5339\u914D\u7684\u8DEF\u7531\u914D\u7F6E: ${String(path2)}`);
175
+ routeData.fail?.(error);
176
+ if (!routeData.fail) throw error;
177
+ return;
178
+ }
179
+ const query = routeData.query ?? {};
180
+ const closeType = resolveCloseType(routeData.close);
181
+ const routePayload = { query, closeType, meta };
182
+ const fromName = getCurrentRouteName();
183
+ const fromMeta = fromName ? router.getRouteMeta(fromName) : void 0;
184
+ const to = {
185
+ name: path2,
186
+ meta,
187
+ query,
188
+ path: meta.url
189
+ };
190
+ const from = {
191
+ name: fromName || "",
192
+ meta: fromMeta,
193
+ query: {},
194
+ path: fromMeta?.url || ""
195
+ };
196
+ let navigationResult;
197
+ let redirectTo;
198
+ try {
199
+ const beforeResult = await router.runBeforeInterceptors(to, from);
200
+ if (!beforeResult.shouldContinue) {
201
+ if (beforeResult.redirectTo) {
202
+ redirectTo = beforeResult.redirectTo;
203
+ } else {
204
+ return;
205
+ }
206
+ }
207
+ } catch (error) {
208
+ routeData.fail?.(error);
209
+ if (!routeData.fail) throw error;
210
+ return;
211
+ }
212
+ if (redirectTo) {
213
+ if (typeof redirectTo === "string") {
214
+ return basicPush(
215
+ typeof routeData === "object" ? { ...routeData, path: redirectTo } : redirectTo
216
+ );
217
+ } else {
218
+ return basicPush({
219
+ ...routeData,
220
+ path: redirectTo.path,
221
+ query: redirectTo.query || routeData.query
222
+ // TODO: 处理 replace 选项
223
+ });
224
+ }
225
+ }
226
+ try {
227
+ const handler = router.getHandler(path2);
228
+ navigationResult = handler ? await handler(routePayload) : void 0;
229
+ await router.runAfterInterceptors(to, from);
230
+ } catch (error) {
231
+ routeData.fail?.(error);
232
+ if (!routeData.fail) throw error;
233
+ return;
234
+ }
235
+ if (navigationResult === false) {
236
+ return;
237
+ }
238
+ router.setPageCache(path2, {
239
+ query,
240
+ handlerResult: navigationResult
241
+ });
242
+ try {
243
+ if (meta.isTabBar) {
244
+ if (Object.keys(query).length > 0) {
245
+ console.warn("\u8DF3\u8F6C tabBar \u9875\u9762\u65F6\u4F1A\u5FFD\u7565 query \u53C2\u6570");
246
+ }
247
+ await new Promise((resolve, reject) => {
248
+ uni.switchTab({
249
+ url: ensureLeadingSlash(meta.url),
250
+ success: () => resolve(),
251
+ fail: (err) => reject(err)
252
+ });
253
+ });
254
+ } else {
255
+ await performNavigation(buildUrlWithQuery(meta.url, query), closeType);
256
+ }
257
+ routeData.success?.(navigationResult);
258
+ } catch (error) {
259
+ router.deletePageCache(path2);
260
+ routeData.fail?.(error);
261
+ if (!routeData.fail) throw error;
262
+ }
263
+ };
264
+ const push = async (data, callbacks) => {
265
+ if (data == null) {
266
+ const error = new Error("\u8DEF\u7531\u53C2\u6570\u4E0D\u80FD\u4E3A\u7A7A");
267
+ callbacks?.fail?.(error);
268
+ throw error;
269
+ }
270
+ const routeData = typeof data === "string" ? { path: data } : data;
271
+ const finalRouteData = {
272
+ ...routeData,
273
+ success: callbacks?.success ?? routeData.success,
274
+ fail: callbacks?.fail ?? routeData.fail
275
+ };
276
+ await basicPush(finalRouteData);
277
+ };
278
+ return {
279
+ push
280
+ };
281
+ }
282
+
283
+ // src/useRoute.ts
284
+ var import_vue = require("vue");
285
+ var decodeQuery = (query) => {
286
+ const decoded = {};
287
+ for (const key in query) {
288
+ const value = query[key];
289
+ if (typeof value === "string") {
290
+ try {
291
+ decoded[key] = decodeURIComponent(value);
292
+ } catch {
293
+ decoded[key] = value;
294
+ }
295
+ } else {
296
+ decoded[key] = value;
297
+ }
298
+ }
299
+ return decoded;
300
+ };
301
+ var parseQueryString = (queryString) => {
302
+ if (!queryString) return {};
303
+ return queryString.split("&").reduce((acc, segment) => {
304
+ if (!segment) return acc;
305
+ const [rawKey, rawValue = ""] = segment.split("=");
306
+ const key = decodeURIComponent(rawKey);
307
+ const value = decodeURIComponent(rawValue);
308
+ acc[key] = value;
309
+ return acc;
310
+ }, {});
311
+ };
312
+ var resolvePageOptions = (page) => {
313
+ if (!page) return {};
314
+ const runtimePage = page;
315
+ const sources = [];
316
+ if (page.options && Object.keys(page.options).length > 0) {
317
+ sources.push(decodeQuery(page.options));
318
+ }
319
+ if (runtimePage.$page?.options && Object.keys(runtimePage.$page.options).length > 0) {
320
+ sources.push(decodeQuery(runtimePage.$page.options));
321
+ }
322
+ if (runtimePage.$page?.fullPath) {
323
+ const queryIndex = runtimePage.$page.fullPath.indexOf("?");
324
+ if (queryIndex !== -1) {
325
+ sources.push(parseQueryString(runtimePage.$page.fullPath.slice(queryIndex + 1)));
326
+ }
327
+ }
328
+ if (sources.length === 0) {
329
+ return {};
330
+ }
331
+ return Object.assign({}, ...sources);
332
+ };
333
+ function createRouteHook(router) {
334
+ const state = (0, import_vue.reactive)({
335
+ name: "",
336
+ meta: void 0,
337
+ query: {},
338
+ handlerResult: void 0
339
+ });
340
+ const fillRouteState = () => {
341
+ const pages = getCurrentPages();
342
+ const currentPage = pages[pages.length - 1];
343
+ if (!currentPage?.route) {
344
+ console.warn("\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u9875\u9762\u7684\u8DEF\u7531\u4FE1\u606F");
345
+ return;
346
+ }
347
+ const routeName = router.resolveNameByUrl(currentPage.route) || extractSecondPathSegment(currentPage.route);
348
+ const meta = router.getRouteMeta(routeName);
349
+ const cache = router.getPageCache(routeName);
350
+ const runtimeQuery = resolvePageOptions(currentPage);
351
+ const mergedQuery = {
352
+ ...runtimeQuery,
353
+ ...cache?.query ? decodeQuery(cache.query) : {}
354
+ };
355
+ state.name = routeName;
356
+ state.meta = meta;
357
+ state.query = mergedQuery;
358
+ state.handlerResult = cache?.handlerResult;
359
+ };
360
+ fillRouteState();
361
+ (0, import_vue.onMounted)(() => {
362
+ fillRouteState();
363
+ });
364
+ return state;
365
+ }
366
+
367
+ // src/create.ts
368
+ function createRouter(routes, options) {
369
+ const handlers = /* @__PURE__ */ new Map();
370
+ const beforeInterceptors = [];
371
+ const afterInterceptors = [];
372
+ const routeMeta = /* @__PURE__ */ new Map();
373
+ const pageCache = /* @__PURE__ */ new Map();
374
+ const namingStrategy = options?.namingStrategy || "default";
375
+ if (routes) {
376
+ parseRoutesFromPagesJson(routes, namingStrategy).forEach((meta, name) => {
377
+ routeMeta.set(name, meta);
378
+ });
379
+ }
380
+ const addInterceptor = (bucket, guard) => {
381
+ bucket.push(guard);
382
+ return () => {
383
+ const index = bucket.indexOf(guard);
384
+ if (index >= 0) bucket.splice(index, 1);
385
+ };
386
+ };
387
+ const runInterceptors = async (bucket, to, from) => {
388
+ for (const guard of bucket) {
389
+ const result = await guard(to, from);
390
+ if (result === false) {
391
+ return { shouldContinue: false };
392
+ }
393
+ if (result && typeof result === "object" && "path" in result) {
394
+ return { shouldContinue: false, redirectTo: result };
395
+ }
396
+ if (typeof result === "string") {
397
+ return { shouldContinue: false, redirectTo: result };
398
+ }
399
+ }
400
+ return { shouldContinue: true };
401
+ };
402
+ const runAfterInterceptors = async (bucket, to, from) => {
403
+ for (const guard of bucket) {
404
+ await guard(to, from);
405
+ }
406
+ };
407
+ const routerImpl = {
408
+ // 注册路由处理函数
409
+ register: (name, handler) => {
410
+ handlers.set(name, handler);
411
+ return () => {
412
+ handlers.delete(name);
413
+ };
414
+ },
415
+ // 移除路由处理函数
416
+ unregister: (name) => handlers.delete(name),
417
+ // 检查是否已注册处理函数
418
+ has: (name) => handlers.has(name),
419
+ // 获取路由处理函数
420
+ getHandler: (name) => handlers.get(name),
421
+ // 添加 beforeEach 守卫
422
+ beforeEach: (interceptor) => addInterceptor(beforeInterceptors, interceptor),
423
+ // 添加 afterEach 守卫
424
+ afterEach: (interceptor) => addInterceptor(afterInterceptors, interceptor),
425
+ // 定义路由元信息
426
+ defineRoute: (name, meta) => {
427
+ routeMeta.set(name, meta);
428
+ return () => {
429
+ routeMeta.delete(name);
430
+ };
431
+ },
432
+ // 获取路由元信息
433
+ getRouteMeta: (name) => routeMeta.get(name),
434
+ // 获取所有路由元信息的只读快照
435
+ listRouteMeta: () => new Map(routeMeta),
436
+ // 执行 beforeEach 守卫链
437
+ runBeforeInterceptors: (to, from) => runInterceptors(beforeInterceptors, to, from),
438
+ // 执行 afterEach 守卫链
439
+ runAfterInterceptors: (to, from) => runAfterInterceptors(afterInterceptors, to, from),
440
+ // 页面缓存管理方法
441
+ setPageCache: (name, data) => pageCache.set(name, data),
442
+ getPageCache: (name) => pageCache.get(name),
443
+ deletePageCache: (name) => pageCache.delete(name),
444
+ resolveNameByUrl: (routePath) => {
445
+ const normalized = routePath.replace(/^\/+/, "");
446
+ for (const [name, meta] of routeMeta.entries()) {
447
+ if (meta.url === normalized) return name;
448
+ }
449
+ return void 0;
450
+ }
451
+ };
452
+ const useRouterFactory = () => createRouterHook(routerImpl);
453
+ const useRouteFactory = () => createRouteHook(routerImpl);
454
+ return {
455
+ afterEach: routerImpl.afterEach,
456
+ // 注册 afterEach 守卫
457
+ beforeEach: routerImpl.beforeEach,
458
+ // 注册 beforeEach 守卫
459
+ register: routerImpl.register,
460
+ // 注册路由处理函数
461
+ unregister: routerImpl.unregister,
462
+ // 移除路由处理函数
463
+ has: routerImpl.has,
464
+ // 检查处理函数是否存在
465
+ useRouter: useRouterFactory,
466
+ // 创建 useRouter 钩子
467
+ useRoute: useRouteFactory
468
+ // 创建 useRoute 钩子
469
+ };
470
+ }
471
+
472
+ // src/plugin.ts
473
+ var import_fs = __toESM(require("fs"));
474
+ var import_path = __toESM(require("path"));
475
+ function extractRouteNamesFromPages(pagesJson, namingStrategy) {
476
+ const routes = /* @__PURE__ */ new Set();
477
+ if (pagesJson.pages) {
478
+ pagesJson.pages.forEach((page) => {
479
+ const name = resolveRouteName(page.path, namingStrategy);
480
+ if (name) {
481
+ routes.add(name);
482
+ }
483
+ });
484
+ }
485
+ if (pagesJson.subPackages) {
486
+ pagesJson.subPackages.forEach((subpackage) => {
487
+ const root = subpackage.root;
488
+ subpackage.pages.forEach((page) => {
489
+ const name = resolveRouteName(`${root}/${page.path}`, namingStrategy);
490
+ if (name) {
491
+ routes.add(name);
492
+ }
493
+ });
494
+ });
495
+ }
496
+ return routes;
497
+ }
498
+ function generateTypeDefinition(routeNames, typeName = "ENHANCE_ROUTE_PATH") {
499
+ const sortedRoutes = [...routeNames].sort((a, b) => a.localeCompare(b));
500
+ return `export type ${typeName} =
501
+ ${sortedRoutes.map((name) => ` | '${name}'`).join("\n")}`;
502
+ }
503
+ function readPagesJson(pagesJsonPath) {
504
+ const content = import_fs.default.readFileSync(pagesJsonPath, "utf8");
505
+ return JSON.parse(content);
506
+ }
507
+ function generateRouteTypeFile(dts, pagesJsonPath, options) {
508
+ const pagesJson = readPagesJson(pagesJsonPath);
509
+ const routeNames = extractRouteNamesFromPages(pagesJson, options?.namingStrategy || "default");
510
+ const typeName = options?.typeName || "ENHANCE_ROUTE_PATH";
511
+ const typeDefinition = options?.generator ? options.generator(Array.from(routeNames), typeName) : generateTypeDefinition(Array.from(routeNames), typeName);
512
+ import_fs.default.writeFileSync(dts, typeDefinition, "utf8");
513
+ }
514
+ var getValidatedPaths = () => {
515
+ const inputDir = process.env.UNI_INPUT_DIR || `${process.env.INIT_CWD}/src`;
516
+ if (!inputDir || inputDir.trim() === "") {
517
+ throw new Error("Missing required environment variables: UNI_INPUT_DIR or INIT_CWD");
518
+ }
519
+ return import_path.default.resolve(inputDir, "pages.json");
520
+ };
521
+ function routeTypesPlugin(options) {
522
+ let isFirstBuild = true;
523
+ const config = typeof options === "string" ? { dts: options } : options;
524
+ return {
525
+ name: "route-types-generator",
526
+ /**
527
+ * 构建开始时生成路由类型
528
+ */
529
+ buildStart() {
530
+ if (isFirstBuild) {
531
+ try {
532
+ const pagesJsonPath = getValidatedPaths();
533
+ generateRouteTypeFile(config.dts, pagesJsonPath, {
534
+ typeName: config.typeName,
535
+ generator: config.generator,
536
+ namingStrategy: config.namingStrategy
537
+ });
538
+ isFirstBuild = false;
539
+ } catch (error) {
540
+ const message = error instanceof Error ? error.message : String(error);
541
+ console.warn("\u8DEF\u7531\u7C7B\u578B\u751F\u6210\u5931\u8D25:", message);
542
+ }
543
+ }
544
+ },
545
+ /**
546
+ * 热更新时监听 pages.json 变化
547
+ */
548
+ watchChange(id, change) {
549
+ if (change.event === "update" && id.includes("pages.json")) {
550
+ try {
551
+ const pagesJsonPath = getValidatedPaths();
552
+ generateRouteTypeFile(config.dts, pagesJsonPath, {
553
+ typeName: config.typeName,
554
+ generator: config.generator,
555
+ namingStrategy: config.namingStrategy
556
+ });
557
+ console.log("\u{1F504} \u68C0\u6D4B\u5230 pages.json \u53D8\u5316\uFF0C\u5DF2\u81EA\u52A8\u66F4\u65B0\u8DEF\u7531\u7C7B\u578B");
558
+ } catch (error) {
559
+ const message = error instanceof Error ? error.message : String(error);
560
+ console.warn("\u70ED\u66F4\u65B0\u65F6\u751F\u6210\u8DEF\u7531\u7C7B\u578B\u5931\u8D25:", message);
561
+ }
562
+ }
563
+ }
564
+ };
565
+ }
566
+ // Annotate the CommonJS export names for ESM import in node:
567
+ 0 && (module.exports = {
568
+ createRouter,
569
+ routeTypesPlugin
570
+ });
571
+ //# sourceMappingURL=index.js.map