@module-federation/bridge-vue3 2.3.1 → 2.3.3
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/CHANGELOG.md +40 -0
- package/dist/index.cjs +5 -5
- package/dist/index.d.ts +5 -1
- package/dist/index.js +523 -508
- package/package.json +4 -4
- package/src/index.ts +1 -0
- package/src/provider.ts +16 -4
- package/src/routeUtils.ts +85 -13
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "git+https://github.com/module-federation/core.git",
|
|
8
8
|
"directory": "packages/bridge/vue3-bridge"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.3.
|
|
10
|
+
"version": "2.3.3",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"vue-router": "=4"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@module-federation/bridge-shared": "2.3.
|
|
35
|
-
"@module-federation/runtime": "2.3.
|
|
36
|
-
"@module-federation/sdk": "2.3.
|
|
34
|
+
"@module-federation/bridge-shared": "2.3.3",
|
|
35
|
+
"@module-federation/runtime": "2.3.3",
|
|
36
|
+
"@module-federation/sdk": "2.3.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/react": "^18.3.11",
|
package/src/index.ts
CHANGED
package/src/provider.ts
CHANGED
|
@@ -16,9 +16,13 @@ type AddOptionsFnParams = {
|
|
|
16
16
|
|
|
17
17
|
export type ProviderFnParams = {
|
|
18
18
|
rootComponent: Vue.Component;
|
|
19
|
-
appOptions: (
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
appOptions: (params: AddOptionsFnParams) => {
|
|
20
|
+
router?: VueRouter.Router;
|
|
21
|
+
/** Called with the bridge's internal router after creation but before navigation.
|
|
22
|
+
* Use this to register global guards (beforeEach, afterEach, etc.) that would
|
|
23
|
+
* otherwise be lost when the bridge recreates the router. */
|
|
24
|
+
afterRouterCreate?: (router: VueRouter.Router) => void;
|
|
25
|
+
} | void;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
|
|
@@ -59,7 +63,7 @@ export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
|
|
|
59
63
|
...extraProps,
|
|
60
64
|
});
|
|
61
65
|
if (bridgeOptions?.router) {
|
|
62
|
-
const { history, routes } = processRoutes({
|
|
66
|
+
const { history, routes, patchRouter } = processRoutes({
|
|
63
67
|
router: bridgeOptions.router,
|
|
64
68
|
basename: info.basename,
|
|
65
69
|
memoryRoute: info.memoryRoute,
|
|
@@ -72,6 +76,14 @@ export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
|
|
|
72
76
|
routes,
|
|
73
77
|
});
|
|
74
78
|
|
|
79
|
+
if (patchRouter) {
|
|
80
|
+
patchRouter(router);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (bridgeOptions.afterRouterCreate) {
|
|
84
|
+
bridgeOptions.afterRouterCreate(router);
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
LoggerInstance.debug(`createBridgeComponent render router info>>>`, {
|
|
76
88
|
moduleName,
|
|
77
89
|
router,
|
package/src/routeUtils.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface RouteProcessingOptions {
|
|
|
10
10
|
export interface RouteProcessingResult {
|
|
11
11
|
history: VueRouter.RouterHistory;
|
|
12
12
|
routes: VueRouter.RouteRecordNormalized[];
|
|
13
|
+
patchRouter?: (router: VueRouter.Router) => void;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -27,8 +28,13 @@ function addBasenameToNestedRoutes(
|
|
|
27
28
|
* Join two path segments, collapse multiple slashes, and optionally
|
|
28
29
|
* preserve a trailing slash that was present in the original value.
|
|
29
30
|
* A bare '/' root is never considered an intentional trailing slash.
|
|
31
|
+
*
|
|
32
|
+
* Relative paths (not starting with '/') are left untouched — Vue Router
|
|
33
|
+
* resolves them against the parent route, which already carries the basename.
|
|
30
34
|
*/
|
|
31
35
|
const prefixPath = (original: string): string => {
|
|
36
|
+
if (!original.startsWith('/')) return original;
|
|
37
|
+
|
|
32
38
|
const hasTrailingSlash = original.length > 1 && original.endsWith('/');
|
|
33
39
|
const normalized =
|
|
34
40
|
`${basename}/${original}`.replace(/\/+/g, '/').replace(/\/$/, '') || '/';
|
|
@@ -68,6 +74,59 @@ function addBasenameToNestedRoutes(
|
|
|
68
74
|
});
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Create a patch function that rewrites path-based navigations to include
|
|
79
|
+
* the basename prefix. This is needed because createWebHashHistory() does
|
|
80
|
+
* not accept a basename argument, so router.push('/foo') would bypass the
|
|
81
|
+
* prefixed route definitions.
|
|
82
|
+
*
|
|
83
|
+
* By patching push/replace/resolve *before* Vue Router resolves the
|
|
84
|
+
* location we also avoid the "No match found" console warning.
|
|
85
|
+
*/
|
|
86
|
+
function createHashBasenamePatch(
|
|
87
|
+
basename: string,
|
|
88
|
+
): (router: VueRouter.Router) => void {
|
|
89
|
+
const normalized = basename.replace(/\/+$/, '');
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Only absolute paths (starting with '/') that don't already carry the
|
|
93
|
+
* basename prefix need rewriting. Relative segments ('settings'),
|
|
94
|
+
* query-only ('?tab=1'), and hash-only ('#anchor') strings are resolved
|
|
95
|
+
* by Vue Router against the current route and must pass through untouched.
|
|
96
|
+
*/
|
|
97
|
+
const needsPrefix = (path: string): boolean =>
|
|
98
|
+
path.startsWith('/') &&
|
|
99
|
+
path !== normalized &&
|
|
100
|
+
!path.startsWith(normalized + '/');
|
|
101
|
+
|
|
102
|
+
const prefix = (path: string): string =>
|
|
103
|
+
`${normalized}${path}`.replace(/\/+/g, '/');
|
|
104
|
+
|
|
105
|
+
const rewrite = (
|
|
106
|
+
to: VueRouter.RouteLocationRaw,
|
|
107
|
+
): VueRouter.RouteLocationRaw => {
|
|
108
|
+
if (typeof to === 'string') {
|
|
109
|
+
return needsPrefix(to) ? prefix(to) : to;
|
|
110
|
+
}
|
|
111
|
+
if ('path' in to && typeof to.path === 'string' && needsPrefix(to.path)) {
|
|
112
|
+
return { ...to, path: prefix(to.path) };
|
|
113
|
+
}
|
|
114
|
+
return to;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (router) => {
|
|
118
|
+
const originalPush = router.push.bind(router);
|
|
119
|
+
const originalReplace = router.replace.bind(router);
|
|
120
|
+
const originalResolve = router.resolve.bind(router);
|
|
121
|
+
|
|
122
|
+
router.push = (to: VueRouter.RouteLocationRaw) => originalPush(rewrite(to));
|
|
123
|
+
router.replace = (to: VueRouter.RouteLocationRaw) =>
|
|
124
|
+
originalReplace(rewrite(to));
|
|
125
|
+
router.resolve = ((to: VueRouter.RouteLocationRaw, ...rest: any[]) =>
|
|
126
|
+
originalResolve(rewrite(to), ...rest)) as typeof router.resolve;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
71
130
|
/**
|
|
72
131
|
* Route processing solution based on path analysis
|
|
73
132
|
*
|
|
@@ -89,11 +148,15 @@ export function processRoutes(
|
|
|
89
148
|
);
|
|
90
149
|
|
|
91
150
|
// Use Map/Set for O(1) lookup performance
|
|
92
|
-
|
|
151
|
+
// Store arrays because multiple routes can resolve to the same path
|
|
152
|
+
// (e.g. a parent and a default child with path: '')
|
|
153
|
+
const flatRoutesMap = new Map<string, VueRouter.RouteRecordNormalized[]>();
|
|
93
154
|
const processedRoutes = new Set<VueRouter.RouteRecordNormalized>();
|
|
94
155
|
|
|
95
156
|
flatRoutes.forEach((route) => {
|
|
96
|
-
flatRoutesMap.
|
|
157
|
+
const existing = flatRoutesMap.get(route.path) || [];
|
|
158
|
+
existing.push(route);
|
|
159
|
+
flatRoutesMap.set(route.path, existing);
|
|
97
160
|
});
|
|
98
161
|
|
|
99
162
|
/**
|
|
@@ -115,9 +178,20 @@ export function processRoutes(
|
|
|
115
178
|
for (let j = 0; j < route.children.length; j++) {
|
|
116
179
|
const child = route.children[j];
|
|
117
180
|
const fullPath = normalizePath(prefix, child.path);
|
|
118
|
-
const
|
|
181
|
+
const candidates = flatRoutesMap.get(fullPath) || [];
|
|
182
|
+
// Find a matching route that:
|
|
183
|
+
// 1. Hasn't been processed yet
|
|
184
|
+
// 2. Isn't the current parent route (avoids circular references when
|
|
185
|
+
// a child with path: '' resolves to the same absolute path)
|
|
186
|
+
// 3. Matches by name when the child definition specifies one
|
|
187
|
+
const childRoute = candidates.find(
|
|
188
|
+
(r) =>
|
|
189
|
+
!processedRoutes.has(r) &&
|
|
190
|
+
r !== route &&
|
|
191
|
+
(child.name == null || r.name === child.name),
|
|
192
|
+
);
|
|
119
193
|
|
|
120
|
-
if (childRoute
|
|
194
|
+
if (childRoute) {
|
|
121
195
|
// Create a new optimized route object with relative path for nested routes
|
|
122
196
|
const relativeChildRoute: VueRouter.RouteRecordNormalized = {
|
|
123
197
|
...childRoute,
|
|
@@ -146,21 +220,19 @@ export function processRoutes(
|
|
|
146
220
|
}
|
|
147
221
|
|
|
148
222
|
let history: VueRouter.RouterHistory;
|
|
223
|
+
let patchRouter: ((router: VueRouter.Router) => void) | undefined;
|
|
224
|
+
|
|
149
225
|
if (memoryRoute) {
|
|
150
|
-
// Memory route mode
|
|
151
226
|
history = VueRouter.createMemoryHistory(basename);
|
|
152
227
|
} else if (hashRoute) {
|
|
153
|
-
// Hash route mode
|
|
154
228
|
history = VueRouter.createWebHashHistory();
|
|
155
|
-
|
|
156
|
-
|
|
229
|
+
if (basename) {
|
|
230
|
+
routes = addBasenameToNestedRoutes(routes, basename);
|
|
231
|
+
patchRouter = createHashBasenamePatch(basename);
|
|
232
|
+
}
|
|
157
233
|
} else {
|
|
158
|
-
// Default Web History mode
|
|
159
234
|
history = VueRouter.createWebHistory(basename);
|
|
160
235
|
}
|
|
161
236
|
|
|
162
|
-
return {
|
|
163
|
-
history,
|
|
164
|
-
routes,
|
|
165
|
-
};
|
|
237
|
+
return { history, routes, patchRouter };
|
|
166
238
|
}
|