@esmx/router-vue 3.0.0-rc.29 → 3.0.0-rc.31

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/src/use.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  provide,
9
9
  ref
10
10
  } from 'vue';
11
- import { createSymbolProperty } from './util';
11
+ import { createDependentProxy, createSymbolProperty } from './util';
12
12
 
13
13
  export interface VueInstance {
14
14
  $parent?: VueInstance | null;
@@ -18,7 +18,7 @@ export interface VueInstance {
18
18
 
19
19
  interface RouterContext {
20
20
  router: Router;
21
- route: Ref<Route>;
21
+ route: Route;
22
22
  }
23
23
 
24
24
  const ROUTER_CONTEXT_KEY = Symbol('router-context');
@@ -136,7 +136,7 @@ export function getRouter(instance?: VueInstance): Router {
136
136
  * ```
137
137
  */
138
138
  export function getRoute(instance?: VueInstance): Route {
139
- return findRouterContext(instance).route.value;
139
+ return findRouterContext(instance).route;
140
140
  }
141
141
 
142
142
  /**
@@ -224,7 +224,7 @@ export function useRouter(): Router {
224
224
  * ```
225
225
  */
226
226
  export function useRoute(): Route {
227
- return useRouterContext('useRoute').route.value;
227
+ return useRouterContext('useRoute').route;
228
228
  }
229
229
 
230
230
  /**
@@ -259,20 +259,23 @@ export function useRoute(): Route {
259
259
  export function useProvideRouter(router: Router): void {
260
260
  const proxy = getCurrentProxy('useProvideRouter');
261
261
 
262
+ const dep = ref(false);
263
+
264
+ const proxiedRouter = createDependentProxy(router, dep);
265
+ const proxiedRoute = createDependentProxy(router.route, dep);
266
+
262
267
  const context: RouterContext = {
263
- router,
264
- route: ref(router.route) as Ref<Route>
268
+ router: proxiedRouter,
269
+ route: proxiedRoute
265
270
  };
266
271
 
267
- // Provide context via Vue 3's provide/inject (works in setup)
268
272
  provide(ROUTER_INJECT_KEY, context);
269
-
270
- // Also set on component instance for fallback (works after mount)
271
273
  routerContextProperty.set(proxy, context);
272
274
 
273
275
  const unwatch = router.afterEach((to: Route) => {
274
276
  if (router.route === to) {
275
- to.syncTo(context.route.value);
277
+ to.syncTo(proxiedRoute);
278
+ dep.value = !dep.value;
276
279
  }
277
280
  });
278
281
 
@@ -311,11 +314,8 @@ export function useProvideRouter(router: Router): void {
311
314
  */
312
315
  export function useLink(props: RouterLinkProps) {
313
316
  const router = useRouter();
314
- const route = useRoute();
315
317
 
316
318
  return computed(() => {
317
- // vue will trigger a re-render when route.fullPath changes
318
- route.fullPath;
319
319
  return router.resolveLink(props);
320
320
  });
321
321
  }
package/src/util.test.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * @vitest-environment happy-dom
3
3
  */
4
- import { beforeEach, describe, expect, it } from 'vitest';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { computed, nextTick, ref } from 'vue';
5
6
  import { version } from 'vue';
6
7
  import {
8
+ createDependentProxy,
7
9
  createSymbolProperty,
8
10
  isESModule,
9
11
  isVue3,
@@ -251,168 +253,183 @@ describe('util.ts - Utility Functions', () => {
251
253
  });
252
254
 
253
255
  it('should return false for primitive values', () => {
254
- const primitives = ['string', 42, true, false, Symbol('test')];
256
+ const primitives = [42, 'string', true, false, Symbol('test')];
255
257
 
256
258
  primitives.forEach((primitive) => {
257
259
  expect(isESModule(primitive)).toBe(false);
258
260
  });
259
261
  });
260
- });
261
- });
262
262
 
