Skip to content

Commit 7489d7c

Browse files
LuisDev20012heal1
andauthored
fix(bridge-vue3): pass hashRoute/memoryRoute to RemoteApp and fix path normalization (#4551)
Co-authored-by: Hanric <hanric.zhang@gmail.com> Co-authored-by: 2heal1 <TwoHeal@163.com>
1 parent 762b9ef commit 7489d7c

File tree

8 files changed

+5792
-7297
lines changed

8 files changed

+5792
-7297
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@module-federation/bridge-vue3': patch
3+
---
4+
5+
fix(bridge-vue3): pass hashRoute and memoryRoute to RemoteApp, fix path and redirect prefixing in addBasenameToNestedRoutes

packages/bridge/vue3-bridge/__tests__/routeUtils.test.ts

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,382 @@ describe('routeUtils', () => {
498498
expect(() => processRoutes({ router: doubleSlashRouter })).not.toThrow();
499499
});
500500
});
501+
502+
describe('processRoutes with hashRoute', () => {
503+
it('should create a hash history when hashRoute is true', () => {
504+
const routes = createNestedRoutes();
505+
const router = createRouter({
506+
history: createWebHistory(),
507+
routes,
508+
});
509+
510+
const result = processRoutes({
511+
router,
512+
hashRoute: true,
513+
});
514+
515+
expect(result.history).toBeDefined();
516+
// Hash history base includes the '#' prefix
517+
expect(result.history.base).toBe('/#');
518+
});
519+
520+
it('should prefix routes with basename when hashRoute is true and basename is provided', () => {
521+
const routes = createNestedRoutes();
522+
const router = createRouter({
523+
history: createWebHistory(),
524+
routes,
525+
});
526+
527+
const result = processRoutes({
528+
router,
529+
basename: '/app',
530+
hashRoute: true,
531+
});
532+
533+
expect(result.history).toBeDefined();
534+
expect(result.routes.length).toBeGreaterThan(0);
535+
536+
// Top-level routes should be prefixed with basename
537+
// Home route path '/' + basename '/app' normalizes to '/app'
538+
const homeRoute = result.routes.find((r) => r.name === 'Home');
539+
expect(homeRoute?.path).toBe('/app');
540+
541+
const dashboardRoute = result.routes.find((r) => r.name === 'Dashboard');
542+
// Paths are joined with '/' and normalized (no double slashes)
543+
expect(dashboardRoute?.path).toBe('/app/dashboard');
544+
});
545+
546+
it('should prefix nested children with basename when hashRoute is true', () => {
547+
const routes = createNestedRoutes();
548+
const router = createRouter({
549+
history: createWebHistory(),
550+
routes,
551+
});
552+
553+
const result = processRoutes({
554+
router,
555+
basename: '/app',
556+
hashRoute: true,
557+
});
558+
559+
const dashboardRoute = result.routes.find((r) => r.name === 'Dashboard');
560+
expect(dashboardRoute).toBeDefined();
561+
562+
if (dashboardRoute?.children) {
563+
const profileRoute = dashboardRoute.children.find(
564+
(child) => child.name === 'Profile',
565+
);
566+
// Paths are now properly joined with '/' separator
567+
expect(profileRoute?.path).toBe('/app/profile');
568+
569+
const settingsRoute = dashboardRoute.children.find(
570+
(child) => child.name === 'Settings',
571+
);
572+
expect(settingsRoute?.path).toBe('/app/settings');
573+
574+
if (settingsRoute?.children) {
575+
const accountRoute = settingsRoute.children.find(
576+
(child) => child.name === 'Account',
577+
);
578+
expect(accountRoute?.path).toBe('/app/account');
579+
}
580+
}
581+
});
582+
583+
it('should not prefix routes when hashRoute is true but no basename', () => {
584+
const routes = createNestedRoutes();
585+
const router = createRouter({
586+
history: createWebHistory(),
587+
routes,
588+
});
589+
590+
const result = processRoutes({
591+
router,
592+
hashRoute: true,
593+
});
594+
595+
const homeRoute = result.routes.find((r) => r.name === 'Home');
596+
expect(homeRoute?.path).toBe('/');
597+
598+
const dashboardRoute = result.routes.find((r) => r.name === 'Dashboard');
599+
expect(dashboardRoute?.path).toBe('/dashboard');
600+
});
601+
602+
it('should prefer memoryRoute over hashRoute when both are set', () => {
603+
const routes = createNestedRoutes();
604+
const router = createRouter({
605+
history: createWebHistory(),
606+
routes,
607+
});
608+
609+
const result = processRoutes({
610+
router,
611+
basename: '/app',
612+
memoryRoute: true,
613+
hashRoute: true,
614+
});
615+
616+
// memoryRoute takes priority (checked first in processRoutes)
617+
// Memory history uses basename as the base
618+
expect(result.history.base).toBe('/app');
619+
});
620+
621+
it('should preserve trailing slashes in paths when prefixing with basename', () => {
622+
const trailingSlashRoutes = [
623+
{
624+
path: '/dashboard/',
625+
name: 'Dashboard',
626+
component: { template: '<div>Dashboard</div>' },
627+
},
628+
{
629+
path: '/settings',
630+
name: 'Settings',
631+
component: { template: '<div>Settings</div>' },
632+
},
633+
];
634+
635+
const router = createRouter({
636+
history: createWebHistory(),
637+
routes: trailingSlashRoutes,
638+
});
639+
640+
const result = processRoutes({
641+
router,
642+
basename: '/app',
643+
hashRoute: true,
644+
});
645+
646+
// Trailing slash should be preserved for /dashboard/
647+
const dashboardRoute = result.routes.find((r) => r.name === 'Dashboard');
648+
expect(dashboardRoute?.path).toBe('/app/dashboard/');
649+
650+
// No trailing slash should remain absent for /settings
651+
const settingsRoute = result.routes.find((r) => r.name === 'Settings');
652+
expect(settingsRoute?.path).toBe('/app/settings');
653+
});
654+
655+
it('should preserve trailing slashes in string redirects', () => {
656+
const redirectRoutes = [
657+
{
658+
path: '/',
659+
redirect: '/dashboard/',
660+
},
661+
{
662+
path: '/dashboard/',
663+
name: 'Dashboard',
664+
component: { template: '<div>Dashboard</div>' },
665+
},
666+
];
667+
668+
const router = createRouter({
669+
history: createWebHistory(),
670+
routes: redirectRoutes,
671+
});
672+
673+
const result = processRoutes({
674+
router,
675+
basename: '/app',
676+
hashRoute: true,
677+
});
678+
679+
const rootRoute = result.routes.find((r) => r.path === '/app');
680+
expect(rootRoute?.redirect).toBe('/app/dashboard/');
681+
});
682+
});
683+
684+
describe('processRoutes with memoryRoute', () => {
685+
it('should create a memory history when memoryRoute is true', () => {
686+
const routes = createNestedRoutes();
687+
const router = createRouter({
688+
history: createWebHistory(),
689+
routes,
690+
});
691+
692+
const result = processRoutes({
693+
router,
694+
memoryRoute: true,
695+
});
696+
697+
expect(result.history).toBeDefined();
698+
expect(result.routes.length).toBeGreaterThan(0);
699+
});
700+
701+
it('should pass basename to memory history', () => {
702+
const routes = createNestedRoutes();
703+
const router = createRouter({
704+
history: createWebHistory(),
705+
routes,
706+
});
707+
708+
const result = processRoutes({
709+
router,
710+
basename: '/app',
711+
memoryRoute: true,
712+
});
713+
714+
expect(result.history).toBeDefined();
715+
expect(result.history.base).toBe('/app');
716+
});
717+
718+
it('should not prefix route paths when using memoryRoute', () => {
719+
const routes = createNestedRoutes();
720+
const router = createRouter({
721+
history: createWebHistory(),
722+
routes,
723+
});
724+
725+
const result = processRoutes({
726+
router,
727+
basename: '/app',
728+
memoryRoute: true,
729+
});
730+
731+
// Unlike hashRoute, memoryRoute does NOT prefix routes — it passes basename to createMemoryHistory
732+
const homeRoute = result.routes.find((r) => r.name === 'Home');
733+
expect(homeRoute?.path).toBe('/');
734+
735+
const dashboardRoute = result.routes.find((r) => r.name === 'Dashboard');
736+
expect(dashboardRoute?.path).toBe('/dashboard');
737+
});
738+
});
739+
740+
describe('processRoutes default (web history)', () => {
741+
it('should create web history with basename by default', () => {
742+
const routes = createNestedRoutes();
743+
const router = createRouter({
744+
history: createWebHistory(),
745+
routes,
746+
});
747+
748+
const result = processRoutes({
749+
router,
750+
basename: '/app',
751+
});
752+
753+
expect(result.history).toBeDefined();
754+
expect(result.history.base).toBe('/app');
755+
});
756+
757+
it('should not prefix route paths in default mode', () => {
758+
const routes = createNestedRoutes();
759+
const router = createRouter({
760+
history: createWebHistory(),
761+
routes,
762+
});
763+
764+
const result = processRoutes({
765+
router,
766+
basename: '/app',
767+
});
768+
769+
const homeRoute = result.routes.find((r) => r.name === 'Home');
770+
expect(homeRoute?.path).toBe('/');
771+
772+
const dashboardRoute = result.routes.find((r) => r.name === 'Dashboard');
773+
expect(dashboardRoute?.path).toBe('/dashboard');
774+
});
775+
});
776+
777+
describe('processRoutes hashRoute with redirects', () => {
778+
it('should prefix string redirects with basename', () => {
779+
const redirectRoutes = [
780+
{
781+
path: '/',
782+
redirect: '/dashboard',
783+
},
784+
{
785+
path: '/dashboard',
786+
name: 'Dashboard',
787+
component: { template: '<div>Dashboard</div>' },
788+
},
789+
{
790+
path: '/about',
791+
name: 'About',
792+
component: { template: '<div>About</div>' },
793+
},
794+
];
795+
796+
const router = createRouter({
797+
history: createWebHistory(),
798+
routes: redirectRoutes,
799+
});
800+
801+
const result = processRoutes({
802+
router,
803+
basename: '/barber',
804+
hashRoute: true,
805+
});
806+
807+
// The redirect route should have its redirect target prefixed
808+
const rootRoute = result.routes.find((r) => r.path === '/barber');
809+
expect(rootRoute).toBeDefined();
810+
expect(rootRoute?.redirect).toBe('/barber/dashboard');
811+
812+
// Other routes should be prefixed normally
813+
const dashboardRoute = result.routes.find((r) => r.name === 'Dashboard');
814+
expect(dashboardRoute?.path).toBe('/barber/dashboard');
815+
816+
const aboutRoute = result.routes.find((r) => r.name === 'About');
817+
expect(aboutRoute?.path).toBe('/barber/about');
818+
});
819+
820+
it('should prefix object redirects with path property', () => {
821+
const redirectRoutes = [
822+
{
823+
path: '/',
824+
redirect: { path: '/dashboard' },
825+
},
826+
{
827+
path: '/dashboard',
828+
name: 'Dashboard',
829+
component: { template: '<div>Dashboard</div>' },
830+
},
831+
];
832+
833+
const router = createRouter({
834+
history: createWebHistory(),
835+
routes: redirectRoutes,
836+
});
837+
838+
const result = processRoutes({
839+
router,
840+
basename: '/app',
841+
hashRoute: true,
842+
});
843+
844+
const rootRoute = result.routes.find((r) => r.path === '/app');
845+
expect(rootRoute).toBeDefined();
846+
expect(rootRoute?.redirect).toEqual({ path: '/app/dashboard' });
847+
});
848+
849+
it('should not modify named redirects (no path property)', () => {
850+
const redirectRoutes = [
851+
{
852+
path: '/',
853+
redirect: { name: 'Dashboard' },
854+
},
855+
{
856+
path: '/dashboard',
857+
name: 'Dashboard',
858+
component: { template: '<div>Dashboard</div>' },
859+
},
860+
];
861+
862+
const router = createRouter({
863+
history: createWebHistory(),
864+
routes: redirectRoutes,
865+
});
866+
867+
const result = processRoutes({
868+
router,
869+
basename: '/app',
870+
hashRoute: true,
871+
});
872+
873+
const rootRoute = result.routes.find((r) => r.path === '/app');
874+
expect(rootRoute).toBeDefined();
875+
// Named redirects should be untouched — Vue Router resolves by name, not path
876+
expect(rootRoute?.redirect).toEqual({ name: 'Dashboard' });
877+
});
878+
});
501879
});

0 commit comments

Comments
 (0)