263
- describe('resolveComponent', () => {
264
- describe('should return null for falsy inputs', () => {
265
- const falsyValues = [null, undefined, false, 0, '', Number.NaN];
263
+ it('should return false for functions and arrays', () => {
264
+ const nonObjects = [
265
+ () => {},
266
+ () => {},
267
+ [],
268
+ [1, 2, 3],
269
+ new Date()
270
+ ];
266
271
 
267
- falsyValues.forEach((value) => {
268
- it(`should return null for ${value}`, () => {
269
- expect(resolveComponent(value)).toBeNull();
272
+ nonObjects.forEach((nonObj) => {
273
+ expect(isESModule(nonObj)).toBe(false);
270
274
  });
271
275
  });
272
276
  });
277
+ });
273
278
 
274
- describe('should handle ES modules', () => {
275
- it('should return default export when available', () => {
276
- const defaultComponent = { name: 'DefaultComponent' };
277
- const esModule = {
278
- __esModule: true,
279
- default: defaultComponent,
280
- namedExport: { name: 'NamedComponent' }
281
- };
282
-
283
- const result = resolveComponent(esModule);
284
-
285
- expect(result).toBe(defaultComponent);
279
+ describe('resolveComponent', () => {
280
+ describe('should return default export or module itself', () => {
281
+ it('should return null for falsy values', () => {
282
+ expect(resolveComponent(null)).toBeNull();
283
+ expect(resolveComponent(undefined)).toBeNull();
284
+ expect(resolveComponent(false)).toBeNull();
285
+ expect(resolveComponent(0)).toBeNull();
286
+ expect(resolveComponent('')).toBeNull();
286
287
  });
287
288
 
288
- it('should return the module itself when no default export', () => {
289
- const esModule = {
289
+ it('should return default export if available', () => {
290
+ const defaultExport = { name: 'DefaultComponent' };
291
+ const module = {
290
292
  __esModule: true,
291
- namedExport: { name: 'NamedComponent' }
293
+ default: defaultExport,
294
+ other: 'value'
292
295
  };
293
296
 
294
- const result = resolveComponent(esModule);
295
-
296
- expect(result).toBe(esModule);
297
+ expect(resolveComponent(module)).toBe(defaultExport);
297
298
  });
298
299
 
299
- it('should prefer default export over module when both exist', () => {
300
- const defaultComponent = { name: 'DefaultComponent' };
301
- const esModule = {
300
+ it('should return the module itself if no default export', () => {
301
+ const module = {
302
302
  __esModule: true,
303
- default: defaultComponent,
304
- name: 'ModuleComponent'
303
+ someExport: 'value',
304
+ otherExport: 42
305
305
  };
306
306
 
307
- const result = resolveComponent(esModule);
308
-
309
- expect(result).toBe(defaultComponent);
310
- expect(result).not.toBe(esModule);
307
+ expect(resolveComponent(module)).toBe(module);
311
308
  });
312
309
 
313
310
  it('should handle modules with Symbol.toStringTag', () => {
314
- const defaultComponent = { name: 'SymbolTagComponent' };
315
- const esModule = {
311
+ const defaultExport = { name: 'SymbolDefaultComponent' };
312
+ const module = {
316
313
  [Symbol.toStringTag]: 'Module',
317
- default: defaultComponent
314
+ default: defaultExport
318
315
  };
319
316
 
320
- const result = resolveComponent(esModule);
321
-
322
- expect(result).toBe(defaultComponent);
317
+ expect(resolveComponent(module)).toBe(defaultExport);
323
318
  });
324
319
 
325
- it('should handle falsy default export', () => {
326
- const falsyDefaults = [null, undefined, false, 0, ''];
327
-
328
- falsyDefaults.forEach((falsyDefault) => {
329
- const esModule = {
330
- __esModule: true,
331
- default: falsyDefault,
332
- fallback: { name: 'FallbackComponent' }
333
- };
320
+ it('should return non-module objects as is', () => {
321
+ const nonModule = { prop: 'value' };
322
+ expect(resolveComponent(nonModule)).toBe(nonModule);
323
+ });
334
324
 
335
- const result = resolveComponent(esModule);
325
+ it('should handle various component types', () => {
326
+ const functionComponent = () => ({ name: 'FunctionComponent' });
327
+ expect(resolveComponent(functionComponent)).toBe(
328
+ functionComponent
329
+ );
336
330
 
337
- // Should return the module itself when default is falsy
338
- expect(result).toBe(esModule);
339
- });
331
+ class ClassComponent {
332
+ name = 'ClassComponent';
333
+ }
334
+ const classInstance = new ClassComponent();
335
+ expect(resolveComponent(classInstance)).toBe(classInstance);
340
336
  });
341
337
  });
338
+ });
342
339
 
343
- describe('should handle non-ES modules', () => {
344
- it('should return component directly for non-ES modules', () => {
345
- const component = { name: 'RegularComponent' };
346
-
347
- const result = resolveComponent(component);
340
+ describe('createDependentProxy', () => {
341
+ it('should return original property values', () => {
342
+ const original = { foo: 'bar', count: 42 };
343
+ const dep = ref(false);
344
+ const proxy = createDependentProxy(original, dep);
348
345
 
349
- expect(result).toBe(component);
350
- });
351
-
352
- it('should return function components directly', () => {
353
- const functionComponent = () => ({ name: 'FunctionComponent' });
346
+ expect(proxy.foo).toBe('bar');
347
+ expect(proxy.count).toBe(42);
348
+ });
354
349
 
355
- const result = resolveComponent(functionComponent);
350
+ it('should handle method calls correctly', () => {
351
+ const original = {
352
+ items: [1, 2, 3],
353
+ getItems() {
354
+ return this.items;
355
+ }
356
+ };
357
+ const dep = ref(false);
358
+ const proxy = createDependentProxy(original, dep);
356
359
 
357
- expect(result).toBe(functionComponent);
358
- });
360
+ expect(proxy.getItems()).toEqual([1, 2, 3]);
361
+ expect(proxy.getItems()).toBe(original.items);
362
+ });
359
363
 
360
- it('should return class components directly', () => {
361
- class ClassComponent {
362
- name = 'ClassComponent';
364
+ it('should handle nested property access', () => {
365
+ const original = {
366
+ nested: {
367
+ value: 'nested-value'
363
368
  }
369
+ };
370
+ const dep = ref(false);
371
+ const proxy = createDependentProxy(original, dep);
364
372
 
365
- const result = resolveComponent(ClassComponent);
366
-
367
- expect(result).toBe(ClassComponent);
368
- });
373
+ expect(proxy.nested.value).toBe('nested-value');
369
374
  });
370
375
 
371
- describe('edge cases', () => {
372
- it('should handle circular references in modules', () => {
373
- const esModule: Record<string, unknown> = {
374
- __esModule: true
375
- };
376
- esModule.default = esModule; // Circular reference
376
+ it('should allow property modification', () => {
377
+ const original = { value: 'original' };
378
+ const dep = ref(false);
379
+ const proxy = createDependentProxy(original, dep);
377
380
 
378
- const result = resolveComponent(esModule);
381
+ proxy.value = 'modified';
382
+ expect(proxy.value).toBe('modified');
383
+ expect(original.value).toBe('modified');
384
+ });
385
+
386
+ it('should trigger computed updates when dependency changes', async () => {
387
+ const original = { value: 'test' };
388
+ const dep = ref(false);
389
+ const proxy = createDependentProxy(original, dep);
379
390
 
380
- expect(result).toBe(esModule);
391
+ const computedValue = computed(() => {
392
+ return proxy.value + '-' + String(dep.value);
381
393
  });
382
394
 
383
- it('should handle deeply nested default exports', () => {
384
- const actualComponent = { name: 'DeepComponent' };
385
- const esModule = {
386
- __esModule: true,
387
- default: {
388
- default: {
389
- default: actualComponent
390
- }
391
- }
392
- };
395
+ expect(computedValue.value).toBe('test-false');
393
396
 
394
- const result = resolveComponent(esModule);
397
+ dep.value = true;
398
+ await nextTick();
399
+ expect(computedValue.value).toBe('test-true');
395
400
 
396
- // Should only resolve one level of default
397
- expect(result).toEqual({
398
- default: {
399
- default: actualComponent
400
- }
401
- });
402
- });
401
+ proxy.value = 'updated';
402
+ dep.value = false;
403
+ await nextTick();
404
+ expect(computedValue.value).toBe('updated-false');
405
+ });
403
406
 
404
- it('should handle modules with both __esModule and Symbol.toStringTag', () => {
405
- const defaultComponent = { name: 'BothPropertiesComponent' };
406
- const esModule = {
407
- __esModule: true,
408
- [Symbol.toStringTag]: 'Module',
409
- default: defaultComponent
410
- };
407
+ it('should handle special properties', () => {
408
+ const symbol = Symbol('test');
409
+ const original = {
410
+ [symbol]: 'symbol-value'
411
+ };
412
+ const dep = ref(false);
413
+ const proxy = createDependentProxy(original, dep);
414
+
415
+ expect(proxy[symbol]).toBe('symbol-value');
416
+ });
411
417
 
412
- const result = resolveComponent(esModule);
418
+ it('should read the dependency on property access', () => {
419
+ const original = { value: 'test' };
420
+ const dep = ref(false);
413
421
 
414
- expect(result).toBe(defaultComponent);
422
+ const spy = vi.fn();
423
+ const depValue = dep.value; // 预先读取一次值
424
+ vi.spyOn(dep, 'value', 'get').mockImplementation(() => {
425
+ spy();
426
+ return depValue;
415
427
  });
428
+
429
+ const proxy = createDependentProxy(original, dep);
430
+
431
+ proxy.value;
432
+ expect(spy).toHaveBeenCalled();
416
433
  });
417
434
  });
418
435
  });
package/src/util.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { version } from 'vue';
2
+ import type { Ref } from 'vue';
2
3
 
3
4
  export const isVue3 = version.startsWith('3.');
4
5
 
@@ -13,6 +14,18 @@ export function createSymbolProperty<T>(symbol: symbol) {
13
14
  } as const;
14
15
  }
15
16
 
17
+ export function createDependentProxy<T extends object>(
18
+ obj: T,
19
+ dep: Ref<boolean>
20
+ ): T {
21
+ return new Proxy(obj, {
22
+ get(target, prop, receiver) {
23
+ dep.value;
24
+ return Reflect.get(target, prop, receiver);
25
+ }
26
+ });
27
+ }
28
+
16
29
  export function isESModule(obj: unknown): obj is Record<string | symbol, any> {
17
30
  if (!obj || typeof obj !== 'object') return false;
18
31
  const module = obj as Record<string | symbol, any>;